/**********************************************************************
 * $Id: geography_btree.c 9324 2012-02-27 22:08:12Z pramsey $
 *
 * PostGIS - Spatial Types for PostgreSQL
 * Copyright 2009 Paul Ramsey <pramsey@cleverelephant.ca>
 *
 * This is free software; you can redistribute and/or modify it under
 * the terms of the GNU General Public Licence. See the COPYING file.
 *
 **********************************************************************/

#include "postgres.h"
#include "access/hash.h"

#include "../postgis_config.h"

#include "liblwgeom.h"         /* For standard geometry types. */
#include "liblwgeom_internal.h"         /* For FP comparators. */
#include "lwgeom_pg.h"       /* For debugging macros. */
#include "gserialized_gist.h"
#include "geography.h"	     /* For utility functions. */

Datum geography_lt(PG_FUNCTION_ARGS);
Datum geography_le(PG_FUNCTION_ARGS);
Datum geography_eq(PG_FUNCTION_ARGS);
Datum geography_ge(PG_FUNCTION_ARGS);
Datum geography_gt(PG_FUNCTION_ARGS);
Datum geography_cmp(PG_FUNCTION_ARGS);


/*
** Utility function to return the center point of a
** geocentric bounding box. We don't divide by two
** because we're only using the values for comparison.
*/
static void geography_gidx_center(GIDX *gidx, POINT3D *p)
{
	p->x = GIDX_GET_MIN(gidx, 0) + GIDX_GET_MAX(gidx, 0);
	p->y = GIDX_GET_MIN(gidx, 1) + GIDX_GET_MAX(gidx, 1);
	p->z = GIDX_GET_MIN(gidx, 2) + GIDX_GET_MAX(gidx, 2);
}

/*
** BTree support function. Based on two geographies return true if
** they are "less than" and false otherwise.
*/
PG_FUNCTION_INFO_V1(geography_lt);
Datum geography_lt(PG_FUNCTION_ARGS)
{
	/* Put aside some stack memory and use it for GIDX pointers. */
	char gboxmem1[GIDX_MAX_SIZE];
	char gboxmem2[GIDX_MAX_SIZE];
	GIDX *gbox1 = (GIDX*)gboxmem1;
	GIDX *gbox2 = (GIDX*)gboxmem2;
	POINT3D p1, p2;

	/* Must be able to build box for each argument (ie, not empty geometry) */
	if ( ! gserialized_datum_get_gidx_p(PG_GETARG_DATUM(0), gbox1) ||
	        ! gserialized_datum_get_gidx_p(PG_GETARG_DATUM(1), gbox2) )
	{
		PG_RETURN_BOOL(FALSE);
	}

	geography_gidx_center(gbox1, &p1);
	geography_gidx_center(gbox2, &p2);

	if ( p1.x < p2.x || p1.y < p2.y || p1.z < p2.z )
		PG_RETURN_BOOL(TRUE);

	PG_RETURN_BOOL(FALSE);
}

/*
** BTree support function. Based on two geographies return true if
** they are "less than or equal" and false otherwise.
*/
PG_FUNCTION_INFO_V1(geography_le);
Datum geography_le(PG_FUNCTION_ARGS)
{
	/* Put aside some stack memory and use it for GIDX pointers. */
	char gboxmem1[GIDX_MAX_SIZE];
	char gboxmem2[GIDX_MAX_SIZE];
	GIDX *gbox1 = (GIDX*)gboxmem1;
	GIDX *gbox2 = (GIDX*)gboxmem2;
	POINT3D p1, p2;

	/* Must be able to build box for each argument (ie, not empty geometry) */
	if ( ! gserialized_datum_get_gidx_p(PG_GETARG_DATUM(0), gbox1) ||
	        ! gserialized_datum_get_gidx_p(PG_GETARG_DATUM(1), gbox2) )
	{
		PG_RETURN_BOOL(FALSE);
	}

	geography_gidx_center(gbox1, &p1);
	geography_gidx_center(gbox2, &p2);

	if ( p1.x <= p2.x || p1.y <= p2.y || p1.z <= p2.z )
		PG_RETURN_BOOL(TRUE);

	PG_RETURN_BOOL(FALSE);
}

/*
** BTree support function. Based on two geographies return true if
** they are "greater than" and false otherwise.
*/
PG_FUNCTION_INFO_V1(geography_gt);
Datum geography_gt(PG_FUNCTION_ARGS)
{
	/* Put aside some stack memory and use it for GIDX pointers. */
	char gboxmem1[GIDX_MAX_SIZE];
	char gboxmem2[GIDX_MAX_SIZE];
	GIDX *gbox1 = (GIDX*)gboxmem1;
	GIDX *gbox2 = (GIDX*)gboxmem2;
	POINT3D p1, p2;

	/* Must be able to build box for each argument (ie, not empty geometry) */
	if ( ! gserialized_datum_get_gidx_p(PG_GETARG_DATUM(0), gbox1) ||
	        ! gserialized_datum_get_gidx_p(PG_GETARG_DATUM(1), gbox2) )
	{
		PG_RETURN_BOOL(FALSE);
	}

	geography_gidx_center(gbox1, &p1);
	geography_gidx_center(gbox2, &p2);

	if ( p1.x > p2.x && p1.y > p2.y && p1.z > p2.z )
		PG_RETURN_BOOL(TRUE);

	PG_RETURN_BOOL(FALSE);
}

/*
** BTree support function. Based on two geographies return true if
** they are "greater than or equal" and false otherwise.
*/
PG_FUNCTION_INFO_V1(geography_ge);
Datum geography_ge(PG_FUNCTION_ARGS)
{
	/* Put aside some stack memory and use it for GIDX pointers. */
	char gboxmem1[GIDX_MAX_SIZE];
	char gboxmem2[GIDX_MAX_SIZE];
	GIDX *gbox1 = (GIDX*)gboxmem1;
	GIDX *gbox2 = (GIDX*)gboxmem2;
	POINT3D p1, p2;

	/* Must be able to build box for each argument (ie, not empty geometry) */
	if ( ! gserialized_datum_get_gidx_p(PG_GETARG_DATUM(0), gbox1) ||
	        ! gserialized_datum_get_gidx_p(PG_GETARG_DATUM(1), gbox2) )
	{
		PG_RETURN_BOOL(FALSE);
	}

	geography_gidx_center(gbox1, &p1);
	geography_gidx_center(gbox2, &p2);

	if ( p1.x >= p2.x && p1.y >= p2.y && p1.z >= p2.z )
		PG_RETURN_BOOL(TRUE);

	PG_RETURN_BOOL(FALSE);
}


#if 0
/*
** Calculate a hash code based on the geometry data alone
*/
static uint32 geography_hash(GSERIALIZED *g)
{
	return DatumGetUInt32(hash_any((void*)g, VARSIZE(g)));
}
/*
** BTree support function. Based on two geographies return true if
** they are "equal" and false otherwise. This version uses a hash
** function to try and shoot for a more exact equality test.
*/
PG_FUNCTION_INFO_V1(geography_eq);
Datum geography_eq(PG_FUNCTION_ARGS)
{
	/* Perfect equals test based on hash */
	GSERIALIZED *g1 = (GSERIALIZED*)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
	GSERIALIZED *g2 = (GSERIALIZED*)PG_DETOAST_DATUM(PG_GETARG_DATUM(1));

	uint32 h1 = geography_hash(g1);
	uint32 h2 = geography_hash(g2);

	PG_FREE_IF_COPY(g1,0);
	PG_FREE_IF_COPY(g2,0);

	if ( h1 == h2 )
		PG_RETURN_BOOL(TRUE);

	PG_RETURN_BOOL(FALSE);
}
#endif

/*
** BTree support function. Based on two geographies return true if
** they are "equal" and false otherwise.
*/
PG_FUNCTION_INFO_V1(geography_eq);
Datum geography_eq(PG_FUNCTION_ARGS)
{
	/* Put aside some stack memory and use it for GIDX pointers. */
	char gboxmem1[GIDX_MAX_SIZE];
	char gboxmem2[GIDX_MAX_SIZE];
	GIDX *gbox1 = (GIDX*)gboxmem1;
	GIDX *gbox2 = (GIDX*)gboxmem2;
	POINT3D p1, p2;

	/* Must be able to build box for each argument (ie, not empty geometry) */
	if ( ! gserialized_datum_get_gidx_p(PG_GETARG_DATUM(0), gbox1) ||
	        ! gserialized_datum_get_gidx_p(PG_GETARG_DATUM(1), gbox2) )
	{
		PG_RETURN_BOOL(FALSE);
	}

	geography_gidx_center(gbox1, &p1);
	geography_gidx_center(gbox2, &p2);

	if ( FP_EQUALS(p1.x, p2.x) && FP_EQUALS(p1.y, p2.y) && FP_EQUALS(p1.z, p2.z) )
		PG_RETURN_BOOL(TRUE);

	PG_RETURN_BOOL(FALSE);

}

/*
** BTree support function. Based on two geographies return true if
** they are "equal" and false otherwise.
*/
PG_FUNCTION_INFO_V1(geography_cmp);
Datum geography_cmp(PG_FUNCTION_ARGS)
{
	/* Put aside some stack memory and use it for GIDX pointers. */
	char gboxmem1[GIDX_MAX_SIZE];
	char gboxmem2[GIDX_MAX_SIZE];
	GIDX *gbox1 = (GIDX*)gboxmem1;
	GIDX *gbox2 = (GIDX*)gboxmem2;
	POINT3D p1, p2;

	/* Must be able to build box for each argument (ie, not empty geometry) */
	if ( ! gserialized_datum_get_gidx_p(PG_GETARG_DATUM(0), gbox1) ||
	        ! gserialized_datum_get_gidx_p(PG_GETARG_DATUM(1), gbox2) )
	{
		PG_RETURN_BOOL(FALSE);
	}

	geography_gidx_center(gbox1, &p1);
	geography_gidx_center(gbox2, &p2);

	if ( p1.x > p2.x && p1.y > p2.y && p1.z > p2.z )
		PG_RETURN_INT32(1);

	if ( FP_EQUALS(p1.x, p2.x) && FP_EQUALS(p1.y, p2.y) && FP_EQUALS(p1.z, p2.z) )
		PG_RETURN_INT32(0);

	PG_RETURN_INT32(-1);

}

