/**********************************************************************
 *
 * PostGIS - Spatial Types for PostgreSQL
 * http://postgis.net
 *
 * Copyright (C) 2011 Sandro Santilli <strk@kbt.io>
 * Copyright (C) 2008 Paul Ramsey
 *
 * 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 "CUnit/Basic.h"
#include "CUnit/CUnit.h"

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


static LWGEOM* lwgeom_from_text(const char *str)
{
	LWGEOM_PARSER_RESULT r;
	if( LW_FAILURE == lwgeom_parse_wkt(&r, (char*)str, LW_PARSER_CHECK_NONE) )
		return NULL;
	return r.geom;
}

static char* lwgeom_to_text(const LWGEOM *geom)
{
	return lwgeom_to_wkt(geom, WKT_ISO, 8, NULL);
}

static void test_ptarray_append_point(void)
{
	LWLINE *line;
	char *wkt;
	POINT4D p;

	line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,1 1)"));
	p.x = 1;
	p.y = 1;
	ptarray_append_point(line->points, &p, LW_TRUE);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line));
	ASSERT_STRING_EQUAL(wkt,"LINESTRING(0 0,1 1,1 1)");
	lwfree(wkt);

	ptarray_append_point(line->points, &p, LW_FALSE);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line));
	ASSERT_STRING_EQUAL(wkt,"LINESTRING(0 0,1 1,1 1)");
	lwfree(wkt);

	lwline_free(line);
}

static void test_ptarray_insert_point(void)
{
	LWLINE *line;
	char *wkt;
	POINT4D p;

	line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING EMPTY"));
	p.x = 1;
	p.y = 1;
	ptarray_insert_point(line->points, &p, 0);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line));
	ASSERT_STRING_EQUAL(wkt,"LINESTRING(1 1)");
	lwfree(wkt);

	p.x = 2;
	p.y = 20;
	ptarray_insert_point(line->points, &p, 0);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line));
	ASSERT_STRING_EQUAL(wkt,"LINESTRING(2 20,1 1)");
	lwfree(wkt);

	p.x = 3;
	p.y = 30;
	ptarray_insert_point(line->points, &p, 1);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line));
	ASSERT_STRING_EQUAL(wkt,"LINESTRING(2 20,3 30,1 1)");
	lwfree(wkt);

	p.x = 4;
	p.y = 40;
	ptarray_insert_point(line->points, &p, 0);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line));
	ASSERT_STRING_EQUAL(wkt,"LINESTRING(4 40,2 20,3 30,1 1)");
	lwfree(wkt);

	p.x = 5;
	p.y = 50;
	ptarray_insert_point(line->points, &p, 4);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line));
	ASSERT_STRING_EQUAL(wkt,"LINESTRING(4 40,2 20,3 30,1 1,5 50)");
	lwfree(wkt);

	lwline_free(line);
}

static void test_ptarray_append_ptarray(void)
{
	LWLINE *line1, *line2;
	int ret;
	char *wkt;

	/* Empty first line */
	line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING EMPTY"));
	line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,0 10,5 5)"));
	ret = ptarray_append_ptarray(line1->points, line2->points, -1);
	CU_ASSERT(ret == LW_SUCCESS);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line1));
	ASSERT_STRING_EQUAL(wkt, "LINESTRING(0 0,0 10,5 5)");
	lwfree(wkt);
	lwline_free(line2);
	lwline_free(line1);

	/* Empty second line */
	line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0, 5 5, 6 3)"));
	line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING EMPTY"));
	ret = ptarray_append_ptarray(line1->points, line2->points, -1);
	CU_ASSERT(ret == LW_SUCCESS);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line1));
	ASSERT_STRING_EQUAL(wkt, "LINESTRING(0 0,5 5,6 3)");
	lwfree(wkt);
	lwline_free(line2);
	lwline_free(line1);

	/* Both lines empty */
	line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING EMPTY"));
	line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING EMPTY"));
	ret = ptarray_append_ptarray(line1->points, line2->points, -1);
	CU_ASSERT(ret == LW_SUCCESS);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line1));
	ASSERT_STRING_EQUAL(wkt, "LINESTRING EMPTY");
	lwfree(wkt);
	lwline_free(line2);
	lwline_free(line1);

	/* Sane sewing */
	line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(10 4, 0 0,5 7)"));
	line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(5 7,12 43, 42 15)"));
	ret = ptarray_append_ptarray(line1->points, line2->points, 0);
	CU_ASSERT(ret == LW_SUCCESS);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line1));
	ASSERT_STRING_EQUAL(wkt, "LINESTRING(10 4,0 0,5 7,12 43,42 15)");
	lwfree(wkt);
	lwline_free(line2);
	lwline_free(line1);

	/* Untolerated sewing */
	line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(10 4, 0 0,5 7)"));
	line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(5.5 7,12 43, 42 15)"));
	ret = ptarray_append_ptarray(line1->points, line2->points, 0);
	CU_ASSERT(ret == LW_FAILURE);
	lwline_free(line2);
	lwline_free(line1);

	/* Tolerated sewing */
	line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(10 4, 0 0,5 7)"));
	line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(5.5 7,12 43, 42 15)"));
	ret = ptarray_append_ptarray(line1->points, line2->points, .7);
	CU_ASSERT(ret == LW_SUCCESS);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line1));
	ASSERT_STRING_EQUAL(wkt, "LINESTRING(10 4,0 0,5 7,5.5 7,12 43,42 15)");
	lwfree(wkt);
	lwline_free(line2);
	lwline_free(line1);

	/* Check user input trust (creates non-simple line */
	line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,0 10)"));
	line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,0 10)"));
	ret = ptarray_append_ptarray(line1->points, line2->points, -1);
	CU_ASSERT(ret == LW_SUCCESS);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line1));
	ASSERT_STRING_EQUAL(wkt, "LINESTRING(0 0,0 10,0 0,0 10)");
	lwfree(wkt);
	lwline_free(line2);
	lwline_free(line1);

	/* Mixed dimensionality is not allowed */
	line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 10 0, 10 0 0)"));
	line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(10 0,11 0)"));
	ret = ptarray_append_ptarray(line1->points, line2->points, -1);
	CU_ASSERT(ret == LW_FAILURE);
	lwline_free(line2);
	lwline_free(line1);

	/* Appending a read-only pointarray is allowed */
	line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 10, 10 0)"));
	line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(10 0,11 0)"));
	FLAGS_SET_READONLY(line2->points->flags, 1);
	ret = ptarray_append_ptarray(line1->points, line2->points, -1);
	CU_ASSERT(ret == LW_SUCCESS);
	wkt = lwgeom_to_text(lwline_as_lwgeom(line1));
	ASSERT_STRING_EQUAL(wkt, "LINESTRING(0 10,10 0,11 0)");
	lwfree(wkt);
	FLAGS_SET_READONLY(line2->points->flags, 0); /* for lwline_free */
	lwline_free(line2);
	lwline_free(line1);

	/* Appending to a read-only pointarray is forbidden */
	line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 10, 10 0)"));
	line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(10 0,11 0)"));
	FLAGS_SET_READONLY(line1->points->flags, 1);
	ret = ptarray_append_ptarray(line1->points, line2->points, -1);
	CU_ASSERT(ret == LW_FAILURE);
	lwline_free(line2);
	FLAGS_SET_READONLY(line1->points->flags, 0); /* for lwline_free */
	lwline_free(line1);

}

static void test_ptarray_locate_point(void)
{
	LWLINE *line;
	double loc, dist;
	POINT4D p, l;

	line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 3,20 4)"));

	p = getPoint4d(line->points, 0);
	loc = ptarray_locate_point(line->points, &p, &dist, &l);
	CU_ASSERT_EQUAL(loc, 0);
	CU_ASSERT_EQUAL(dist, 0.0);

	p = getPoint4d(line->points, 1);
	loc = ptarray_locate_point(line->points, &p, &dist, &l);
	CU_ASSERT_EQUAL(loc, 1);
	CU_ASSERT_EQUAL(dist, 0.0);

	p.x = 21; p.y = 4;
	loc = ptarray_locate_point(line->points, &p, &dist, NULL);
	CU_ASSERT_EQUAL(loc, 1);
	CU_ASSERT_EQUAL(dist, 1.0);

	p.x = 0; p.y = 2;
	loc = ptarray_locate_point(line->points, &p, &dist, &l);
	CU_ASSERT_EQUAL(loc, 0);
	CU_ASSERT_EQUAL(dist, 1.0);

	lwline_free(line);
	line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,20 0,40 0)"));

	p.x = 20; p.y = 0;
	loc = ptarray_locate_point(line->points, &p, &dist, &l);
	CU_ASSERT_EQUAL(loc, 0.5);
	CU_ASSERT_EQUAL(dist, 0.0);

	lwline_free(line);
	line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-40 0,0 0,20 0,40 0)"));

	p.x = 20; p.y = 0;
	loc = ptarray_locate_point(line->points, &p, &dist, &l);
	CU_ASSERT_EQUAL(loc, 0.75);
	CU_ASSERT_EQUAL(dist, 0.0);

	lwline_free(line);
	line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING M (0 0 0, 10 0 20)"));

	p.x = 5; p.y = 0;
	loc = ptarray_locate_point(line->points, &p, &dist, &l);
	CU_ASSERT_EQUAL(loc, 0.5);
	CU_ASSERT_EQUAL(dist, 0.0);
	CU_ASSERT_EQUAL(l.m, 10.0);

	lwline_free(line);

}

static void test_ptarray_isccw(void)
{
	LWLINE *line;
	LWPOLY* poly;
	int ccw;

	/* clockwise rectangle */
	line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,0 10,10 10,10 0, 0 0)"));
	ccw = ptarray_isccw(line->points);
	CU_ASSERT_EQUAL(ccw, 0);
	lwline_free(line);

	/* clockwise triangle */
	line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 3,20 4,20 3, 0 3)"));
	ccw = ptarray_isccw(line->points);
	CU_ASSERT_EQUAL(ccw, 0);
	lwline_free(line);

	/* counterclockwise triangle */
	line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 3,20 3,20 4, 0 3)"));
	ccw = ptarray_isccw(line->points);
	CU_ASSERT_EQUAL(ccw, 1);
	lwline_free(line);

	/* counterclockwise narrow ring (see ticket #1302) */
	line = lwgeom_as_lwline(lwgeom_from_hexwkb("01020000000500000000917E9BA468294100917E9B8AEA284137894120A4682941C976BE9F8AEA2841B39ABE1FA46829415ACCC29F8AEA2841C976BE1FA4682941C976BE9F8AEA284100917E9BA468294100917E9B8AEA2841", LW_PARSER_CHECK_NONE));
	ccw = ptarray_isccw(line->points);
	CU_ASSERT_EQUAL(ccw, 1);
	lwline_free(line);

	/* clockwise narrow ring (see ticket #1302) */
	line = lwgeom_as_lwline(lwgeom_from_hexwkb("01020000000500000000917E9BA468294100917E9B8AEA2841C976BE1FA4682941C976BE9F8AEA2841B39ABE1FA46829415ACCC29F8AEA284137894120A4682941C976BE9F8AEA284100917E9BA468294100917E9B8AEA2841", LW_PARSER_CHECK_NONE));
	ccw = ptarray_isccw(line->points);
	CU_ASSERT_EQUAL(ccw, 0);
	lwline_free(line);

	/* Clockwise narrow ring (see ticket #1302) */
	poly = lwgeom_as_lwpoly(lwgeom_from_hexwkb("0103000000010000000500000000917E9BA468294100917E9B8AEA2841C976BE1FA4682941C976BE9F8AEA2841B39ABE1FA46829415ACCC29F8AEA284137894120A4682941C976BE9F8AEA284100917E9BA468294100917E9B8AEA2841", LW_PARSER_CHECK_NONE));
	ccw = ptarray_isccw(poly->rings[0]);
	CU_ASSERT_EQUAL(ccw, 0);
	lwpoly_free(poly);
}

static void test_ptarray_signed_area()
{
	LWLINE *line;
	double area;

	/* parallelogram */
	line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,1 1, 2 1, 1 0, 0 0)"));
	area = ptarray_signed_area(line->points);
	CU_ASSERT_DOUBLE_EQUAL(area, 1.0, 0.0000001);
	lwline_free(line);

	/* square */
	line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,0 2, 2 2, 2 0, 0 0)"));
	area = ptarray_signed_area(line->points);
	CU_ASSERT_DOUBLE_EQUAL(area, 4.0, 0.0000001);
	lwline_free(line);

	/* square backwares*/
	line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,2 0, 2 2, 0 2, 0 0)"));
	area = ptarray_signed_area(line->points);
	//printf("%g\n",area);
	CU_ASSERT_DOUBLE_EQUAL(area, -4.0, 0.0000001);
	lwline_free(line);

}

static void test_ptarray_contains_point()
{
/* int ptarray_contains_point(const POINTARRAY *pa, const POINT2D *pt, int *winding_number) */

	LWLINE *lwline;
	POINTARRAY *pa;
	POINT2D pt;
	int rv;

	lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0, 0 1, 1 1, 1 0, 0 0)"));
	pa = lwline->points;

	/* Point in middle of square */
	pt.x = 0.5;
	pt.y = 0.5;
	rv = ptarray_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_INSIDE);

	/* Point on left edge of square */
	pt.x = 0;
	pt.y = 0.5;
	rv = ptarray_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_BOUNDARY);

	/* Point on top edge of square */
	pt.x = 0.5;
	pt.y = 1;
	rv = ptarray_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_BOUNDARY);

	/* Point on bottom left corner of square */
	pt.x = 0;
	pt.y = 0;
	rv = ptarray_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_BOUNDARY);

	/* Point on top left corner of square */
	pt.x = 0;
	pt.y = 1;
	rv = ptarray_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_BOUNDARY);

	/* Point outside top left corner of square */
	pt.x = -0.1;
	pt.y = 1;
	rv = ptarray_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_OUTSIDE);

	/* Point outside top left corner of square */
	pt.x = 0;
	pt.y = 1.1;
	rv = ptarray_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_OUTSIDE);

	/* Point outside left side of square */
	pt.x = -0.2;
	pt.y = 0.5;
	rv = ptarray_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_OUTSIDE);

	lwline_free(lwline);
	lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0, 1 1, 2 0, 0 0)"));
	pa = lwline->points;

	/* Point outside grazing top of triangle */
	pt.x = 0;
	pt.y = 1;
	rv = ptarray_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_OUTSIDE);

	lwline_free(lwline);
	lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0, 0 4, 1 4, 2 2, 3 4, 4 4, 4 0, 0 0)"));
	pa = lwline->points;

	/* Point outside grazing top of triangle */
	pt.x = 1;
	pt.y = 2;
	rv = ptarray_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_INSIDE);

	/* Point outside grazing top of triangle */
	pt.x = 3;
	pt.y = 2;
	rv = ptarray_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_INSIDE);

	lwline_free(lwline);
}

static void test_ptarrayarc_contains_point()
{
	/* int ptarrayarc_contains_point(const POINTARRAY *pa, const POINT2D *pt) */

	LWLINE *lwline;
	POINTARRAY *pa;
	POINT2D pt;
	int rv;

	/*** Collection of semi-circles surrounding unit square ***/
	lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 -1, -2 0, -1 1, 0 2, 1 1, 2 0, 1 -1, 0 -2, -1 -1)"));
	pa = lwline->points;

	/* Point in middle of square */
	pt.x = 0;
	pt.y = 0;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_INSIDE);

	/* Point in left lobe */
	pt.x = -1.1;
	pt.y = 0.1;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_INSIDE);

	/* Point on boundary of left lobe */
	pt.x = -1;
	pt.y = 0;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_INSIDE);

	/* Point on boundary vertex */
	pt.x = -1;
	pt.y = 1;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_BOUNDARY);

	/* Point outside */
	pt.x = -1.5;
	pt.y = 1.5;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_OUTSIDE);

	/*** Two-edge ring made up of semi-circles (really, a circle) ***/
	lwline_free(lwline);
	lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 0, 0 1, 1 0, 0 -1, -1 0)"));
	pa = lwline->points;

	/* Point outside */
	pt.x = -1.5;
	pt.y = 1.5;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_OUTSIDE);

	/* Point more outside */
	pt.x = 2.5;
	pt.y = 1.5;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_OUTSIDE);

	/* Point more outside */
	pt.x = 2.5;
	pt.y = 2.5;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_OUTSIDE);

	/* Point inside at middle */
	pt.x = 0;
	pt.y = 0;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_INSIDE);

	/* Point inside offset from middle */
	pt.x = 0.01;
	pt.y = 0.01;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_INSIDE);

	/* Point on edge vertex */
	pt.x = 0;
	pt.y = 1;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_BOUNDARY);

	/*** Two-edge ring, closed ***/
	lwline_free(lwline);
	lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(1 6, 6 1, 9 7, 6 10, 1 6)"));
	pa = lwline->points;

	/* Point to left of ring */
	pt.x = 20;
	pt.y = 4;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_OUTSIDE);

	/*** One-edge ring, closed circle ***/
	lwline_free(lwline);
	lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 0, 1 0, -1 0)"));
	pa = lwline->points;

	/* Point inside */
	pt.x = 0;
	pt.y = 0;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_INSIDE);

	/* Point outside */
	pt.x = 0;
	pt.y = 2;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_OUTSIDE);

	/* Point on boundary */
	pt.x = 0;
	pt.y = 1;
	rv = ptarrayarc_contains_point(pa, &pt);
	CU_ASSERT_EQUAL(rv, LW_BOUNDARY);

	/*** Overshort ring ***/
	lwline_free(lwline);
	lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 0, 1 0)"));
	pa = lwline->points;
	cu_error_msg_reset();
	rv = ptarrayarc_contains_point(pa, &pt);
	//printf("%s\n", cu_error_msg);
	ASSERT_STRING_EQUAL("ptarrayarc_contains_point called with even number of points", cu_error_msg);

	/*** Unclosed ring ***/
	lwline_free(lwline);
	lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 0, 1 0, 2 0)"));
	pa = lwline->points;
	cu_error_msg_reset();
	rv = ptarrayarc_contains_point(pa, &pt);
	ASSERT_STRING_EQUAL("ptarrayarc_contains_point called on unclosed ring", cu_error_msg);

	lwline_free(lwline);
}

static void test_ptarray_scale()
{
  LWLINE *line;
  POINTARRAY *pa;
  POINT4D factor;
  const char *wkt;
  char *wktout;

  wkt = "LINESTRING ZM (0 1 2 3,1 2 3 0,-2 -3 0 -1,-3 0 -1 -2)";
  line = lwgeom_as_lwline(lwgeom_from_text(wkt));
  pa = line->points;

  factor.x = factor.y = factor.z = factor.m = 1;
  ptarray_scale(pa, &factor);
  wktout = lwgeom_to_text(lwline_as_lwgeom(line));
  ASSERT_STRING_EQUAL(wktout, wkt);
  lwfree(wktout);

  factor.x = 2;
  wkt = "LINESTRING ZM (0 1 2 3,2 2 3 0,-4 -3 0 -1,-6 0 -1 -2)";
  ptarray_scale(pa, &factor);
  wktout = lwgeom_to_text(lwline_as_lwgeom(line));
  ASSERT_STRING_EQUAL(wktout, wkt);
  lwfree(wktout);

  factor.x = 1; factor.y = 3;
  wkt = "LINESTRING ZM (0 3 2 3,2 6 3 0,-4 -9 0 -1,-6 0 -1 -2)";
  ptarray_scale(pa, &factor);
  wktout = lwgeom_to_text(lwline_as_lwgeom(line));
  ASSERT_STRING_EQUAL(wktout, wkt);
  lwfree(wktout);

  factor.x = 1; factor.y = 1; factor.z = -2;
  wkt = "LINESTRING ZM (0 3 -4 3,2 6 -6 0,-4 -9 0 -1,-6 0 2 -2)";
  ptarray_scale(pa, &factor);
  wktout = lwgeom_to_text(lwline_as_lwgeom(line));
  ASSERT_STRING_EQUAL(wktout, wkt);
  lwfree(wktout);

  factor.x = 1; factor.y = 1; factor.z = 1; factor.m = 2;
  wkt = "LINESTRING ZM (0 3 -4 6,2 6 -6 0,-4 -9 0 -2,-6 0 2 -4)";
  ptarray_scale(pa, &factor);
  wktout = lwgeom_to_text(lwline_as_lwgeom(line));
  ASSERT_STRING_EQUAL(wktout, wkt);
  lwfree(wktout);

  lwline_free(line);
}

static void test_ptarray_scroll()
{
  LWLINE *line;
  POINTARRAY *pa;
  POINT4D scroll;
  const char *wkt;
  char *wktout;
	int rv;

  wkt = "LINESTRING ZM (1 1 1 1,2 2 2 2,3 3 3 3,4 4 4 4,1 1 1 1)";
  line = lwgeom_as_lwline(lwgeom_from_text(wkt));
  pa = line->points;

	scroll.x = scroll.y = scroll.z = scroll.m = 2;
  rv = ptarray_scroll_in_place(pa, &scroll);
	CU_ASSERT_EQUAL(rv, LW_SUCCESS);
  wktout = lwgeom_to_text(lwline_as_lwgeom(line));
  wkt = "LINESTRING ZM (2 2 2 2,3 3 3 3,4 4 4 4,1 1 1 1,2 2 2 2)";
  ASSERT_STRING_EQUAL(wktout, wkt);
  lwfree(wktout);

	scroll.x = scroll.y = scroll.z = scroll.m = 1;
  rv = ptarray_scroll_in_place(pa, &scroll);
	CU_ASSERT_EQUAL(rv, LW_SUCCESS);
  wktout = lwgeom_to_text(lwline_as_lwgeom(line));
  wkt = "LINESTRING ZM (1 1 1 1,2 2 2 2,3 3 3 3,4 4 4 4,1 1 1 1)";
  ASSERT_STRING_EQUAL(wktout, wkt);
  lwfree(wktout);

	scroll.x = scroll.y = scroll.z = scroll.m = 9;
  rv = ptarray_scroll_in_place(pa, &scroll);
	CU_ASSERT_EQUAL(rv, LW_FAILURE);
	ASSERT_STRING_EQUAL(cu_error_msg, "ptarray_scroll_in_place: input POINTARRAY does not contain the given point");

  lwline_free(line);
}

static void test_ptarray_closest_vertex_2d()
{
	LWLINE *line;
	POINTARRAY *pa;
	double dist;
	POINT2D qp;
	const char *wkt;
	int rv;

	wkt = "LINESTRING (0 0 0, 1 0 0, 2 0 0, 3 0 10)";
	line = lwgeom_as_lwline(lwgeom_from_text(wkt));
	pa = line->points;

	qp.x = qp.y = 0;
	rv = ptarray_closest_vertex_2d(pa, &qp, &dist);
	ASSERT_INT_EQUAL(rv, 0);
	ASSERT_DOUBLE_EQUAL(dist, 0);

	qp.x = qp.y = 1;
	rv = ptarray_closest_vertex_2d(pa, &qp, &dist);
	ASSERT_INT_EQUAL(rv, 1);
	ASSERT_DOUBLE_EQUAL(dist, 1);

	qp.x = 5; qp.y = 0;
	rv = ptarray_closest_vertex_2d(pa, &qp, &dist);
	ASSERT_INT_EQUAL(rv, 3);
	ASSERT_DOUBLE_EQUAL(dist, 2);


  lwline_free(line);
}

static void test_ptarray_closest_segment_2d()
{
	LWLINE *line;
	POINTARRAY *pa;
	double dist;
	POINT2D qp;
	const char *wkt;
	int rv;

	wkt = "LINESTRING (0 0 0, 1 0 0, 2 0 0, 3 0 10)";
	line = lwgeom_as_lwline(lwgeom_from_text(wkt));
	pa = line->points;

	qp.x = qp.y = 0;
	rv = ptarray_closest_segment_2d(pa, &qp, &dist);
	ASSERT_INT_EQUAL(rv, 0);
	ASSERT_DOUBLE_EQUAL(dist, 0);

	qp.x = 1;
	rv = ptarray_closest_segment_2d(pa, &qp, &dist);
	ASSERT_INT_EQUAL(rv, 0);
	ASSERT_DOUBLE_EQUAL(dist, 0);

	qp.y = 1;
	rv = ptarray_closest_segment_2d(pa, &qp, &dist);
	ASSERT_INT_EQUAL(rv, 0);
	ASSERT_DOUBLE_EQUAL(dist, 1);

	qp.x = 5; qp.y = 0;
	rv = ptarray_closest_segment_2d(pa, &qp, &dist);
	ASSERT_INT_EQUAL(rv, 2);
	ASSERT_DOUBLE_EQUAL(dist, 2);


	lwline_free(line);

	/* See https://trac.osgeo.org/postgis/ticket/4990 */
	/* Test modified to give more stable results */
	wkt = "LINESTRING(4 31,7 31,7 34,4 34,4 31)";
	line = lwgeom_as_lwline(lwgeom_from_text(wkt));
	pa = line->points;
	qp.x = 7.1; qp.y = 31.1;
	rv = ptarray_closest_segment_2d(pa, &qp, &dist);
	ASSERT_INT_EQUAL(rv, 1);
	lwline_free(line);
}

static void test_ptarray_closest_point_on_segment(void)
{
	POINT4D s0, s1, qp, cp;

	s0.x = s0.y = 0; s0.z = 10; s0.m = 20;
	s1.x = 0; s1.y = 10; s1.z = 0; s1.m = 10;

	/* Closest is bottom point */

	qp.x = -0.1; qp.y = 0;
	closest_point_on_segment(&qp, &s0, &s1, &cp);
	ASSERT_DOUBLE_EQUAL(cp.x, 0);
	ASSERT_DOUBLE_EQUAL(cp.y, 0);
	ASSERT_DOUBLE_EQUAL(cp.z, 10);
	ASSERT_DOUBLE_EQUAL(cp.m, 20);

	qp.x = 0.1; qp.y = 0;
	closest_point_on_segment(&qp, &s0, &s1, &cp);
	ASSERT_DOUBLE_EQUAL(cp.x, 0);
	ASSERT_DOUBLE_EQUAL(cp.y, 0);
	ASSERT_DOUBLE_EQUAL(cp.z, 10);
	ASSERT_DOUBLE_EQUAL(cp.m, 20);

	qp.x = 0; qp.y = -0.1;
	closest_point_on_segment(&qp, &s0, &s1, &cp);
	ASSERT_DOUBLE_EQUAL(cp.x, 0);
	ASSERT_DOUBLE_EQUAL(cp.y, 0);
	ASSERT_DOUBLE_EQUAL(cp.z, 10);
	ASSERT_DOUBLE_EQUAL(cp.m, 20);

	/* Closest is top point */

	qp.x = 0; qp.y = 10.1;
	closest_point_on_segment(&qp, &s0, &s1, &cp);
	ASSERT_DOUBLE_EQUAL(cp.x, 0);
	ASSERT_DOUBLE_EQUAL(cp.y, 10);
	ASSERT_DOUBLE_EQUAL(cp.z, 0);
	ASSERT_DOUBLE_EQUAL(cp.m, 10);

	qp.x = 0.1; qp.y = 10;
	closest_point_on_segment(&qp, &s0, &s1, &cp);
	ASSERT_DOUBLE_EQUAL(cp.x, 0);
	ASSERT_DOUBLE_EQUAL(cp.y, 10);
	ASSERT_DOUBLE_EQUAL(cp.z, 0);
	ASSERT_DOUBLE_EQUAL(cp.m, 10);

	qp.x = -0.1; qp.y = 10;
	closest_point_on_segment(&qp, &s0, &s1, &cp);
	ASSERT_DOUBLE_EQUAL(cp.x, 0);
	ASSERT_DOUBLE_EQUAL(cp.y, 10);
	ASSERT_DOUBLE_EQUAL(cp.z, 0);
	ASSERT_DOUBLE_EQUAL(cp.m, 10);

	/* Closest is mid point */

	qp.x = 0.1; qp.y = 5;
	closest_point_on_segment(&qp, &s0, &s1, &cp);
	ASSERT_DOUBLE_EQUAL(cp.x, 0);
	ASSERT_DOUBLE_EQUAL(cp.y, 5);
	ASSERT_DOUBLE_EQUAL(cp.z, 5);
	ASSERT_DOUBLE_EQUAL(cp.m, 15);

	qp.x = -0.1; qp.y = 5;
	closest_point_on_segment(&qp, &s0, &s1, &cp);
	ASSERT_DOUBLE_EQUAL(cp.x, 0);
	ASSERT_DOUBLE_EQUAL(cp.y, 5);
	ASSERT_DOUBLE_EQUAL(cp.z, 5);
	ASSERT_DOUBLE_EQUAL(cp.m, 15);

	qp.x = 0.1; qp.y = 2;
	closest_point_on_segment(&qp, &s0, &s1, &cp);
	ASSERT_DOUBLE_EQUAL(cp.x, 0);
	ASSERT_DOUBLE_EQUAL(cp.y, 2);
	ASSERT_DOUBLE_EQUAL(cp.z, 8);
	ASSERT_DOUBLE_EQUAL(cp.m, 18);

	qp.x = -0.1; qp.y = 2;
	closest_point_on_segment(&qp, &s0, &s1, &cp);
	ASSERT_DOUBLE_EQUAL(cp.x, 0);
	ASSERT_DOUBLE_EQUAL(cp.y, 2);
	ASSERT_DOUBLE_EQUAL(cp.z, 8);
	ASSERT_DOUBLE_EQUAL(cp.m, 18);

	qp.x = 0.1; qp.y = 8;
	closest_point_on_segment(&qp, &s0, &s1, &cp);
	ASSERT_DOUBLE_EQUAL(cp.x, 0);
	ASSERT_DOUBLE_EQUAL(cp.y, 8);
	ASSERT_DOUBLE_EQUAL_TOLERANCE(cp.z, 2, 1e-5);
	ASSERT_DOUBLE_EQUAL(cp.m, 12);

	qp.x = -0.1; qp.y = 8;
	closest_point_on_segment(&qp, &s0, &s1, &cp);
	ASSERT_DOUBLE_EQUAL(cp.x, 0);
	ASSERT_DOUBLE_EQUAL(cp.y, 8);
	ASSERT_DOUBLE_EQUAL_TOLERANCE(cp.z, 2, 1e-5);
	ASSERT_DOUBLE_EQUAL(cp.m, 12);


}


/*
** Used by the test harness to register the tests in this file.
*/
void ptarray_suite_setup(void);
void ptarray_suite_setup(void)
{
	CU_pSuite suite = CU_add_suite("ptarray", NULL, NULL);
	PG_ADD_TEST(suite, test_ptarray_append_point);
	PG_ADD_TEST(suite, test_ptarray_append_ptarray);
	PG_ADD_TEST(suite, test_ptarray_locate_point);
	PG_ADD_TEST(suite, test_ptarray_isccw);
	PG_ADD_TEST(suite, test_ptarray_signed_area);
	PG_ADD_TEST(suite, test_ptarray_insert_point);
	PG_ADD_TEST(suite, test_ptarray_contains_point);
	PG_ADD_TEST(suite, test_ptarrayarc_contains_point);
	PG_ADD_TEST(suite, test_ptarray_scale);
	PG_ADD_TEST(suite, test_ptarray_scroll);
	PG_ADD_TEST(suite, test_ptarray_closest_vertex_2d);
	PG_ADD_TEST(suite, test_ptarray_closest_segment_2d);
	PG_ADD_TEST(suite, test_ptarray_closest_point_on_segment);
}
