/**********************************************************************
 *
 * PostGIS - Spatial Types for PostgreSQL
 * http://postgis.net
 *
 * PostGIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * PostGIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with PostGIS.  If not, see <http://www.gnu.org/licenses/>.
 *
 **********************************************************************
 *
 * Copyright 2009-2011 Olivier Courtin <olivier.courtin@oslandia.com>
 *
 **********************************************************************/



/** @file
 *  Commons functions for all export functions
 */

#include "postgres.h"
#include "catalog/pg_type.h" /* for INT4OID */
#include "executor/spi.h"
#include "utils/builtins.h"
#include "utils/jsonb.h"

#include "../postgis_config.h"
#include "lwgeom_cache.h"
#include "lwgeom_pg.h"
#include "liblwgeom.h"
#include "liblwgeom_internal.h"

Datum LWGEOM_asGML(PG_FUNCTION_ARGS);
Datum LWGEOM_asGeoJson(PG_FUNCTION_ARGS);
Datum LWGEOM_asGeoJson_old(PG_FUNCTION_ARGS);
Datum LWGEOM_asSVG(PG_FUNCTION_ARGS);
Datum LWGEOM_asX3D(PG_FUNCTION_ARGS);
Datum LWGEOM_asEncodedPolyline(PG_FUNCTION_ARGS);
Datum geometry_to_json(PG_FUNCTION_ARGS);
Datum geometry_to_jsonb(PG_FUNCTION_ARGS);

/**
 * Encode feature in GML
 */
PG_FUNCTION_INFO_V1(LWGEOM_asGML);
Datum LWGEOM_asGML(PG_FUNCTION_ARGS)
{
	GSERIALIZED *geom;
	LWGEOM *lwgeom;
	lwvarlena_t *v = NULL;
	int version;
	const char *srs;
	int32_t srid;
	int option = 0;
	int lwopts = LW_GML_IS_DIMS;
	int precision = OUT_DEFAULT_DECIMAL_DIGITS;
	static const char* default_prefix = "gml:"; /* default prefix */
	const char* prefix = default_prefix;
	const char* gml_id = NULL;
	size_t len;
	char *gml_id_buf, *prefix_buf;
	text *prefix_text, *gml_id_text;

	/*
	 * Two potential callers, one starts with GML version,
	 * one starts with geometry, and we check for initial
	 * argument type and then dynamically change what args
	 * we read based on presence/absence
	 */
	Oid first_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
	int argnum = 0;
	if (first_type != INT4OID)
	{
		version = 2;
	}
	else
	{
		/* Get the version */
		version = PG_GETARG_INT32(argnum++);
		if (version != 2 && version != 3)
		{
			elog(ERROR, "Only GML 2 and GML 3 are supported");
			PG_RETURN_NULL();
		}
	}

	/* Get the geometry */
	if (PG_ARGISNULL(argnum))
		PG_RETURN_NULL();
	geom = PG_GETARG_GSERIALIZED_P(argnum++);

	/* Retrieve precision if any (default is max) */
	if (PG_NARGS() > argnum && !PG_ARGISNULL(argnum))
	{
		precision = PG_GETARG_INT32(argnum);
	}
	argnum++;

	/* retrieve option */
	if (PG_NARGS() > argnum && !PG_ARGISNULL(argnum))
		option = PG_GETARG_INT32(argnum);
	argnum++;

	/* retrieve prefix */
	if (PG_NARGS() > argnum && !PG_ARGISNULL(argnum))
	{
		prefix_text = PG_GETARG_TEXT_P(argnum);
		if ( VARSIZE(prefix_text) == VARHDRSZ )
		{
			prefix = "";
		}
		else
		{
			len = VARSIZE_ANY_EXHDR(prefix_text);
			prefix_buf = palloc(len + 2); /* +2 is one for the ':' and one for term null */
			memcpy(prefix_buf, VARDATA(prefix_text), len);
			/* add colon and null terminate */
			prefix_buf[len] = ':';
			prefix_buf[len+1] = '\0';
			prefix = prefix_buf;
		}
	}
	argnum++;

	if (PG_NARGS() > argnum && !PG_ARGISNULL(argnum))
	{
		gml_id_text = PG_GETARG_TEXT_P(argnum);
		if ( VARSIZE(gml_id_text) == VARHDRSZ )
		{
			gml_id = "";
		}
		else
		{
			len = VARSIZE_ANY_EXHDR(gml_id_text);
			gml_id_buf = palloc(len+1);
			memcpy(gml_id_buf, VARDATA(gml_id_text), len);
			gml_id_buf[len] = '\0';
			gml_id = gml_id_buf;
		}
	}
	argnum++;

	srid = gserialized_get_srid(geom);
	if (srid == SRID_UNKNOWN)      srs = NULL;
	else if (option & 1)
		srs = GetSRSCacheBySRID(fcinfo, srid, false);
	else
		srs = GetSRSCacheBySRID(fcinfo, srid, true);

	if (option & 2) lwopts &= ~LW_GML_IS_DIMS;
	if (option & 4) lwopts |= LW_GML_SHORTLINE;
	if (option & 8)
	{
		elog(ERROR,
		     "Options %d passed to ST_AsGML(geometry) sets "
		     "unsupported value 8",
		     option);
		PG_RETURN_NULL();
	}
	if (option & 16) lwopts |= LW_GML_IS_DEGREE;
	if (option & 32) lwopts |= LW_GML_EXTENT;

	lwgeom = lwgeom_from_gserialized(geom);

	if (version == 2)
	{
		if (lwopts & LW_GML_EXTENT)
			v = lwgeom_extent_to_gml2(lwgeom, srs, precision, prefix);
		else
			v = lwgeom_to_gml2(lwgeom, srs, precision, prefix);
	}
	if (version == 3)
	{
		if (lwopts & LW_GML_EXTENT)
			v = lwgeom_extent_to_gml3(lwgeom, srs, precision, lwopts, prefix);
		else
			v = lwgeom_to_gml3(lwgeom, srs, precision, lwopts, prefix, gml_id);
	}

	if (!v)
		PG_RETURN_NULL();
	PG_RETURN_TEXT_P(v);
}


/**
 * Encode Feature in GeoJson
 */
PG_FUNCTION_INFO_V1(LWGEOM_asGeoJson);
Datum LWGEOM_asGeoJson(PG_FUNCTION_ARGS)
{
	GSERIALIZED *geom;
	LWGEOM *lwgeom;
	int precision = OUT_DEFAULT_DECIMAL_DIGITS;
	int output_bbox = LW_FALSE;
	int output_long_crs = LW_FALSE;
	int output_short_crs = LW_FALSE;
	int output_guess_short_srid = LW_FALSE;
	const char *srs = NULL;
	int32_t srid;

	/* Get the geometry */
	if (PG_ARGISNULL(0))
		PG_RETURN_NULL();

	geom = PG_GETARG_GSERIALIZED_P(0);
	srid = gserialized_get_srid(geom);

	/* Retrieve precision if any (default is max) */
	if ( PG_NARGS() > 1 && !PG_ARGISNULL(1) )
	{
		precision = PG_GETARG_INT32(1);
	}

	/* Retrieve output option
	 * 0 = without option
	 * 1 = bbox
	 * 2 = short crs
	 * 4 = long crs
	 * 8 = guess if CRS is needed (default)
	 */
	if (PG_NARGS() > 2 && !PG_ARGISNULL(2))
	{
		int option = PG_GETARG_INT32(2);
		output_guess_short_srid = (option & 8) ? LW_TRUE : LW_FALSE;
		output_short_crs = (option & 2) ? LW_TRUE : LW_FALSE;
		output_long_crs = (option & 4) ? LW_TRUE : LW_FALSE;
		output_bbox = (option & 1) ? LW_TRUE : LW_FALSE;
	}
	else
		output_guess_short_srid = LW_TRUE;

	if (output_guess_short_srid && srid != WGS84_SRID && srid != SRID_UNKNOWN)
		output_short_crs = LW_TRUE;

	if (srid != SRID_UNKNOWN && (output_short_crs || output_long_crs))
	{
		srs = GetSRSCacheBySRID(fcinfo, srid, !output_long_crs);

		if (!srs)
		{
			elog(ERROR, "SRID %i unknown in spatial_ref_sys table", srid);
			PG_RETURN_NULL();
		}
	}

	lwgeom = lwgeom_from_gserialized(geom);
	PG_RETURN_TEXT_P(lwgeom_to_geojson(lwgeom, srs, precision, output_bbox));
}


/**
 * Cast feature to JSON
 */
PG_FUNCTION_INFO_V1(geometry_to_json);
Datum geometry_to_json(PG_FUNCTION_ARGS)
{
	GSERIALIZED *geom = PG_GETARG_GSERIALIZED_P(0);
	LWGEOM *lwgeom = lwgeom_from_gserialized(geom);
	const char *srs = NULL;
	lwvarlena_t *geojson;
	if (lwgeom->srid != SRID_UNKNOWN)
	{
		const int short_crs = LW_TRUE;
		srs = GetSRSCacheBySRID(fcinfo, lwgeom->srid, short_crs);
	}
	geojson = lwgeom_to_geojson(lwgeom, srs, 15, 0);
	lwgeom_free(lwgeom);
	PG_FREE_IF_COPY(geom, 0);
	PG_RETURN_TEXT_P(geojson);
}

PG_FUNCTION_INFO_V1(geometry_to_jsonb);
Datum geometry_to_jsonb(PG_FUNCTION_ARGS)
{
	GSERIALIZED *geom = PG_GETARG_GSERIALIZED_P(0);
	LWGEOM *lwgeom = lwgeom_from_gserialized(geom);
	const char *srs = NULL;
	lwvarlena_t *geojson;
	if (lwgeom->srid != SRID_UNKNOWN)
	{
		const int short_crs = LW_TRUE;
		srs = GetSRSCacheBySRID(fcinfo, lwgeom->srid, short_crs);
	}
	geojson = lwgeom_to_geojson(lwgeom, srs, 15, 0);
	lwgeom_free(lwgeom);
	PG_RETURN_DATUM(DirectFunctionCall1(jsonb_in, PointerGetDatum(pstrdup(geojson->data))));
}


/**
 * SVG features
 */
PG_FUNCTION_INFO_V1(LWGEOM_asSVG);
Datum LWGEOM_asSVG(PG_FUNCTION_ARGS)
{
	GSERIALIZED *geom;
	LWGEOM *lwgeom;
	int relative = 0;
	int precision = OUT_DEFAULT_DECIMAL_DIGITS;

	if ( PG_ARGISNULL(0) ) PG_RETURN_NULL();

	geom = PG_GETARG_GSERIALIZED_P(0);

	/* check for relative path notation */
	if ( PG_NARGS() > 1 && ! PG_ARGISNULL(1) )
		relative = PG_GETARG_INT32(1) ? 1:0;

	if ( PG_NARGS() > 2 && ! PG_ARGISNULL(2) )
	{
		precision = PG_GETARG_INT32(2);
	}

	lwgeom = lwgeom_from_gserialized(geom);
	PG_RETURN_TEXT_P(lwgeom_to_svg(lwgeom, precision, relative));
}

/**
 * Encode feature as X3D
 */
PG_FUNCTION_INFO_V1(LWGEOM_asX3D);
Datum LWGEOM_asX3D(PG_FUNCTION_ARGS)
{
	GSERIALIZED *geom;
	LWGEOM *lwgeom;
	int version;
	int option = 0;
	int precision = OUT_DEFAULT_DECIMAL_DIGITS;
	static const char* default_defid = "x3d:"; /* default defid */
	char *defidbuf;
	const char* defid = default_defid;
	text *defid_text;

	/* Get the version */
	version = PG_GETARG_INT32(0);
	if (  version != 3 )
	{
		elog(ERROR, "Only X3D version 3 are supported");
		PG_RETURN_NULL();
	}

	/* Get the geometry */
	if ( PG_ARGISNULL(1) ) PG_RETURN_NULL();
	geom = PG_GETARG_GSERIALIZED_P(1);

	/* Retrieve precision if any (default is max) */
	if (PG_NARGS() >2 && !PG_ARGISNULL(2))
	{
		precision = PG_GETARG_INT32(2);
	}

	/* retrieve option */
	if (PG_NARGS() >3 && !PG_ARGISNULL(3))
		option = PG_GETARG_INT32(3);


	/* retrieve defid */
	if (PG_NARGS() >4 && !PG_ARGISNULL(4))
	{
		defid_text = PG_GETARG_TEXT_P(4);
		if ( VARSIZE_ANY_EXHDR(defid_text) == 0 )
		{
			defid = "";
		}
		else
		{
			/* +2 is one for the ':' and one for term null */
			defidbuf = palloc(VARSIZE_ANY_EXHDR(defid_text)+2);
			memcpy(defidbuf, VARDATA(defid_text),
			       VARSIZE_ANY_EXHDR(defid_text));
			/* add colon and null terminate */
			defidbuf[VARSIZE_ANY_EXHDR(defid_text)] = ':';
			defidbuf[VARSIZE_ANY_EXHDR(defid_text)+1] = '\0';
			defid = defidbuf;
		}
	}

	lwgeom = lwgeom_from_gserialized(geom);

	if (option & LW_X3D_USE_GEOCOORDS) {
		if (lwgeom->srid != 4326)
		{
			PG_FREE_IF_COPY(geom, 0);
			/** TODO: we need to support UTM and other coordinate systems supported by X3D eventually
			http://www.web3d.org/documents/specifications/19775-1/V3.2/Part01/components/geodata.html#t-earthgeoids **/
			elog(ERROR, "Only SRID 4326 is supported for geocoordinates.");
			PG_RETURN_NULL();
		}
	}

	PG_RETURN_TEXT_P(lwgeom_to_x3d3(lwgeom, precision, option, defid));
}

/**
 * Encode feature as Encoded Polyline
 */
PG_FUNCTION_INFO_V1(LWGEOM_asEncodedPolyline);
Datum LWGEOM_asEncodedPolyline(PG_FUNCTION_ARGS)
{
	GSERIALIZED *geom;
	LWGEOM *lwgeom;
	int precision = 5;

	if ( PG_ARGISNULL(0) ) PG_RETURN_NULL();

	geom = PG_GETARG_GSERIALIZED_P(0);
	if (gserialized_get_srid(geom) != 4326) {
		PG_FREE_IF_COPY(geom, 0);
		elog(ERROR, "Only SRID 4326 is supported.");
		PG_RETURN_NULL();
	}
	lwgeom = lwgeom_from_gserialized(geom);

	if (PG_NARGS() > 1 && !PG_ARGISNULL(1))
	{
		precision = PG_GETARG_INT32(1);
		if ( precision < 0 ) precision = 5;
	}

	PG_RETURN_TEXT_P(lwgeom_to_encoded_polyline(lwgeom, precision));
}
