/*-------------------------------------------------------------------------
 *
 * rtree_gist.c
 *    pg_amproc entries for GiSTs over 2-D boxes.
 * This gives R-tree behavior, with Guttman's poly-time split algorithm.
 *
 *
 *
 * IDENTIFICATION
 *	$Header: /cvsroot/pgsql/contrib/rtree_gist/Attic/rtree_gist.c,v 1.2 2001/08/22 18:24:26 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/gist.h"
#include "access/itup.h"
#include "access/rtree.h"
#include "utils/palloc.h"
#include "utils/geo_decls.h"
#include "utils/elog.h"

typedef Datum (*RDF)(PG_FUNCTION_ARGS);
typedef Datum (*BINARY_UNION)(Datum, Datum, int*);
typedef float (*SIZE_BOX)(Datum);

/*
** box ops
*/
PG_FUNCTION_INFO_V1(gbox_compress);
PG_FUNCTION_INFO_V1(gbox_union);
PG_FUNCTION_INFO_V1(gbox_picksplit);
PG_FUNCTION_INFO_V1(gbox_consistent);
PG_FUNCTION_INFO_V1(gbox_penalty);
PG_FUNCTION_INFO_V1(gbox_same);

Datum gbox_compress(PG_FUNCTION_ARGS);
Datum gbox_union(PG_FUNCTION_ARGS);
Datum gbox_picksplit(PG_FUNCTION_ARGS);
Datum gbox_consistent(PG_FUNCTION_ARGS);
Datum gbox_penalty(PG_FUNCTION_ARGS);
Datum gbox_same(PG_FUNCTION_ARGS);

static bool gbox_leaf_consistent(BOX *key, BOX *query, StrategyNumber strategy);
static float size_box( Datum box );

/*
** Polygon ops 
*/
PG_FUNCTION_INFO_V1(gpoly_compress);
PG_FUNCTION_INFO_V1(gpoly_consistent);

Datum gpoly_compress(PG_FUNCTION_ARGS);
Datum gpoly_consistent(PG_FUNCTION_ARGS);

/*
** Common rtree-function (for all ops)
*/
static bool rtree_internal_consistent(BOX *key, BOX *query, StrategyNumber strategy);

PG_FUNCTION_INFO_V1(rtree_decompress);

Datum rtree_decompress(PG_FUNCTION_ARGS);

/**************************************************
 * Box ops
 **************************************************/

/*
** The GiST Consistent method for boxes
** Should return false if for all data items x below entry,
** the predicate x op query == FALSE, where op is the oper
** corresponding to strategy in the pg_amop table.
*/
Datum
gbox_consistent(PG_FUNCTION_ARGS)
{
    GISTENTRY *entry = (GISTENTRY*) PG_GETARG_POINTER(0);
    BOX *query       = (BOX*)	    PG_GETARG_POINTER(1);
    StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);

    /*
    ** if entry is not leaf, use gbox_internal_consistent,
    ** else use gbox_leaf_consistent
    */
    if ( ! (DatumGetPointer(entry->key) != NULL && query) )
		PG_RETURN_BOOL(FALSE);

    if (GIST_LEAF(entry))
      PG_RETURN_BOOL(gbox_leaf_consistent((BOX *) DatumGetPointer(entry->key), query, strategy));
    else
      PG_RETURN_BOOL(rtree_internal_consistent((BOX *) DatumGetPointer(entry->key), query, strategy));
}


/*
** The GiST Union method for boxes
** returns the minimal bounding box that encloses all the entries in entryvec
*/
Datum
gbox_union(PG_FUNCTION_ARGS)
{
    bytea *entryvec = (bytea*) PG_GETARG_POINTER(0);
    int *sizep = (int*) PG_GETARG_POINTER(1);
    int numranges, i;
    BOX	*cur, *pageunion;

    numranges = (VARSIZE(entryvec) - VARHDRSZ)/sizeof(GISTENTRY); 
    pageunion = (BOX *)palloc( sizeof(BOX) );
    cur = DatumGetBoxP( ((GISTENTRY *) VARDATA(entryvec))[0].key );
    memcpy( (void*)pageunion, (void*)cur, sizeof( BOX ) ); 
   
    for (i = 1; i < numranges; i++) {
	cur = DatumGetBoxP( ((GISTENTRY *) VARDATA(entryvec))[i].key );
	if ( pageunion->high.x < cur->high.x ) 
		pageunion->high.x  = cur->high.x;
	if ( pageunion->low.x  > cur->low.x  ) 
		pageunion->low.x   = cur->low.x;
	if ( pageunion->high.y < cur->high.y ) 
		pageunion->high.y  = cur->high.y;
	if ( pageunion->low.y  > cur->low.y  ) 
		pageunion->low.y   = cur->low.y;
    }
    *sizep = sizeof(BOX);

    PG_RETURN_POINTER(pageunion);
}

/*
** GiST Compress methods for boxes
** do not do anything.
*/
Datum
gbox_compress(PG_FUNCTION_ARGS)
{
	PG_RETURN_POINTER(PG_GETARG_POINTER(0));
}

/*
** The GiST Penalty method for boxes
** As in the R-tree paper, we use change in area as our penalty metric
*/
Datum
gbox_penalty(PG_FUNCTION_ARGS)
{
    GISTENTRY *origentry = (GISTENTRY*) PG_GETARG_POINTER(0);
    GISTENTRY *newentry  = (GISTENTRY*) PG_GETARG_POINTER(1);
    float *result        = (float*) PG_GETARG_POINTER(2);
    Datum	ud;
    float tmp1;
   
    ud = DirectFunctionCall2(rt_box_union, origentry->key, newentry->key);
    tmp1 = size_box( ud ); 
    if (DatumGetPointer(ud) != NULL) pfree(DatumGetPointer(ud));

    *result = tmp1 - size_box( origentry->key );
    PG_RETURN_POINTER(result);
}

/*
** The GiST PickSplit method
** New linear algorithm, see 'New Linear Node Splitting Algorithm for R-tree',
** C.H.Ang and T.C.Tan
*/
Datum
gbox_picksplit(PG_FUNCTION_ARGS)
{
    bytea *entryvec = (bytea*)PG_GETARG_POINTER(0);
    GIST_SPLITVEC *v = (GIST_SPLITVEC*)PG_GETARG_POINTER(1);
    OffsetNumber i;
    OffsetNumber *listL, *listR, *listB, *listT;
    BOX         *unionL,*unionR,*unionB,*unionT;
    int		   posL,   posR,   posB,   posT;
    BOX 	pageunion;
    BOX 	*cur;
    char	direction=' ';
    bool   	allisequal=true;
    OffsetNumber maxoff;
    int nbytes;

    posL = posR = posB = posT = 0; 
    maxoff = ((VARSIZE(entryvec) - VARHDRSZ)/sizeof(GISTENTRY)) - 1;
   
    cur = DatumGetBoxP( ((GISTENTRY *) VARDATA(entryvec))[FirstOffsetNumber].key );
    memcpy( (void*)&pageunion, (void*)cur, sizeof( BOX ) ); 
   
    /* find MBR */ 
    for (i = OffsetNumberNext(FirstOffsetNumber); i <= maxoff; i = OffsetNumberNext(i)) {
	cur = DatumGetBoxP( ((GISTENTRY *) VARDATA(entryvec))[i].key );
	if ( pageunion.high.x < cur->high.x ) 
		{ allisequal=false; pageunion.high.x  = cur->high.x; }
	if ( pageunion.low.x  > cur->low.x  ) 
		{ allisequal=false; pageunion.low.x   = cur->low.x;  }
	if ( pageunion.high.y < cur->high.y ) 
		{ allisequal=false; pageunion.high.y  = cur->high.y; }
	if ( pageunion.low.y  > cur->low.y  ) 
		{ allisequal=false; pageunion.low.y   = cur->low.y;  }
    }

    nbytes =  (maxoff + 2) * sizeof(OffsetNumber);
    listL = (OffsetNumber*)palloc( nbytes );
    listR = (OffsetNumber*)palloc( nbytes );
    unionL = (BOX*)palloc( sizeof(BOX) );
    unionR = (BOX*)palloc( sizeof(BOX) );
    if ( allisequal ) {
	cur = DatumGetBoxP( ((GISTENTRY *) VARDATA(entryvec))[OffsetNumberNext(FirstOffsetNumber)].key );
	if ( memcmp( (void*)cur, (void*)&pageunion, sizeof( BOX ) ) == 0 ) {  
		v->spl_left = listL;
		v->spl_right = listR;
		v->spl_nleft = v->spl_nright = 0;
		memcpy( (void*)unionL, (void*)&pageunion, sizeof( BOX ) );
		memcpy( (void*)unionR, (void*)&pageunion, sizeof( BOX ) );
	
    		for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) {
        		if (i <= (maxoff - FirstOffsetNumber + 1)/2) {
       	 	    		v->spl_left[ v->spl_nleft ] = i;
        	    		v->spl_nleft++;
        		} else {
        	    		v->spl_right[ v->spl_nright ] = i;
        	   		v->spl_nright++;
        		}
		}
    		v->spl_ldatum = BoxPGetDatum( unionL );
    		v->spl_rdatum = BoxPGetDatum( unionR );

		PG_RETURN_POINTER( v );
	}
    }

    listB = (OffsetNumber*)palloc( nbytes );
    listT = (OffsetNumber*)palloc( nbytes );
    unionB = (BOX*)palloc( sizeof(BOX) );
    unionT = (BOX*)palloc( sizeof(BOX) );

#define ADDLIST( list, unionD, pos ) do { \
	if ( pos ) { \
		if ( unionD->high.x < cur->high.x ) unionD->high.x  = cur->high.x; \
		if ( unionD->low.x  > cur->low.x  ) unionD->low.x   = cur->low.x; \
		if ( unionD->high.y < cur->high.y ) unionD->high.y  = cur->high.y; \
		if ( unionD->low.y  > cur->low.y  ) unionD->low.y   = cur->low.y; \
	} else { \
    		memcpy( (void*)unionD, (void*) cur, sizeof( BOX ) );  \
	} \
	list[pos] = i; \
	(pos)++; \
} while(0)

    for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) {
	cur = DatumGetBoxP( ((GISTENTRY *) VARDATA(entryvec))[i].key );
	if ( cur->low.x - pageunion.low.x < pageunion.high.x - cur->high.x )
		ADDLIST( listL, unionL, posL );
	else
		ADDLIST( listR, unionR, posR );
	if ( cur->low.y - pageunion.low.y < pageunion.high.y - cur->high.y )
		ADDLIST( listB, unionB, posB );
	else
		ADDLIST( listT, unionT, posT );
    }

    /* which split more optimal? */

    if ( Max( posL, posR ) < Max( posB, posT ) ) {
	direction = 'x';
    } else if ( Max( posL, posR ) > Max( posB, posT ) ) {
	direction = 'y';
    } else {
	Datum interLR = DirectFunctionCall2(rt_box_inter,
				BoxPGetDatum(unionL),
				BoxPGetDatum(unionR));
	Datum interBT = DirectFunctionCall2(rt_box_inter,
				BoxPGetDatum(unionB),
				BoxPGetDatum(unionT));
	float sizeLR, sizeBT;

    	sizeLR = size_box( interLR );
    	sizeBT = size_box( interBT );

	if ( sizeLR < sizeBT ) {
		direction = 'x';
	//} else if ( sizeLR > sizeBT ) {
	} else {
		direction = 'y';
	} 
    }

   if ( direction == 'x' ) {
	pfree( unionB ); pfree( listB );
	pfree( unionT ); pfree( listT );

	v->spl_left = listL;
	v->spl_right = listR;
	v->spl_nleft = posL;
	v->spl_nright = posR;
    	v->spl_ldatum = BoxPGetDatum( unionL );
    	v->spl_rdatum = BoxPGetDatum( unionR );
   } else {
	pfree( unionR ); pfree( listR );
	pfree( unionL ); pfree( listL );

	v->spl_left = listB;
	v->spl_right = listT;
	v->spl_nleft = posB;
	v->spl_nright = posT;
    	v->spl_ldatum = BoxPGetDatum( unionB );
    	v->spl_rdatum = BoxPGetDatum( unionT );
   }

   PG_RETURN_POINTER (v);
}

/*
** Equality method
*/
Datum
gbox_same(PG_FUNCTION_ARGS)
{
  BOX *b1 = (BOX*) PG_GETARG_POINTER(0);
  BOX *b2 = (BOX*) PG_GETARG_POINTER(1);
  bool *result = (bool*) PG_GETARG_POINTER(2);
  if ( b1 && b2 )
   	*result = DatumGetBool( DirectFunctionCall2( box_same, PointerGetDatum(b1), PointerGetDatum(b2)) );
  else
	*result = ( b1==NULL && b2==NULL ) ? TRUE : FALSE; 
  PG_RETURN_POINTER(result);
}

/* 
** SUPPORT ROUTINES for boxes
*/
static bool 
gbox_leaf_consistent(BOX *key,
		     BOX *query,
		     StrategyNumber strategy)
{
    bool retval;

    switch(strategy) {
    case RTLeftStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_left, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    case RTOverLeftStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_overleft, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    case RTOverlapStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_overlap, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    case RTOverRightStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_overright, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    case RTRightStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_right, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    case RTSameStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_same, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    case RTContainsStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_contain, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    case RTContainedByStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_contained, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    default:
      retval = FALSE;
    }
    return(retval);
}

static float 
size_box( Datum box ) {
    if ( DatumGetPointer(box) != NULL ) {
		float size;

    	DirectFunctionCall2( rt_box_size,
							 box, PointerGetDatum( &size ) );
		return size;
    } else
		return 0.0;
}

/**************************************************
 * Polygon ops
 **************************************************/

Datum
gpoly_compress(PG_FUNCTION_ARGS)
{
	GISTENTRY *entry=(GISTENTRY*)PG_GETARG_POINTER(0);
	GISTENTRY *retval;

	if ( entry->leafkey) {
		retval = palloc(sizeof(GISTENTRY));
		if ( DatumGetPointer(entry->key) != NULL ) {
			POLYGON *in;
			BOX *r;
			in = (POLYGON *) PG_DETOAST_DATUM(entry->key);
			r = (BOX *) palloc( sizeof(BOX) );
			memcpy( (void*)r, (void*)&(in->boundbox), sizeof(BOX) );
			if ( in != (POLYGON *) DatumGetPointer(entry->key) )
				pfree( in );

			gistentryinit(*retval, PointerGetDatum(r),
						  entry->rel, entry->page,
						  entry->offset, sizeof(BOX), FALSE);

		} else {
			gistentryinit(*retval, (Datum) 0,
						  entry->rel, entry->page,
						  entry->offset, 0,FALSE);
		} 
	} else {
		retval = entry;
	}
	PG_RETURN_POINTER( retval );
}

Datum
gpoly_consistent(PG_FUNCTION_ARGS)
{
    GISTENTRY *entry = (GISTENTRY*) PG_GETARG_POINTER(0);
    POLYGON *query       = (POLYGON*)PG_DETOAST_DATUM( PG_GETARG_POINTER(1) );
    StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
    bool result;

    /*
    ** if entry is not leaf, use gbox_internal_consistent,
    ** else use gbox_leaf_consistent
    */
    if ( ! (DatumGetPointer(entry->key) != NULL && query) )
		PG_RETURN_BOOL(FALSE);

    result = rtree_internal_consistent((BOX *) DatumGetPointer(entry->key), 
		&(query->boundbox), strategy);

    PG_FREE_IF_COPY(query,1);
    PG_RETURN_BOOL( result ); 
}

/*****************************************
 * Common rtree-function (for all ops)
 *****************************************/

static bool 
rtree_internal_consistent(BOX *key,
			BOX *query,
			StrategyNumber strategy)
{
    bool retval;

    switch(strategy) {
    case RTLeftStrategyNumber:
    case RTOverLeftStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_overleft, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    case RTOverlapStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_overlap, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    case RTOverRightStrategyNumber:
    case RTRightStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_right, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    case RTSameStrategyNumber:
    case RTContainsStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_contain, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    case RTContainedByStrategyNumber:
      retval = DatumGetBool( DirectFunctionCall2( box_overlap, PointerGetDatum(key), PointerGetDatum(query) ) );
      break;
    default:
      retval = FALSE;
    }
    return(retval);
}

/*
** GiST DeCompress methods
** do not do anything.
*/
Datum
rtree_decompress(PG_FUNCTION_ARGS)
{
	PG_RETURN_POINTER(PG_GETARG_POINTER(0));
}
