/***********************************************************************
* pc_pgsql.c
*
*  Utility functions to bind pc_api.h functions to PgSQL, including
*  memory management and serialization/deserializations.
*
*  PgSQL Pointcloud is free and open source software provided
*  by the Government of Canada
*  Copyright (c) 2013 Natural Resources Canada
*
***********************************************************************/

#include <assert.h>
#include "pc_pgsql.h"
#include "executor/spi.h"
#include "access/hash.h"
#include "utils/hsearch.h"

PG_MODULE_MAGIC;

/**********************************************************************************
* POSTGRESQL MEMORY MANAGEMENT HOOKS
*/

static void *
pgsql_alloc(size_t size)
{
	void * result;
	result = palloc(size);

	if ( ! result )
	{
		ereport(ERROR,
			(errcode(ERRCODE_OUT_OF_MEMORY),
			errmsg("Out of virtual memory")));
	}

	return result;
}

static void *
pgsql_realloc(void *mem, size_t size)
{
	void * result;
	result = repalloc(mem, size);
	if ( ! result )
	{
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("Out of virtual memory")));
	}
	return result;
}

static void
pgsql_free(void *ptr)
{
	pfree(ptr);
}

static void
pgsql_msg_handler(int sig, const char *fmt, va_list ap)
	__attribute__ (( format (printf, 2, 0) ));

static void
pgsql_msg_handler(int sig, const char *fmt, va_list ap)
{
#define MSG_MAXLEN 1024
	char msg[MSG_MAXLEN] = {0};
	vsnprintf(msg, MSG_MAXLEN, fmt, ap);
	msg[MSG_MAXLEN-1] = '\0';
	ereport(sig, (errmsg_internal("%s", msg)));
}

static void
pgsql_error(const char *fmt, va_list ap) __attribute__ (( format (printf, 1, 0) ));

static void
pgsql_error(const char *fmt, va_list ap)
{
	pgsql_msg_handler(ERROR, fmt, ap);
}

static void
pgsql_warn(const char *fmt, va_list ap) __attribute__ (( format (printf, 1, 0) ));

static void
pgsql_warn(const char *fmt, va_list ap)
{
	pgsql_msg_handler(WARNING, fmt, ap);
}

static void
pgsql_info(const char *fmt, va_list ap) __attribute__ (( format (printf, 1, 0) ));

static void
pgsql_info(const char *fmt, va_list ap)
{
	pgsql_msg_handler(NOTICE, fmt, ap);
}

/**********************************************************************************
* POINTCLOUD START-UP/SHUT-DOWN CALLBACKS
*/

/**
* On module load we want to hook the message writing and memory allocation
* functions of libpc to the PostgreSQL ones.
* TODO: also hook the libxml2 hooks into PostgreSQL.
*/
void _PG_init(void);
void
_PG_init(void)
{
	elog(LOG, "Pointcloud (%s) module loaded", POINTCLOUD_VERSION);
	pc_set_handlers(
		pgsql_alloc, pgsql_realloc,
		pgsql_free, pgsql_error,
		pgsql_info, pgsql_warn
	);

}

/* Module unload callback */
void _PG_fini(void);
void
_PG_fini(void)
{
	elog(LOG, "Pointcloud (%s) module unloaded", POINTCLOUD_VERSION);
}

/* Mask pcid from bottom of typmod */
uint32 pcid_from_typmod(const int32 typmod)
{
	if ( typmod == -1 )
		return 0;
	else
		return (typmod & 0x0000FFFF);
}

/**********************************************************************************
* PCPOINT WKB Handling
*/

PCPOINT *
#if PGSQL_VERSION < 120
pc_point_from_hexwkb(const char *hexwkb, size_t hexlen, FunctionCallInfoData *fcinfo)
#else
pc_point_from_hexwkb(const char *hexwkb, size_t hexlen, FunctionCallInfo fcinfo)
#endif
{
	PCPOINT *pt;
	PCSCHEMA *schema;
	uint32 pcid;
	uint8 *wkb = pc_bytes_from_hexbytes(hexwkb, hexlen);
	size_t wkblen = hexlen/2;
	pcid = pc_wkb_get_pcid(wkb);
	schema = pc_schema_from_pcid(pcid, fcinfo);
	pt = pc_point_from_wkb(schema, wkb, wkblen);
	pfree(wkb);
	return pt;
}

char *
pc_point_to_hexwkb(const PCPOINT *pt)
{
	uint8 *wkb;
	size_t wkb_size;
	char *hexwkb;

	wkb = pc_point_to_wkb(pt, &wkb_size);
	hexwkb = pc_hexbytes_from_bytes(wkb, wkb_size);
	pfree(wkb);
	return hexwkb;
}


/**********************************************************************************
* PCPATCH WKB Handling
*/

PCPATCH *
#if PGSQL_VERSION < 120
pc_patch_from_hexwkb(const char *hexwkb, size_t hexlen, FunctionCallInfoData *fcinfo)
#else
pc_patch_from_hexwkb(const char *hexwkb, size_t hexlen, FunctionCallInfo fcinfo)
#endif
{
	PCPATCH *patch;
	PCSCHEMA *schema;
	uint32 pcid;
	uint8 *wkb = pc_bytes_from_hexbytes(hexwkb, hexlen);
	size_t wkblen = hexlen/2;
	pcid = pc_wkb_get_pcid(wkb);
	if ( ! pcid )
		elog(ERROR, "%s: pcid is zero", __func__);

	schema = pc_schema_from_pcid(pcid, fcinfo);
	if ( ! schema )
		elog(ERROR, "%s: unable to look up schema entry", __func__);

	patch = pc_patch_from_wkb(schema, wkb, wkblen);
	pfree(wkb);
	return patch;
}

char *
pc_patch_to_hexwkb(const PCPATCH *patch)
{
	uint8 *wkb;
	size_t wkb_size;
	char *hexwkb;

	wkb = pc_patch_to_wkb(patch, &wkb_size);
	hexwkb = pc_hexbytes_from_bytes(wkb, wkb_size);
	pfree(wkb);
	return hexwkb;
}


/**********************************************************************************
* PCID <=> PCSCHEMA translation via POINTCLOUD_FORMATS
*/

uint32 pcid_from_datum(Datum d)
{
	SERIALIZED_POINT *serpart;
	if ( ! d )
		return 0;
	/* Serializations are int32_t <size> uint32_t <pcid> == 8 bytes */
	/* Cast to SERIALIZED_POINT for convenience, SERIALIZED_PATCH shares same header */
	serpart = (SERIALIZED_POINT*)PG_DETOAST_DATUM_SLICE(d, 0, 8);
	return serpart->pcid;
}

PCSCHEMA *
pc_schema_from_pcid_uncached(uint32 pcid)
{
	char sql[256];
	char *xml, *xml_spi, *srid_spi;
	int err, srid;
	size_t size;
	PCSCHEMA *schema;

	if (SPI_OK_CONNECT != SPI_connect ())
	{
		SPI_finish();
		elog(ERROR, "%s: could not connect to SPI manager", __func__);
		return NULL;
	}

	sprintf(sql, "select %s, %s from %s where pcid = %d",
		POINTCLOUD_FORMATS_XML, POINTCLOUD_FORMATS_SRID, POINTCLOUD_FORMATS, pcid);
	err = SPI_exec(sql, 1);

	if ( err < 0 )
	{
		SPI_finish();
		elog(ERROR, "%s: error (%d) executing query: %s", __func__, err, sql);
		return NULL;
	}

	/* No entry in POINTCLOUD_FORMATS */
	if (SPI_processed <= 0)
	{
		SPI_finish();
		elog(ERROR, "no entry in \"%s\" for pcid = %d", POINTCLOUD_FORMATS, pcid);
		return NULL;
	}

	/* Result  */
	xml_spi = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1);
	srid_spi = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2);

	/* NULL result */
	if ( ! ( xml_spi && srid_spi ) )
	{
		SPI_finish();
		elog(ERROR, "unable to read row from \"%s\" for pcid = %d", POINTCLOUD_FORMATS, pcid);
		return NULL;
	}

	/* Copy result to upper executor context */
	size = strlen(xml_spi) + 1;
	xml = SPI_palloc(size);
	memcpy(xml, xml_spi, size);

	/* Parse the SRID string into the function stack */
	srid = atoi(srid_spi);

	/* Disconnect from SPI, losing all our SPI-allocated memory now... */
	SPI_finish();

	/* Build the schema object */
	schema = pc_schema_from_xml(xml);

	if ( !schema )
	{
		ereport(ERROR,
			(errcode(ERRCODE_NOT_AN_XML_DOCUMENT),
			errmsg("unable to parse XML for pcid = %d in \"%s\"", pcid, POINTCLOUD_FORMATS)));
	}

	schema->pcid = pcid;
	schema->srid = srid;

	return schema;
}


/**
* Hold the schema references in a list.
* We'll just search them linearly, because
* usually we'll have only one per statement
*/
#define SchemaCacheSize 16

typedef struct
{
	int next_slot;
	int pcids[SchemaCacheSize];
	PCSCHEMA* schemas[SchemaCacheSize];
} SchemaCache;


/**
* Get the schema entry from the schema cache if one exists.
* If it doesn't exist, make a new empty one, cache it, and
* return it.
*/
static SchemaCache *
#if PGSQL_VERSION < 120
GetSchemaCache(FunctionCallInfoData* fcinfo)
#else
GetSchemaCache(FunctionCallInfo fcinfo)
#endif
{
	SchemaCache *cache = fcinfo->flinfo->fn_extra;
	if ( ! cache )
	{
		cache = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(SchemaCache));
		memset(cache, 0, sizeof(SchemaCache));
		fcinfo->flinfo->fn_extra = cache;
	}
	return cache;
}


PCSCHEMA *
#if PGSQL_VERSION < 120
pc_schema_from_pcid(uint32 pcid, FunctionCallInfoData *fcinfo)
#else
pc_schema_from_pcid(uint32 pcid, FunctionCallInfo fcinfo)
#endif
{
	SchemaCache *schema_cache = GetSchemaCache(fcinfo);
	int i;
	PCSCHEMA *schema;
	MemoryContext oldcontext;

	/* Unable to find/make a schema cache? Odd. */
	if ( ! schema_cache )
	{
		ereport(ERROR,
			(errcode(ERRCODE_INTERNAL_ERROR),
			errmsg("unable to create/load statement level schema cache")));
	}

	/* Find our PCID if it's in there (usually it will be first) */
	for ( i = 0; i < SchemaCacheSize; i++ )
	{
		if ( schema_cache->pcids[i] == pcid )
		{
			return schema_cache->schemas[i];
		}
	}

	elog(DEBUG1, "schema cache miss, use pc_schema_from_pcid_uncached");

	/* Not in there, load one the old-fashioned way. */
	oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
	schema = pc_schema_from_pcid_uncached(pcid);
	MemoryContextSwitchTo(oldcontext);

	/* Failed to load the XML? Odd. */
	if ( ! schema )
	{
		ereport(ERROR,
			(errcode(ERRCODE_INTERNAL_ERROR),
			errmsg("unable to load schema for pcid %u", pcid)));
	}

	/* Save the XML in the next unused slot */
	schema_cache->schemas[schema_cache->next_slot] = schema;
	schema_cache->pcids[schema_cache->next_slot] = pcid;
	schema_cache->next_slot = (schema_cache->next_slot + 1) % SchemaCacheSize;
	return schema;
}



/**********************************************************************************
* SERIALIZATION/DESERIALIZATION UTILITIES
*/

SERIALIZED_POINT *
pc_point_serialize(const PCPOINT *pcpt)
{
	size_t serpt_size = sizeof(SERIALIZED_POINT) - 1 + pcpt->schema->size;
	SERIALIZED_POINT *serpt = palloc(serpt_size);
	serpt->pcid = pcpt->schema->pcid;
	memcpy(serpt->data, pcpt->data, pcpt->schema->size);
	SET_VARSIZE(serpt, serpt_size);
	return serpt;
}

PCPOINT *
pc_point_deserialize(const SERIALIZED_POINT *serpt, const PCSCHEMA *schema)
{
	PCPOINT *pcpt;
	size_t pgsize = VARSIZE(serpt) + 1 - sizeof(SERIALIZED_POINT);
	/*
	* Big problem, the size on disk doesn't match what we expect,
	* so we cannot reliably interpret the contents.
	*/
	if ( schema->size != pgsize )
	{
		elog(ERROR, "schema size and disk size mismatch, repair the schema");
		return NULL;
	}
	pcpt = pc_point_from_data(schema, serpt->data);
	return pcpt;
}


size_t
pc_patch_serialized_size(const PCPATCH *patch)
{
	size_t stats_size = pc_stats_size(patch->schema);
	size_t common_size = sizeof(SERIALIZED_PATCH) - 1;
	switch( patch->type )
	{
	case PC_NONE:
	{
		PCPATCH_UNCOMPRESSED *pu = (PCPATCH_UNCOMPRESSED*)patch;
		return common_size + stats_size + pu->datasize;
	}
	case PC_DIMENSIONAL:
	{
		return common_size + stats_size + pc_patch_dimensional_serialized_size((PCPATCH_DIMENSIONAL*)patch);
	}
	case PC_LAZPERF:
	{
		static size_t lazsize_size = 4;
		PCPATCH_LAZPERF *pg = (PCPATCH_LAZPERF*)patch;
		return common_size + stats_size + lazsize_size + pg->lazperfsize;
	}
	default:
	{
		pcerror("%s: unknown compresed %d", __func__, patch->type);
	}
	}
	return -1;
}

static size_t
pc_patch_stats_serialize(uint8_t *buf, const PCSCHEMA *schema, const PCSTATS *stats)
{
	size_t sz = schema->size;
	/* Copy min */
	memcpy(buf, stats->min.data, sz);
	/* Copy max */
	memcpy(buf + sz, stats->max.data, sz);
	/* Copy avg */
	memcpy(buf + 2*sz, stats->avg.data, sz);

	return sz*3;
}

/**
* Stats are always three PCPOINT serializations in a row,
* min, max, avg. Their size is the uncompressed buffer size for
* a point, the schema->size.
*/
PCSTATS *
pc_patch_stats_deserialize(const PCSCHEMA *schema, const uint8_t *buf)
{
	size_t sz = schema->size;
	const uint8_t *buf_min = buf;
	const uint8_t *buf_max = buf + sz;
	const uint8_t *buf_avg = buf + 2*sz;

	return pc_stats_new_from_data(schema, buf_min, buf_max, buf_avg);
}

static SERIALIZED_PATCH *
pc_patch_dimensional_serialize(const PCPATCH *patch_in)
{
	//  uint32_t size;
	//  uint32_t pcid;
	//  uint32_t compression;
	//  uint32_t npoints;
	//  double xmin, xmax, ymin, ymax;
	//  data:
	//    pcpoint[3] stats;
	//    serialized_pcbytes[ndims] dimensions;

	int i;
	uint8_t *buf;
	size_t serpch_size = pc_patch_serialized_size(patch_in);
	SERIALIZED_PATCH *serpch = pcalloc(serpch_size);
	const PCPATCH_DIMENSIONAL *patch = (PCPATCH_DIMENSIONAL*)patch_in;

	assert(patch_in);
	assert(patch_in->type == PC_DIMENSIONAL);

	/* Copy basics */
	serpch->pcid = patch->schema->pcid;
	serpch->npoints = patch->npoints;
	serpch->bounds = patch->bounds;
	serpch->compression = patch->type;

	/* Get a pointer to the data area */
	buf = serpch->data;

	/* Write stats into the buffer */
	if ( patch->stats )
	{
		buf += pc_patch_stats_serialize(buf, patch->schema, patch->stats);
	}
	else
	{
		pcerror("%s: stats missing!", __func__);
	}

	/* Write each dimension in after the stats */
	for ( i = 0; i < patch->schema->ndims; i++ )
	{
		size_t bsize = 0;
		PCBYTES *pcb = &(patch->bytes[i]);
		pc_bytes_serialize(pcb, buf, &bsize);
		buf += bsize;
	}

	SET_VARSIZE(serpch, serpch_size);
	return serpch;
}


static SERIALIZED_PATCH *
pc_patch_lazperf_serialize(const PCPATCH *patch_in)
{
	size_t serpch_size = pc_patch_serialized_size(patch_in);
	SERIALIZED_PATCH *serpch = pcalloc(serpch_size);
	const PCPATCH_LAZPERF *patch = (PCPATCH_LAZPERF*)patch_in;
	uint32_t lazsize = patch->lazperfsize;
	uint8_t *buf = serpch->data;

	assert(patch);
	assert(patch->type == PC_LAZPERF);

	/* Copy basics */
	serpch->pcid = patch->schema->pcid;
	serpch->npoints = patch->npoints;
	serpch->bounds = patch->bounds;
	serpch->compression = patch->type;

	/* Write stats into the buffer first */
	if ( patch->stats )
	{
		buf += pc_patch_stats_serialize(buf, patch->schema, patch->stats);
	}
	else
	{
		pcerror("%s: stats missing!", __func__);
	}

	/* Write buffer size */
	memcpy(buf, &(lazsize), 4);
	buf += 4;

	/* Write buffer */
	memcpy(buf, patch->lazperf, patch->lazperfsize);
	SET_VARSIZE(serpch, serpch_size);

	return serpch;
}

static SERIALIZED_PATCH *
pc_patch_uncompressed_serialize(const PCPATCH *patch_in)
{
	//  uint32_t size;
	//  uint32_t pcid;
	//  uint32_t compression;
	//  uint32_t npoints;
	//  double xmin, xmax, ymin, ymax;
	//  data:
	//    pcpoint [];

	uint8_t *buf;
	size_t serpch_size;
	SERIALIZED_PATCH *serpch;
	const PCPATCH_UNCOMPRESSED *patch = (PCPATCH_UNCOMPRESSED *)patch_in;

	serpch_size = pc_patch_serialized_size(patch_in);
	serpch = pcalloc(serpch_size);

	/* Copy basic */
	serpch->compression = patch->type;
	serpch->pcid = patch->schema->pcid;
	serpch->npoints = patch->npoints;
	serpch->bounds = patch->bounds;

	/* Write stats into the buffer first */
	buf = serpch->data;
	if ( patch->stats )
	{
		buf += pc_patch_stats_serialize(buf, patch->schema, patch->stats);
	}
	else
	{
		pcerror("%s: stats missing!", __func__);
	}

	/* Copy point list into data buffer */
	memcpy(buf, patch->data, patch->datasize);
	SET_VARSIZE(serpch, serpch_size);
	return serpch;
}


/**
* Convert struct to byte array.
* Userdata is currently only PCDIMSTATS, hopefully updated across
* a number of iterations and saved.
*/
SERIALIZED_PATCH *
pc_patch_serialize(const PCPATCH *patch_in, void *userdata)
{
	PCPATCH *patch = (PCPATCH*)patch_in;
	SERIALIZED_PATCH *serpatch = NULL;
	/*
	* Ensure the patch has stats calculated before going on
	*/
	if ( ! patch->stats )
	{
		pcerror("%s: patch is missing stats", __func__);
		return NULL;
	}
	/*
	* Convert the patch to the final target compression,
	* which is the one in the schema.
	*/
	if ( patch->type != patch->schema->compression )
	{
		patch = pc_patch_compress(patch_in, userdata);
	}

	switch( patch->type )
	{
	case PC_NONE:
	{
		serpatch = pc_patch_uncompressed_serialize(patch);
		break;
	}
	case PC_DIMENSIONAL:
	{
		serpatch = pc_patch_dimensional_serialize(patch);
		break;
	}
	case PC_LAZPERF:
	{
		serpatch = pc_patch_lazperf_serialize(patch);
		break;
	}
	default:
	{
		pcerror("%s: unsupported compression type %d", __func__, patch->type);
	}
	}

	if ( patch != patch_in )
		pc_patch_free(patch);

	return serpatch;
}




/**
* Convert struct to byte array.
* Userdata is currently only PCDIMSTATS, hopefully updated across
* a number of iterations and saved.
*/
SERIALIZED_PATCH *
pc_patch_serialize_to_uncompressed(const PCPATCH *patch_in)
{
	PCPATCH *patch = (PCPATCH*)patch_in;
	SERIALIZED_PATCH *serpatch;

	/*  Convert the patch to uncompressed, if necessary */
	if ( patch->type != PC_NONE )
	{
		patch = pc_patch_uncompress(patch_in);
	}

	serpatch = pc_patch_uncompressed_serialize(patch);

	/* An uncompressed input won't result in a copy */
	if ( patch != patch_in )
		pc_patch_free(patch);

	return serpatch;
}

static PCPATCH *
pc_patch_uncompressed_deserialize(const SERIALIZED_PATCH *serpatch, const PCSCHEMA *schema)
{
	// typedef struct
	// {
	//  uint32_t size;
	//  uint32_t pcid;
	//  uint32_t compression;
	//  uint32_t npoints;
	//  double xmin, xmax, ymin, ymax;
	//  data:
	//    pcpoint[3] pcstats(min, max, avg)
	//    pcpoint[npoints]
	// }
	// SERIALIZED_PATCH;

	uint8_t *buf;
	size_t stats_size = pc_stats_size(schema); // 3 pcpoints worth of stats
	PCPATCH_UNCOMPRESSED *patch = pcalloc(sizeof(PCPATCH_UNCOMPRESSED));

	/* Set up basic info */
	patch->type = serpatch->compression;
	patch->schema = schema;
	patch->readonly = true;
	patch->npoints = serpatch->npoints;
	patch->maxpoints = 0;
	patch->bounds = serpatch->bounds;

	buf = (uint8_t*)serpatch->data;

	/* Point into the stats area */
	patch->stats = pc_patch_stats_deserialize(schema, buf);

	/* Advance data pointer past the stats serialization */
	patch->data = buf + stats_size;

	/* Calculate the point data buffer size */
	patch->datasize = VARSIZE(serpatch) - sizeof(SERIALIZED_PATCH) + 1 - stats_size;
	if ( patch->datasize != patch->npoints * schema->size )
		pcerror("%s: calculated patch data sizes don't match (%d != %d)", __func__, patch->datasize, patch->npoints * schema->size);

	return (PCPATCH*)patch;
}

static PCPATCH *
pc_patch_dimensional_deserialize(const SERIALIZED_PATCH *serpatch, const PCSCHEMA *schema)
{
	// typedef struct
	// {
	//  uint32_t size;
	//  uint32_t pcid;
	//  uint32_t compression;
	//  uint32_t npoints;
	//  double xmin, xmax, ymin, ymax;
	//  data:
	//    pcpoint[3] pcstats(min, max, avg)
	//    pcbytes[ndims];
	// }
	// SERIALIZED_PATCH;

	PCPATCH_DIMENSIONAL *patch;
	int i;
	const uint8_t *buf;
	int ndims = schema->ndims;
	int npoints = serpatch->npoints;
	size_t stats_size = pc_stats_size(schema); // 3 pcpoints worth of stats

	/* Reference the external data */
	patch = pcalloc(sizeof(PCPATCH_DIMENSIONAL));

	/* Set up basic info */
	patch->type = serpatch->compression;
	patch->schema = schema;
	patch->readonly = true;
	patch->npoints = npoints;
	patch->bounds = serpatch->bounds;

	/* Point into the stats area */
	patch->stats = pc_patch_stats_deserialize(schema, serpatch->data);

	/* Set up dimensions */
	patch->bytes = pcalloc(ndims * sizeof(PCBYTES));
	buf = serpatch->data + stats_size;

	for ( i = 0; i < ndims; i++ )
	{
		PCBYTES *pcb = &(patch->bytes[i]);
		PCDIMENSION *dim = schema->dims[i];
		pc_bytes_deserialize(buf, dim, pcb, true /*readonly*/, false /*flipendian*/);
		pcb->npoints = npoints;
		buf += pc_bytes_serialized_size(pcb);
	}

	return (PCPATCH*)patch;
}

/*
* We don't do any radical deserialization here. Don't build out the tree, just
* set up pointers to the start of the buffer, so we can build it out later
* if necessary.
*/
static PCPATCH *
pc_patch_lazperf_deserialize(const SERIALIZED_PATCH *serpatch, const PCSCHEMA *schema)
{
	PCPATCH_LAZPERF *patch;
	uint32_t lazperfsize;
	int npoints = serpatch->npoints;
	size_t stats_size = pc_stats_size(schema);
	uint8_t *buf = (uint8_t*)serpatch->data + stats_size;

	/* Reference the external data */
	patch = pcalloc(sizeof(PCPATCH_LAZPERF));

	/* Set up basic info */
	patch->type = serpatch->compression;
	patch->schema = schema;
	patch->readonly = true;
	patch->npoints = npoints;
	patch->bounds = serpatch->bounds;

	/* Point into the stats area */
	patch->stats = pc_patch_stats_deserialize(schema, serpatch->data);

	/* Set up buffer */
	memcpy(&lazperfsize, buf, 4);
	patch->lazperfsize = lazperfsize;
	buf += 4;

	patch->lazperf = pcalloc( patch->lazperfsize );
	memcpy(patch->lazperf, buf, patch->lazperfsize);

	return (PCPATCH*)patch;
}

PCPATCH *
pc_patch_deserialize(const SERIALIZED_PATCH *serpatch, const PCSCHEMA *schema)
{
	switch(serpatch->compression)
	{
	case PC_NONE:
		return pc_patch_uncompressed_deserialize(serpatch, schema);
	case PC_DIMENSIONAL:
		return pc_patch_dimensional_deserialize(serpatch, schema);
	case PC_LAZPERF:
		return pc_patch_lazperf_deserialize(serpatch, schema);
	}
	pcerror("%s: unsupported compression type", __func__);
	return NULL;
}


static uint8_t *
pc_patch_wkb_set_double(uint8_t *wkb, double d)
{
	memcpy(wkb, &d, 8);
	wkb += 8;
	return wkb;
}

static uint8_t *
pc_patch_wkb_set_int32(uint8_t *wkb, uint32_t i)
{
	memcpy(wkb, &i, 4);
	wkb += 4;
	return wkb;
}

static uint8_t *
pc_patch_wkb_set_char(uint8_t *wkb, char c)
{
	memcpy(wkb, &c, 1);
	wkb += 1;
	return wkb;
}

/* 0 = xdr | big endian */
/* 1 = ndr | little endian */
static char
machine_endian(void)
{
	static int check_int = 1; /* dont modify this!!! */
	return *((char *) &check_int);
}

uint8_t *
pc_patch_to_geometry_wkb_envelope(const SERIALIZED_PATCH *pa, const PCSCHEMA *schema, size_t *wkbsize)
{
	static uint32_t srid_mask = 0x20000000;
	static uint32_t nrings = 1;
	static uint32_t npoints = 5;
	uint32_t wkbtype = 3; /* WKB POLYGON */
	uint8_t *wkb, *ptr;
	int has_srid = false;
	size_t size = 1 + 4 + 4 + 4 + 2*npoints*8; /* endian + type + nrings + npoints + 5 dbl pts */

	/* Bounds! */
	double xmin = pa->bounds.xmin;
	double ymin = pa->bounds.ymin;
	double xmax = pa->bounds.xmax;
	double ymax = pa->bounds.ymax;

	/* Make sure they're slightly bigger than a point */
	if ( xmin == xmax ) xmax += xmax * 0.0000001;
	if ( ymin == ymax ) ymax += ymax * 0.0000001;

	if ( schema->srid > 0 )
	{
		has_srid = true;
		wkbtype |= srid_mask;
		size += 4;
	}

	wkb = palloc(size);
	ptr = wkb;

	ptr = pc_patch_wkb_set_char(ptr, machine_endian()); /* Endian flag */

	ptr = pc_patch_wkb_set_int32(ptr, wkbtype); /* TYPE = Polygon */

	if ( has_srid )
	{
		ptr = pc_patch_wkb_set_int32(ptr, schema->srid); /* SRID */
	}

	ptr = pc_patch_wkb_set_int32(ptr, nrings);  /* NRINGS = 1 */
	ptr = pc_patch_wkb_set_int32(ptr, npoints); /* NPOINTS = 5 */

	/* Point 0 */
	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmin);
	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymin);

	/* Point 1 */
	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmin);
	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymax);

	/* Point 2 */
	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmax);
	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymax);

	/* Point 3 */
	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmax);
	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymin);

	/* Point 4 */
	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmin);
	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymin);

	if ( wkbsize ) *wkbsize = size;
	return wkb;
}
