/**********************************************************************
 *
 * PostGIS - Spatial Types for PostgreSQL
 * http://postgis.net
 * Copyright 2008 Kevin Neufeld
 *
 * This is free software; you can redistribute and/or modify it under
 * the terms of the GNU General Public Licence. See the COPYING file.
 *
 * This program will generate a .png image for every .wkt file specified
 * in this directory's Makefile.  Every .wkt file may contain several
 * entries of geometries represented as WKT strings.  Every line in
 * a wkt file is stylized using a predetermined style (line thinkness,
 * fill color, etc) currently hard coded in this programs main function.
 *
 * In order to generate a png file, ImageMagicK must be installed in the
 * user's path as system calls are invoked to "convert".  In this manner,
 * WKT files are converted into SVG syntax and rasterized as png.  (PostGIS's
 * internal SVG methods could not be used dues to syntax issues with ImageMagick)
 *
 * The goal of this application is to dynamically generate all the spatial
 * pictures used in PostGIS's documentation pages.
 *
 * Note: the coordinates of the supplied geometries should be within and scaled
 * to the x-y range of [0,200], otherwise the rendered geometries may be
 * rendered outside of the generated image's extents, or may be rendered too
 * small to be recognizable as anything other than a single point.
 *
 **********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h> /* for WEXITSTATUS */

#include "liblwgeom_internal.h"
#include "lwgeom_log.h"
#include "styles.h"

#define SHOW_DIGS_DOUBLE 15
#define MAX_DOUBLE_PRECISION 15
#define MAX_DIGS_DOUBLE (SHOW_DIGS_DOUBLE + 2) /* +2 for dot and sign */

// Some global styling variables
char *imageSize = "200x200";

int getStyleName(char **styleName, char* line);

static void
checked_system(const char* cmd)
{
  int ret = system(cmd);
	if ( WEXITSTATUS(ret) != 0 ) {
		fprintf(stderr, "Failure return code (%d) from command: %s", WEXITSTATUS(ret), cmd);
	}
}

/**
 * Writes the coordinates of a POINTARRAY to a char* where ordinates are
 * separated by a comma and coordinates by a space so that the coordinate
 * pairs can be interpreted by ImageMagick's SVG draw command.
 *
 * @param output a reference to write the POINTARRAY to
 * @param pa a reference to a POINTARRAY
 * @return the numbers of character written to *output
 */
static size_t
pointarrayToString(char *output, POINTARRAY *pa)
{
	char x[OUT_DOUBLE_BUFFER_SIZE];
	char y[OUT_DOUBLE_BUFFER_SIZE];
	int i;
	char *ptr = output;

	for ( i=0; i < pa->npoints; i++ )
	{
		POINT2D pt;
		getPoint2d_p(pa, i, &pt);

		lwprint_double(pt.x, 10, x);
		lwprint_double(pt.y, 10, y);

		if ( i ) ptr += sprintf(ptr, " ");
		ptr += sprintf(ptr, "%s,%s", x, y);
	}

	return (ptr - output);
}

/**
 * Serializes a LWPOINT to a char*.  This is a helper function that partially
 * writes the appropriate draw and fill commands used to generate an SVG image
 * using ImageMagick's "convert" command.

 * @param output a char reference to write the LWPOINT to
 * @param lwp a reference to a LWPOINT
 * @return the numbers of character written to *output
 */
static size_t
drawPoint(char *output, LWPOINT *lwp, LAYERSTYLE *styles)
{
	char x[OUT_DOUBLE_BUFFER_SIZE];
	char y1[OUT_DOUBLE_BUFFER_SIZE];
	char y2[OUT_DOUBLE_BUFFER_SIZE];
	char *ptr = output;
	POINTARRAY *pa = lwp->point;
	POINT2D p;
	getPoint2d_p(pa, 0, &p);

	LWDEBUGF(4, "%s", "drawPoint called");
	LWDEBUGF( 4, "point = %s", lwgeom_to_ewkt((LWGEOM*)lwp) );

	lwprint_double(p.x, 10, x);
	lwprint_double(p.y, 10, y1);
	lwprint_double(p.y + styles->pointSize, 10, y2);

	ptr += sprintf(ptr, "-fill %s -strokewidth 0 ", styles->pointColor);
	ptr += sprintf(ptr, "-draw \"circle %s,%s %s,%s", x, y1, x, y2);
	ptr += sprintf(ptr, "'\" ");

	return (ptr - output);
}

/**
 * Serializes a LWLINE to a char*.  This is a helper function that partially
 * writes the appropriate draw and stroke commands used to generate an SVG image
 * using ImageMagick's "convert" command.

 * @param output a char reference to write the LWLINE to
 * @param lwl a reference to a LWLINE
 * @return the numbers of character written to *output
 */
static size_t
drawLineString(char *output, LWLINE *lwl, LAYERSTYLE *style)
{
	char *ptr = output;

	LWDEBUGF(4, "%s", "drawLineString called");
	LWDEBUGF( 4, "line = %s", lwgeom_to_ewkt((LWGEOM*)lwl) );

	ptr += sprintf(ptr, "-fill none -stroke %s -strokewidth %d ", style->lineColor, style->lineWidth);
	ptr += sprintf(ptr, "-draw \"stroke-linecap round stroke-linejoin round path 'M ");
	ptr += pointarrayToString(ptr, lwl->points );
	ptr += sprintf(ptr, "'\" ");

	return (ptr - output);
}

/**
 * Serializes a LWPOLY to a char*.  This is a helper function that partially
 * writes the appropriate draw and fill commands used to generate an SVG image
 * using ImageMagick's "convert" command.

 * @param output a char reference to write the LWPOLY to
 * @param lwp a reference to a LWPOLY
 * @return the numbers of character written to *output
 */
static size_t
drawPolygon(char *output, LWPOLY *lwp, LAYERSTYLE *style)
{
	char *ptr = output;
	int i;

	LWDEBUGF(4, "%s", "drawPolygon called");
	LWDEBUGF( 4, "poly = %s", lwgeom_to_ewkt((LWGEOM*)lwp) );

	ptr += sprintf(ptr, "-fill %s -stroke %s -strokewidth %d ", style->polygonFillColor, style->polygonStrokeColor, style->polygonStrokeWidth );
	ptr += sprintf(ptr, "-draw \"path '");
	for (i=0; i<lwp->nrings; i++)
	{
		ptr += sprintf(ptr, "M ");
		ptr += pointarrayToString(ptr, lwp->rings[i] );
		ptr += sprintf(ptr, " ");
	}
	ptr += sprintf(ptr, "'\" ");

	return (ptr - output);
}

/**
 * Serializes a LWGEOM to a char*.  This is a helper function that partially
 * writes the appropriate draw, stroke, and fill commands used to generate an
 * SVG image using ImageMagick's "convert" command.

 * @param output a char reference to write the LWGEOM to
 * @param lwgeom a reference to a LWGEOM
 * @return the numbers of character written to *output
 */
static size_t
drawGeometry(char *output, LWGEOM *lwgeom, LAYERSTYLE *styles )
{
	char *ptr = output;
	int i;
	int type = lwgeom->type;

	switch (type)
	{
	case POINTTYPE:
		ptr += drawPoint(ptr, (LWPOINT*)lwgeom, styles );
		break;
	case LINETYPE:
		ptr += drawLineString(ptr, (LWLINE*)lwgeom, styles );
		break;
	case POLYGONTYPE:
		ptr += drawPolygon(ptr, (LWPOLY*)lwgeom, styles );
		break;
	case MULTIPOINTTYPE:
	case MULTILINETYPE:
	case MULTIPOLYGONTYPE:
	case COLLECTIONTYPE:
		for (i=0; i<((LWCOLLECTION*)lwgeom)->ngeoms; i++)
		{
			ptr += drawGeometry( ptr, lwcollection_getsubgeom ((LWCOLLECTION*)lwgeom, i), styles );
		}
		break;
	}

	return (ptr - output);
}

/**
 * Invokes a system call to ImageMagick's "convert" command that adds a drop
 * shadow to the current layer image.
 *
 * @param layerNumber the current working layer number.
static void
addDropShadow(int layerNumber)
{
	char str[512];
	sprintf(
	    str,
	    "convert tmp%d.png -gravity center \"(\" +clone -background navy -shadow 100x3+4+4 \")\" +swap -background none -flatten tmp%d.png",
	    layerNumber, layerNumber);
	LWDEBUGF(4, "%s", str);
	checked_system(str);
}
 */

/**
 * Invokes a system call to ImageMagick's "convert" command that adds a
 * highlight to the current layer image.
 *
 * @param layerNumber the current working layer number.
 */
static void
addHighlight(int layerNumber)
{
	// TODO: change to properly sized string
	char str[512];
	sprintf(
	    str,
	    "convert tmp%d.png \"(\" +clone -channel A -separate +channel -negate -background black -virtual-pixel background -blur 0x3 -shade 120x55 -contrast-stretch 0%% +sigmoidal-contrast 7x50%% -fill grey50 -colorize 10%% +clone +swap -compose overlay -composite \")\" -compose In -composite tmp%d.png",
	    layerNumber, layerNumber);
	LWDEBUGF(4, "%s", str);
	checked_system(str);
}

/**
 * Invokes a system call to ImageMagick's "convert" command that reduces
 * the overall filesize
 *
 * @param filename the current working image.
 */
static void
optimizeImage(char* filename)
{
	char *str;
	str = malloc( (18 + (2*strlen(filename)) + 1) * sizeof(char) );
	sprintf(str, "convert %s -depth 8 %s", filename, filename);
	LWDEBUGF(4, "%s", str);
	checked_system(str);
	free(str);
}

/**
 * Flattens all the temporary processing png files into a single image
 */
static void
flattenLayers(char* filename)
{
	char *str = malloc( (48 + strlen(filename) + 1) * sizeof(char) );
	sprintf(str, "convert tmp[0-9].png -background white -flatten %s", filename);

	LWDEBUGF(4, "%s", str);
	checked_system(str);
	// TODO: only remove the tmp files if they exist.
	remove("tmp0.png");
	remove("tmp1.png");
	remove("tmp2.png");
	remove("tmp3.png");
	remove("tmp4.png");
	remove("tmp5.png");
	remove("tmp6.png");
	remove("tmp7.png");
	remove("tmp8.png");
	remove("tmp9.png");
	free(str);
}


// TODO: comments
int
getStyleName(char **styleName, char* line)
{
	char *ptr = strrchr(line, ';');
	if (ptr == NULL)
	{
		*styleName = malloc( 8 );
		strncpy(*styleName, "Default", 7);
		(*styleName)[7] = '\0';
		return 1;
	}
	else
	{
		*styleName = malloc( ptr - line + 1);
		strncpy(*styleName, line, ptr - line);
		(*styleName)[ptr - line] = '\0';
		LWDEBUGF( 4, "%s", *styleName );
		return 0;
	}
}


/**
 * Main Application.  Currently, drawing styles are hardcoded in this method.
 * Future work may entail reading the styles from a .properties file.
 */
int main( int argc, const char* argv[] )
{
	FILE *pfile;
	LWGEOM *lwgeom;
	char line [2048];
	char *filename;
	int layerCount;
	LAYERSTYLE *styles;
	char *image_path = "../images/";

	getStyles(&styles);

	if ( argc != 2 || strlen(argv[1]) < 3)
	{
		lwerror("You must specify a wkt filename to convert, and it must be 3 or more characters long.\n");
		return -1;
	}

	if ( (pfile = fopen(argv[1], "r")) == NULL)
	{
		perror ( argv[1] );
		return -1;
	}

	filename = malloc( strlen(argv[1]) + strlen(image_path) + 1 );
	strcpy( filename, image_path );
	strncat( filename, argv[1], strlen(argv[1])-3 );
	strncat( filename, "png", 3 );

	printf( "generating %s\n", filename );

	layerCount = 0;
	while ( fgets ( line, sizeof line, pfile ) != NULL && !isspace(*line) )
	{

		char output[32768];
		char *ptr = output;
		char *styleName;
		LAYERSTYLE *style;
		int useDefaultStyle;

		ptr += sprintf( ptr, "convert -size %s xc:none ", imageSize );

		useDefaultStyle = getStyleName(&styleName, line);
		LWDEBUGF( 4, "%s", styleName );

		if (useDefaultStyle)
		{
			printf("   Warning: using Default style for layer %d\n", layerCount);
			lwgeom = lwgeom_from_wkt( line, LW_PARSER_CHECK_NONE );
		}
		else
			lwgeom = lwgeom_from_wkt( line+strlen(styleName)+1, LW_PARSER_CHECK_NONE );
		LWDEBUGF( 4, "geom = %s", lwgeom_to_ewkt((LWGEOM*)lwgeom) );

		style = getStyle(styles, styleName);
		if ( ! style ) {
		  lwerror("Could not find style named %s", styleName);
		  return -1;
		}
		ptr += drawGeometry( ptr, lwgeom, style );

		ptr += sprintf( ptr, "-flip tmp%d.png", layerCount );

		lwfree( lwgeom );

		LWDEBUGF( 4, "%s", output );
		checked_system(output);

		addHighlight( layerCount );
		// addDropShadow( layerCount );
		layerCount++;
		free(styleName);
	}

	flattenLayers(filename);
	optimizeImage(filename);

	fclose(pfile);
	free(filename);
	freeStyles(&styles);
	return 0;
}
