/**********************************************************************
 *
 * PostGIS - Spatial Types for PostgreSQL
 * http://postgis.net
 * Copyright 2010 Paul Ramsey <pramsey@cleverelephant.ca>
 * Copyright 2026 Darafei Praliaskouski <me@komzpa.net>
 *
 * 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "CUnit/Basic.h"

#include "liblwgeom_internal.h"
#include "cu_tester.h"
#include "varint.h"

/*
** Global variable to hold TWKB strings
*/
static char *hex_a;
static char *hex_b;
static uint8_t precision = 0;
static uint8_t variant = 0;

/*
** The suite initialization function.
** Create any reused objects.
*/
static int init_twkb_in_suite(void)
{
	hex_a = NULL;
	hex_b = NULL;
	return 0;
}

/*
** The suite cleanup function.
** Frees any global objects.
*/
static int clean_twkb_in_suite(void)
{
	if (hex_a) free(hex_a);
	if (hex_b) free(hex_b);
	hex_a = NULL;
	hex_b = NULL;
	return 0;
}


static void cu_twkb_in(char *wkt)
{
	LWGEOM_PARSER_RESULT pr;
	LWGEOM *g_a, *g_b;
	/* int i; char *hex; */

	/* Turn WKT into geom */
	lwgeom_parse_wkt(&pr, wkt, LW_PARSER_CHECK_NONE);
	if ( pr.errcode )
	{
		printf("ERROR: %s\n", pr.message);
		printf("POSITION: %d\n", pr.errlocation);
		exit(0);
	}

	/* Get the geom */
	g_a = pr.geom;

	/* Turn geom into TWKB */
	lwvarlena_t *twkb_a = lwgeom_to_twkb(g_a, variant, precision, precision, precision);

	// printf("\n Size: %ld\n", twkb_size_a);

	/* Turn TWKB back into geom  */
	g_b = lwgeom_from_twkb((uint8_t *)twkb_a->data, LWSIZE_GET(twkb_a->size) - LWVARHDRSZ, LW_PARSER_CHECK_NONE);

	// printf("\n Org: %s\n 1st: %s\n 2nd: %s\n", wkt, lwgeom_to_ewkt(g_a), lwgeom_to_ewkt(g_b));

	/* Turn geom to TWKB again */
	lwvarlena_t *twkb_b = lwgeom_to_twkb(g_b, variant, precision, precision, precision);

	/* Turn TWKB into hex for comparisons */
	if ( hex_a ) lwfree(hex_a);
	if ( hex_b ) lwfree(hex_b);
	hex_a = hexbytes_from_bytes((uint8_t *)twkb_a->data, LWSIZE_GET(twkb_a->size) - LWVARHDRSZ);
	hex_b = hexbytes_from_bytes((uint8_t *)twkb_b->data, LWSIZE_GET(twkb_b->size) - LWVARHDRSZ);

	/* Clean up */
	lwfree(twkb_a);
	lwfree(twkb_b);
	lwgeom_parser_result_free(&pr);
	lwgeom_free(g_b);
}

static void test_twkb_in_point(void)
{
	cu_twkb_in("POINT(0 0 0 0)");
//	printf("old: %s\nnew: %s\n",hex_a, hex_b);
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("POINT(1 1)");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("POINT EMPTY");
	ASSERT_STRING_EQUAL(hex_a, hex_b);
}

static void test_twkb_in_linestring(void)
{
	cu_twkb_in("LINESTRING(0 0,1 1)");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("LINESTRING(0 0 1,1 1 2,2 2 3)");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("LINESTRING EMPTY");
	ASSERT_STRING_EQUAL(hex_a, hex_b);
}

static void test_twkb_in_polygon(void)
{
	cu_twkb_in("POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0))");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("POLYGON((0 0 0 1,0 1 0 2,1 1 0 3,1 0 0 4,0 0 0 5))");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("POLYGON((0 0 0 1,0 1 0 2,1 1 0 3,1 0 0 4,0 0 0 5))");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("POLYGON EMPTY");
	ASSERT_STRING_EQUAL(hex_a, hex_b);
}

static void test_twkb_in_multipoint(void)
{
	cu_twkb_in("MULTIPOINT Z EMPTY");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("MULTIPOINT(1 2, EMPTY, 5 6)");
	ASSERT_STRING_EQUAL(hex_a, hex_b);
	// printf(" 1st: %s\n 2nd: %s\n", hex_a, hex_b);

	cu_twkb_in("MULTIPOINT(0 0 0,0 1 0,1 1 0,1 0 0,0 0 1)");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("MULTIPOINT(1 2 3, 1 2 3, 4 5 6, -3 -4 -5, -10 -5 -1)");
	ASSERT_STRING_EQUAL(hex_a, hex_b);
}

static void test_twkb_in_multilinestring(void)
{
	cu_twkb_in("MULTILINESTRING((0 0,0 1),(1 1, 10 10))");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("MULTILINESTRING((0 0,0 1),EMPTY,(1 1, 10 10))");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("MULTILINESTRING((0 0 200000,0 1 10),(1 100000000 23, 10 10 45))");
	ASSERT_STRING_EQUAL(hex_a, hex_b);
	// printf(" 1st: %s\n 2nd: %s\n", hex_a, hex_b);

	cu_twkb_in("MULTILINESTRING EMPTY");
	ASSERT_STRING_EQUAL(hex_a, hex_b);
}

static void test_twkb_in_multipolygon(void)
{
	cu_twkb_in("MULTIPOLYGON(((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((-1 -1 0,-1 2 0,2 2 0,2 -1 0,-1 -1 0),(0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)))");
	ASSERT_STRING_EQUAL(hex_a, hex_b);
	//printf("old: %s\nnew: %s\n",hex_a, hex_b);

	cu_twkb_in("MULTIPOLYGON EMPTY");
	ASSERT_STRING_EQUAL(hex_a, hex_b);
	//printf("old: %s\nnew: %s\n",hex_a, hex_b);
}

static void test_twkb_in_collection(void)
{
	cu_twkb_in("GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0))),POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),POINT(1 1 1),LINESTRING(0 0 0, 1 1 1))");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("GEOMETRYCOLLECTION(POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),POINT(1 1 1))");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("GEOMETRYCOLLECTION EMPTY");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("GEOMETRYCOLLECTION(POINT(1 2 3), LINESTRING EMPTY, POINT(4 5 6))");
	ASSERT_STRING_EQUAL(hex_a, hex_b);

	cu_twkb_in("GEOMETRYCOLLECTION(POINT(1 2 3), POINT EMPTY, POINT(4 5 6))");
	ASSERT_STRING_EQUAL(hex_a, hex_b);
}

/*
** PRECISION TESTS HERE: We ALTER THE 'precision' GLOBAL
*/

static void test_twkb_in_precision(void)
{
	/* Try these cases at several different precisions */
	for ( precision = 1; precision <= 6; precision++ )
	{
		cu_twkb_in("MULTILINESTRING((0 0,0 1),EMPTY,(1 1, 10 10))");
		ASSERT_STRING_EQUAL(hex_a, hex_b);

		cu_twkb_in("MULTIPOLYGON(((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((-1 -1 0,-1 2 0,2 2 0,2 -1 0,-1 -1 0),(0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)))");
		ASSERT_STRING_EQUAL(hex_a, hex_b);

		cu_twkb_in("GEOMETRYCOLLECTION(POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),POINT(1 1 1))");
		ASSERT_STRING_EQUAL(hex_a, hex_b);

		cu_twkb_in("MULTILINESTRING((0 0 200000,0 1 10),(1 100000000 23, 10 10 45))");
		// printf("old: %s\nnew: %s\n",hex_a, hex_b);
		ASSERT_STRING_EQUAL(hex_a, hex_b);
	}

	/* Go back to default precision */
	precision = 0;
}

static void
test_twkb_in_truncated_extended_dims(void)
{
	const uint8_t twkb[] = {
	    0x01, /* POINT with default precision. */
	    0x18  /* Empty geometry plus extended-dimension byte follows. */
	};
	LWGEOM *geom;

	cu_error_msg_reset();

	geom = lwgeom_from_twkb(twkb, sizeof(twkb), LW_PARSER_CHECK_NONE);

	/* Truncated extended-dimension metadata used to be read before the bounds
	 * check. The expected contract is an ordinary TWKB size error, not an
	 * out-of-buffer read while constructing the header.
	 */
	ASSERT_STRING_EQUAL(cu_error_msg, "twkb_parse_state_advance: TWKB structure does not match expected size!");
	CU_ASSERT_PTR_NULL(geom);
	if (geom != NULL)
		lwgeom_free(geom);
	cu_error_msg_reset();
}

static void
test_twkb_in_overlong_varint(void)
{
	const uint8_t twkb[] = {0x02,
				0x00, /* LINESTRING with default precision. */
				0x80,
				0x80,
				0x80,
				0x80,
				0x80,
				0x80, /* Overlong point-count varint. */
				0x80,
				0x80,
				0x80,
				0x80,
				0x00};
	LWGEOM *geom;

	cu_error_msg_reset();

	geom = lwgeom_from_twkb(twkb, sizeof(twkb), LW_PARSER_CHECK_NONE);

	/* Varints longer than the uint64_t encoding space used to shift by more
	 * than the C type width before the parser could report malformed input.
	 */
	ASSERT_STRING_EQUAL(cu_error_msg, "varint_u64_decode: varint exceeds 64 bits");
	CU_ASSERT_PTR_NULL(geom);
	if (geom != NULL)
		lwgeom_free(geom);
	cu_error_msg_reset();
}

static void
test_twkb_in_count_exceeds_payload(void)
{
	const uint8_t twkb[] = {0x02,
				0x00, /* LINESTRING with default precision. */
				0xff,
				0xff,
				0xff,
				0xff,
				0x0f, /* UINT32_MAX points. */
				0x00,
				0x00};
	LWGEOM *geom;

	cu_error_msg_reset();

	geom = lwgeom_from_twkb(twkb, sizeof(twkb), LW_PARSER_CHECK_NONE);

	/* Count fields are allocation sizes. Reject impossible counts before
	 * constructing point arrays from a buffer that cannot contain them.
	 */
	ASSERT_STRING_EQUAL(cu_error_msg,
			    "twkb_parse_state_has_min_bytes: TWKB element count exceeds remaining payload");
	CU_ASSERT_PTR_NULL(geom);
	if (geom != NULL)
		lwgeom_free(geom);
	cu_error_msg_reset();
}

static void
test_twkb_in_deep_collection(void)
{
	const size_t ngeoms = 201;
	const size_t twkb_size = ngeoms * 3 + 2;
	uint8_t *twkb = lwalloc(twkb_size);
	LWGEOM *geom;
	size_t i;

	for (i = 0; i < ngeoms; i++)
	{
		/* GEOMETRYCOLLECTION with default precision and one child. */
		twkb[3 * i] = 0x07;
		twkb[3 * i + 1] = 0x00;
		twkb[3 * i + 2] = 0x01;
	}
	twkb[3 * ngeoms] = 0x01;     /* POINT with default precision. */
	twkb[3 * ngeoms + 1] = 0x10; /* Empty geometry. */

	cu_error_msg_reset();

	geom = lwgeom_from_twkb(twkb, twkb_size, LW_PARSER_CHECK_NONE);

	/* Recursive collection parsing must reject hostile nesting before the C
	 * stack becomes the effective input validator.
	 */
	ASSERT_STRING_EQUAL(cu_error_msg, "Geometry has too many chained collections");
	CU_ASSERT_PTR_NULL(geom);
	lwfree(twkb);
	cu_error_msg_reset();
}

static void
test_twkb_in_coordinate_delta_wraparound(void)
{
	const uint8_t twkb[] = {0x03, 0x00, 0x16, 0x16, 0x16, 0x16, 0x16, 0x2f, 0x00, 0x00, 0x3d, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x16, 0x16, 0x16, 0x16, 0x16, 0x23, 0x06, 0x02, 0x7e, 0x84,
				0x83, 0x84, 0x84, 0x84, 0x84, 0xae, 0xee, 0xff, 0x01, 0xf9, 0xff, 0xff, 0xff,
				0xff, 0xef, 0x16, 0x16, 0xff, 0x16, 0x16, 0x16, 0x23, 0x06, 0x02, 0x7e, 0x84,
				0x83, 0x84, 0x84, 0x84, 0x84, 0xae, 0xee, 0xff, 0x01, 0xf9, 0xff, 0xff, 0xff,
				0xff, 0xef, 0xff, 0xff, 0x01, 0xff, 0x02, 0x00, 0x79, 0xb0};
	LWGEOM *geom;

	cu_error_msg_reset();

	geom = lwgeom_from_twkb(twkb, sizeof(twkb), LW_PARSER_CHECK_NONE);

	/* Delta-encoded coordinates must not rely on signed integer overflow to
	 * wrap hostile inputs into the int64_t accumulator range.
	 */
	CU_ASSERT(strstr(cu_error_msg, "called with n=0 and npoints=0") != NULL);
	CU_ASSERT_PTR_NULL(geom);
	if (geom != NULL)
		lwgeom_free(geom);
	cu_error_msg_reset();
}

static void
test_twkb_in_linestring_coordinate_delta_wraparound(void)
{
	uint8_t twkb[64] = {0x02, 0x00, 0x02};
	uint8_t *pos = twkb + 3;
	LWGEOM *geom;

	pos += varint_s64_encode_buf(INT64_MAX, pos);
	pos += varint_s64_encode_buf(0, pos);
	pos += varint_s64_encode_buf(1, pos);
	pos += varint_s64_encode_buf(0, pos);

	cu_error_msg_reset();

	geom = lwgeom_from_twkb(twkb, (size_t)(pos - twkb), LW_PARSER_CHECK_NONE);

	ASSERT_STRING_EQUAL(cu_error_msg, "");
	CU_ASSERT_PTR_NOT_NULL(geom);
	if (geom != NULL)
		lwgeom_free(geom);
	cu_error_msg_reset();
}

static void
test_twkb_in_unsupported_type(void)
{
	const uint8_t twkb[] = {0x08, /* unsupported TWKB type with default precision. */
				0x00};
	LWGEOM *geom;

	cu_error_msg_reset();

	geom = lwgeom_from_twkb(twkb, sizeof(twkb), LW_PARSER_CHECK_NONE);

	ASSERT_STRING_EQUAL(cu_error_msg, "lwgeom_from_twkb_state: Unsupported geometry type: Unknown");
	CU_ASSERT_PTR_NULL(geom);
	if (geom != NULL)
		lwgeom_free(geom);
	cu_error_msg_reset();
}

/*
** Used by test harness to register the tests in this file.
*/
void twkb_in_suite_setup(void);
void twkb_in_suite_setup(void)
{
	CU_pSuite suite = CU_add_suite("twkb_input", init_twkb_in_suite, clean_twkb_in_suite);
	PG_ADD_TEST(suite, test_twkb_in_point);
	PG_ADD_TEST(suite, test_twkb_in_linestring);
	PG_ADD_TEST(suite, test_twkb_in_polygon);
	PG_ADD_TEST(suite, test_twkb_in_multipoint);
	PG_ADD_TEST(suite, test_twkb_in_multilinestring);
	PG_ADD_TEST(suite, test_twkb_in_multipolygon);
	PG_ADD_TEST(suite, test_twkb_in_collection);
	PG_ADD_TEST(suite, test_twkb_in_precision);
	PG_ADD_TEST(suite, test_twkb_in_truncated_extended_dims);
	PG_ADD_TEST(suite, test_twkb_in_overlong_varint);
	PG_ADD_TEST(suite, test_twkb_in_count_exceeds_payload);
	PG_ADD_TEST(suite, test_twkb_in_deep_collection);
	PG_ADD_TEST(suite, test_twkb_in_coordinate_delta_wraparound);
	PG_ADD_TEST(suite, test_twkb_in_linestring_coordinate_delta_wraparound);
	PG_ADD_TEST(suite, test_twkb_in_unsupported_type);
}
