/** \file svgoutput.c
 * Exporting SVG files
*/

/*  XTrkCad - Model Railroad CAD
 *  Copyright (C) 2020 Martin Fischer
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <stdio.h>
#include <string.h>
#include <time.h>
#ifdef WINDOWS
#include <io.h>
#define UTFCONVERT
#else
#include <errno.h>
#endif

#include <xtrkcad-config.h>
#include <locale.h>
#include <mxml.h>
#include <dynstring.h>

#include "cselect.h"
#include "custom.h"
#include "draw.h"
#include "include/svgformat.h"
#include "fileio.h"
#include "i18n.h"
#include "layout.h"
#include "messages.h"
#include "paths.h"
#include "track.h"
#include "include/utf8convert.h"
#include "utility.h"
#include "wlib.h"

static struct wFilSel_t * exportSVGFile_fs;
static coOrd roomSize;

static int svgLineWidth[4] = {10, 10, 20, 30};

/**
 * get line style for element
 *
 * \param  d drawCmd_p to process.
 *
 * \returns the line style
 */

static unsigned
SvgDrawGetLineStyle(drawCmd_p d)
{
	unsigned long notSolid = DC_NOTSOLIDLINE;
	unsigned long opt = d->options & notSolid;
	unsigned lineOpt;

	switch (opt) {
	case DC_DASH:
		lineOpt = wDrawLineDash;
		break;
	case DC_DOT:
		lineOpt = wDrawLineDot;
		break;
	case DC_DASHDOT:
		lineOpt = wDrawLineDashDot;
		break;
	case DC_DASHDOTDOT:
		lineOpt = wDrawLineDashDotDot;
		break;
	case DC_CENTER:
		lineOpt = wDrawLineCenter;
		break;
	case DC_PHANTOM:
		lineOpt = wDrawLinePhantom;
		break;
	default:
		lineOpt = wDrawLineSolid;
		break;
	}

	return (lineOpt);
}

/**
 * Svg draw line
 *
 * \param  d	 A drawCmd_p to process.
 * \param  p0    The p 0.
 * \param  p1    The first coOrd.
 * \param  width The width.
 * \param  color The color.
 */

static void SvgDrawLine(
        drawCmd_p d,
        coOrd p0,
        coOrd p1,
        wDrawWidth width,
        wDrawColor color)
{
	unsigned lineOpt = SvgDrawGetLineStyle(d);

	width = (wDrawWidth)(width >= MINIMUMLINEWIDTH ? width : svgLineWidth[width]);

	SvgLineCommand((SVGParent *)(d->d),
	               p0.x, roomSize.y - p0.y,
	               p1.x, roomSize.y - p1.y,
	               (double)width,
	               wDrawGetRGB(color),
	               lineOpt);
}

/**
 * Svg draw arc
 *
 * \param  d		  A drawCmd_p to process.
 * \param  p		  A coOrd to process.
 * \param  r		  A DIST_T to process.
 * \param  angle0	  The angle 0.
 * \param  angle1	  The first angle.
 * \param  drawCenter The draw center.
 * \param  width	  The width.
 * \param  color	  The color.
 */

static void SvgDrawArc(
        drawCmd_p d,
        coOrd p,
        DIST_T r,
        ANGLE_T angle0,
        ANGLE_T angle1,
        BOOL_T drawCenter,
        wDrawWidth width,
        wDrawColor color)
{
	unsigned lineOpt = SvgDrawGetLineStyle(d);
	wDrawWidth w = (width >= MINIMUMLINEWIDTH ? width : svgLineWidth[width]);

	if (angle1 >= 360.0) {
		SvgCircleCommand((SVGParent *)(d->d),
		                 p.x,
		                 roomSize.y - p.y,
		                 r,
		                 w,
		                 wDrawGetRGB(color),
		                 false,
		                 lineOpt);
	} else {
		SvgArcCommand((SVGParent *)(d->d),
		              p.x,
		              roomSize.y-p.y,
		              r,
		              angle0,
		              angle1,
		              drawCenter,
		              w,
		              wDrawGetRGB(color),
		              lineOpt);
	}

}

/**
 * Svg draw string. Perform conversion to UTF-8 if required.
 *
 * \param 		   d	    A drawCmd_p to process.
 * \param 		   p	    position of text
 * \param 		   a	    text angle
 * \param [in,out] s	    the string
 * \param 		   fp	    font definition (ignored)
 * \param 		   fontSize Size of the font.
 * \param 		   color    color.
 */

static void SvgDrawString(
        drawCmd_p d,
        coOrd p,
        ANGLE_T a,
        char * s,
        wFont_p fp,
        FONTSIZE_T fontSize,
        wDrawColor color)
{
	char *text = MyStrdup(s);

#ifdef UTFCONVERT
	text = Convert2UTF8(text);
#endif // UTFCONVERT

	SvgTextCommand((SVGParent *)(d->d),
	               p.x,
	               roomSize.y - p.y,
	               fontSize,
	               wDrawGetRGB(color),
	               text);

	MyFree(text);
}

/**
 * Svg draw bitmap
 *
 * \param  d	 A drawCmd_p to process.
 * \param  p	 A coOrd to process.
 * \param  bm    The bm.
 * \param  color The color.
 */

static void SvgDrawBitmap(
        drawCmd_p d,
        coOrd p,
        wDrawBitMap_p bm,
        wDrawColor color)
{
}

/**
 * Svg draw fill polygon
 *
 * \param 		   d		 A drawCmd_p to process.
 * \param 		   cnt		 Number of points in polyline.
 * \param [in,out] pts		 the coordinates
 * \param [in,out] pointer   If non-null, the pointer.
 * \param 		   color	 color.
 * \param 		   width	 line width.
 * \param 		   fillStyle fill style.
 */

static void SvgDrawFillPoly(
        drawCmd_p d,
        int cnt,
        coOrd * pts,
        int * pointer,
        wDrawColor color, wDrawWidth width, drawFill_e fillStyle)
{
	int i;
	double *points = malloc((cnt + 1) * 2 * sizeof(double));
	CHECK( points );

	unsigned lineOpt = SvgDrawGetLineStyle(d);

	for (i = 0; i < cnt; i++) {
		points[i * 2] = pts[i].x;
		points[i * 2 + 1] = roomSize.y - pts[i].y;
	}

	if (fillStyle == DRAW_CLOSED || fillStyle == DRAW_FILL) {
		points[i * 2] = points[0];
		points[i * 2 + 1] = points[1];
		cnt++;
	}

	width = (wDrawWidth)(width >= MINIMUMLINEWIDTH ? width : svgLineWidth[width]);
	SvgPolyLineCommand((SVGParent *)(d->d), cnt, points,  wDrawGetRGB(color),
	                   (double)width, fillStyle == DRAW_FILL, lineOpt);

	free(points);
}

/**
 * Svg draw filled circle
 *
 * \param  d	  A drawCmd_p to process.
 * \param  center The center.
 * \param  radius The radius.
 * \param  color  The fill color.
 */

static void SvgDrawFillCircle(drawCmd_p d, coOrd center, DIST_T radius,
                              wDrawColor color)
{
	SvgCircleCommand((SVGParent *)(d->d),
	                 center.x,
	                 roomSize.y - center.y,
	                 radius,
	                 0,
	                 wDrawGetRGB(color),
	                 true,
	                 0);
}

/**
 * Svg draw rectangle
 *
 * \param  d	   A drawCmd_p to process.
 * \param  corner1 The first corner.
 * \param  corner2 The second corner.
 * \param  color   The color.
 * \param  pattern Specifies the pattern.
 */

static void
SvgDrawRectangle(drawCmd_p d, coOrd corner1, coOrd corner2, wDrawColor color,
                 drawFill_e fillOpt)
{
	SvgRectCommand((SVGParent *)(d->d),
	               corner1.x, roomSize.y - corner1.y,
	               corner2.x, roomSize.y - corner2.y,
	               wDrawGetRGB(color),
	               fillOpt);
}

static drawFuncs_t svgDrawFuncs = {
	SvgDrawLine,
	SvgDrawArc,
	SvgDrawString,
	SvgDrawBitmap,
	SvgDrawFillPoly,
	SvgDrawFillCircle,
	SvgDrawRectangle
};

static drawCmd_t svgD = {
	NULL, &svgDrawFuncs, 0, 1.0, 0.0, {0.0,0.0}, {0.0,0.0}, Pix2CoOrd, CoOrd2Pix, 100.0
};

/**
 * Creates valid identifier from a string. Whitespaces are removed
 * and characters are prepended to make sure the i starts with
 * valid chars.
 * https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/id
 *
 * \param [in,out] base the base for the id.
 *
 * \returns Null if it fails, else the new valid identifier.
 */

static char *
CreateValidId(char *base)
{
	const char *idHead = "id";
	char *out = MyMalloc(strlen(idHead) + strlen(base) + 1);
	char *tmp;
	int j;

	strcpy(out, idHead);
	j = strlen(out);

	for (unsigned int i = 0; i < strlen(base); i++) {
		if (isblank(base[i])) {
			i++;
		} else {
			out[j++] = base[i];
		}
	}

	out[j] = '\0';

	// strip off the extension
	tmp = strchr(out, '.');
	if (tmp) {
		*tmp = '\0';
	}
	return (out);
}

/**
 * get a valid identifier for SVG export
 *
 * \returns Null if it fails, else a pointer to a char.
 */

static char * SvgGetId(void)
{
	char *fileName = GetLayoutFilename();
	char *id = NULL;

	if (fileName) {
		id = CreateValidId(fileName);
#ifdef UTFCONVERT
		id = Convert2UTF8(id);
#endif
	}

	return (id);
}

/**
 * Set title for SVG file
 * The first title line of the design is used and stored in the SVG file
 *
 * \param  d A drawCmd_p to process.
 */

static void SvgSetTitle(drawCmd_p d)
{
	char *tmp = GetLayoutTitle();
	char *title;

	if (tmp) {
		title = MyStrdup(tmp);
#ifdef UTFCONVERT
		title = Convert2UTF8(title);
#endif
		SvgAddTitle((SVGParent *)(d->d), title);
		MyFree(title);
	}

}

/**
 * Executes the export tracks to SVG operation
 *
 * \param 		   cnt	    Number of filenames, has to be 1
 * \param [in]     fileName filename of the export file.
 * \param [in]     data	    If non-null, the data.
 *
 * \returns TRUE on success, FALSE on failure
 */

static int DoExportSVGTracks(
        int cnt,
        char ** fileName,
        void * data)
{
	DynString command = NaS;
	SVGDocument *svg;
	SVGParent *svgData;
	BOOL_T all = (selectedTrackCount == 0);
	char *id;

	CHECK(fileName != NULL);
	CHECK(cnt == 1);

	SetCLocale();
	GetLayoutRoomSize(&roomSize);

	SetCurrentPath(SVGPATHKEY, fileName[ 0 ]);

	svg = SvgCreateDocument();
	id = SvgGetId();
	svgData = SvgPrologue(svg, id, 0, 0.0, 0.0, roomSize.x, roomSize.y);
	MyFree(id);

	wSetCursor(mainD.d, wCursorWait);
//    time(&clock);

	svgD.d = (wDraw_p)svgData;

	DrawSelectedTracks(&svgD,all);
	SvgAddCSSStyle((SVGParent *)svgD.d);
	SvgSetTitle(&svgD);						// make sure this is the last element

	if (!SvgSaveFile(svg, fileName[0])) {
		NoticeMessage(MSG_OPEN_FAIL, _("Cancel"), NULL, "SVG", fileName[0],
		              strerror(errno));

		SvgDestroyDocument(svg);
		wSetCursor(mainD.d, wCursorNormal);
		SetUserLocale();
		return FALSE;
	}
	SvgDestroyDocument(svg);
	Reset();	/**<TODO: was tut das? */
	wSetCursor(mainD.d, wCursorNormal);
	SetUserLocale();
	return TRUE;
}

/**
 * Create and show the dialog for selecting the DXF export filename
 */

void DoExportSVG(void * unused)
{
	// CHECK(selectedTrackCount > 0);

	if (exportSVGFile_fs == NULL)
		exportSVGFile_fs = wFilSelCreate(mainW, FS_SAVE, 0, _("Export to SVG"),
		                                 sSVGFilePattern, DoExportSVGTracks, NULL);

	wFilSelect(exportSVGFile_fs, GetCurrentPath(SVGPATHKEY));
}


