/***********************************************************************
* pc_util.c
*
*  Handy functions used by the library.
*
*  PgSQL Pointcloud is free and open source software provided
*  by the Government of Canada
*  Copyright (c) 2013 Natural Resources Canada
*
***********************************************************************/

#include "pc_api_internal.h"
#include <float.h>

/**********************************************************************************
* WKB AND ENDIANESS UTILITIES
*/

/* Our static character->number map. Anything > 15 is invalid */
static uint8_t hex2char[256] =
{
	/* not Hex characters */
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
	/* 0-9 */
	0,1,2,3,4,5,6,7,8,9,20,20,20,20,20,20,
	/* A-F */
	20,10,11,12,13,14,15,20,20,20,20,20,20,20,20,20,
	/* not Hex characters */
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
	/* a-f */
	20,10,11,12,13,14,15,20,20,20,20,20,20,20,20,20,
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
	/* not Hex characters (upper 128 characters) */
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
	20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20
};


uint8_t*
pc_bytes_from_hexbytes(const char *hexbuf, size_t hexsize)
{
	uint8_t *buf = NULL;
	register uint8_t h1, h2;
	int i;

	if( hexsize % 2 )
		pcerror("Invalid hex string, length (%d) has to be a multiple of two!", hexsize);

	buf = pcalloc(hexsize/2);

	if( ! buf )
		pcerror("Unable to allocate memory buffer.");

	for( i = 0; i < hexsize/2; i++ )
	{
		h1 = hex2char[(int)hexbuf[2*i]];
		h2 = hex2char[(int)hexbuf[2*i+1]];
		if( h1 > 15 )
			pcerror("Invalid hex character (%c) encountered", hexbuf[2*i]);
		if( h2 > 15 )
			pcerror("Invalid hex character (%c) encountered", hexbuf[2*i+1]);
		/* First character is high bits, second is low bits */
		buf[i] = ((h1 & 0x0F) << 4) | (h2 & 0x0F);
	}
	return buf;
}

static char *hexchr = "0123456789ABCDEF";

char*
pc_hexbytes_from_bytes(const uint8_t *bytebuf, size_t bytesize)
{
	char *buf = pcalloc(2*bytesize + 1); /* 2 chars per byte + null terminator */
	int i;
	buf[2*bytesize] = '\0';
	for ( i = 0; i < bytesize; i++ )
	{
		/* Top four bits to 0-F */
		buf[2*i] = hexchr[bytebuf[i] >> 4];
		/* Bottom four bits to 0-F */
		buf[2*i+1] = hexchr[bytebuf[i] & 0x0F];
	}

	return buf;
}


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

int32_t
int32_flip_endian(int32_t val)
{
	int i;
	uint8_t tmp;
	uint8_t b[4];
	memcpy(b, &val, 4);
	for ( i = 0; i < 2; i++ )
	{
		tmp = b[i];
		b[i] = b[3-i];
		b[3-i] = tmp;
	}
	memcpy(&val, b, 4);
	return val;
}

int16_t
int16_flip_endian(int16_t val)
{
	uint8_t tmp;
	uint8_t b[2];
	memcpy(b, &val, 2);
	tmp = b[0];
	b[0] = b[1];
	b[1] = tmp;
	memcpy(&val, b, 2);
	return val;
}

int32_t
wkb_get_int32(const uint8_t *wkb, int flip_endian)
{
	int32_t i;
	memcpy(&i, wkb, 4);
	if ( flip_endian )
		return int32_flip_endian(i);
	else
		return i;
}

int16_t
wkb_get_int16(const uint8_t *wkb, int flip_endian)
{
	int16_t i;
	memcpy(&i, wkb, 2);
	if ( flip_endian )
		return int16_flip_endian(i);
	else
		return i;
}

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

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

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

uint32_t
pc_wkb_get_pcid(const uint8_t *wkb)
{
	/* We expect the bytes to be in WKB format for PCPOINT/PCPATCH */
	/* byte 0:   endian */
	/* byte 1-4: pcid */
	/* ...data... */
	uint32_t pcid;
	memcpy(&pcid, wkb + 1, 4);
	if ( wkb[0] != machine_endian() )
	{
		pcid = int32_flip_endian(pcid);
	}
	return pcid;
}

uint32_t
wkb_get_compression(const uint8_t *wkb)
{
	/* We expect the bytes to be in WKB format for PCPATCH */
	/* byte 0:   endian */
	/* byte 1-4: pcid */
	/* byte 5-8: compression */
	/* ...data... */
	uint32_t compression;
	memcpy(&compression, wkb+1+4, 4);
	if ( wkb[0] != machine_endian() )
	{
		compression = int32_flip_endian(compression);
	}
	return compression;
}

uint32_t
wkb_get_npoints(const uint8_t *wkb)
{
	/* We expect the bytes to be in WKB format for PCPATCH */
	/* byte 0:   endian */
	/* byte 1-4: pcid */
	/* byte 5-8: compression */
	/* byte 9-12: npoints */
	/* ...data... */
	uint32_t npoints;
	memcpy(&npoints, wkb+1+4+4, 4);
	if ( wkb[0] != machine_endian() )
	{
		npoints = int32_flip_endian(npoints);
	}
	return npoints;
}

uint8_t*
uncompressed_bytes_flip_endian(const uint8_t *bytebuf, const PCSCHEMA *schema, uint32_t npoints)
{
	int i, j, k;
	size_t bufsize = schema->size * npoints;
	uint8_t *buf = pcalloc(bufsize);

	memcpy(buf, bytebuf, bufsize);

	for ( i = 0; i < npoints ; i++ )
	{
		for ( j = 0; j < schema->ndims; j++ )
		{
			PCDIMENSION *dimension = schema->dims[j];
			uint8_t *ptr = buf + i * schema->size + dimension->byteoffset;

			for ( k = 0; k < ((dimension->size)/2); k++ )
			{
				int l = dimension->size - k - 1;
				uint8_t tmp = ptr[k];
				ptr[k] = ptr[l];
				ptr[l] = tmp;
			}
		}
	}

	return buf;
}

int
pc_bounds_intersects(const PCBOUNDS *b1, const PCBOUNDS *b2)
{
	if ( b1->xmin > b2->xmax ||
		 b1->xmax < b2->xmin ||
		 b1->ymin > b2->ymax ||
		 b1->ymax < b2->ymin )
	{
		return PC_FALSE;
	}
	return PC_TRUE;
}

void
pc_bounds_init(PCBOUNDS *b)
{
	b->xmin = b->ymin = DBL_MAX;
	b->xmax = b->ymax = -1*DBL_MAX;
}

void pc_bounds_merge(PCBOUNDS *b1, const PCBOUNDS *b2)
{
	if ( b2->xmin < b1->xmin ) b1->xmin = b2->xmin;
	if ( b2->ymin < b1->ymin ) b1->ymin = b2->ymin;
	if ( b2->xmax > b1->xmax ) b1->xmax = b2->xmax;
	if ( b2->ymax > b1->ymax ) b1->ymax = b2->ymax;
}

static uint32_t srid_mask = 0x20000000;
static uint32_t m_mask = 0x40000000;
static uint32_t z_mask = 0x80000000;

uint8_t *
pc_bounding_diagonal_wkb_from_bounds(
	const PCBOUNDS *bounds, const PCSCHEMA *schema, size_t *wkbsize)
{
	uint8_t *wkb, *ptr;
	uint32_t wkbtype;
	size_t size;

	wkbtype = 2; /* WKB LINESTRING */
	size = 1 + 4 + 4 + (2 * 2 * 8); /* endian + type + npoints + 2 dbl pts */

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

	wkb = pcalloc(size);
	ptr = wkb;

	ptr = wkb_set_char(ptr, machine_endian()); /* Endian flag */
	ptr = wkb_set_uint32(ptr, wkbtype); /* Geometry type */

	if ( schema->srid != 0 )
	{
		ptr = wkb_set_uint32(ptr, schema->srid); /* SRID */
	}

	ptr = wkb_set_uint32(ptr, 2); /* NPOINTS = 2 */

	// point 1
	ptr = wkb_set_double(ptr, bounds->xmin);
	ptr = wkb_set_double(ptr, bounds->ymin);

	// point 2
	ptr = wkb_set_double(ptr, bounds->xmax);
	ptr = wkb_set_double(ptr, bounds->ymax);

	if ( wkbsize )
		*wkbsize = size;

	return wkb;
}

uint8_t *
pc_bounding_diagonal_wkb_from_stats(const PCSTATS *stats, size_t *wkbsize)
{
	const PCSCHEMA *schema = stats->min.schema;
	const PCPOINT *stat;
	uint8_t *wkb, *ptr;
	uint32_t wkbtype;
	size_t size;
	double val;

	wkbtype = 2; /* WKB LINESTRING */
	size = 1 + 4 + 4 + (2 * 2 * 8); /* endian + type + npoints + 2 dbl pts */

	if ( schema->srid != 0 )
	{
		wkbtype |= srid_mask;
		size += 4;
	}
	if ( schema->zdim )
	{
		wkbtype |= z_mask;
		size += 2 * 8;
	}
	if ( schema->mdim )
	{
		wkbtype |= m_mask;
		size += 2 * 8;
	}

	wkb = pcalloc(size);
	ptr = wkb;

	ptr = wkb_set_char(ptr, machine_endian()); /* Endian flag */
	ptr = wkb_set_uint32(ptr, wkbtype); /* Geometry type */

	if ( schema->srid != 0 )
	{
		ptr = wkb_set_uint32(ptr, schema->srid); /* SRID */
	}

	ptr = wkb_set_uint32(ptr, 2); /* NPOINTS = 2 */

	// point 1
	stat = &stats->min;
	pc_point_get_x(stat, &val);
	ptr = wkb_set_double(ptr, val);
	pc_point_get_y(stat, &val);
	ptr = wkb_set_double(ptr, val);
	if ( schema->zdim )
	{
		pc_point_get_z(stat, &val);
		ptr = wkb_set_double(ptr, val);
	}
	if ( schema->mdim )
	{
		pc_point_get_m(stat, &val);
		ptr = wkb_set_double(ptr, val);
	}

	// point 2
	stat = &stats->max;
	pc_point_get_x(stat, &val);
	ptr = wkb_set_double(ptr, val);
	pc_point_get_y(stat, &val);
	ptr = wkb_set_double(ptr, val);
	if ( schema->zdim )
	{
		pc_point_get_z(stat, &val);
		ptr = wkb_set_double(ptr, val);
	}
	if ( schema->mdim )
	{
		pc_point_get_m(stat, &val);
		ptr = wkb_set_double(ptr, val);
	}

	if ( wkbsize )
		*wkbsize = size;

	return wkb;
}
