/** \file dxfformat.c
* Formating of DXF commands and parameters
*/

/*  XTrkCad - Model Railroad CAD
*  Copyright (C)2017 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 <dynstring.h>
#include "dxfformat.h"

extern char *sProdNameUpper;
extern long units;						   /**< meaning is 0 = English, 1 = metric */

static char *dxfDimensionDefaults[][3] = {
	/**< default values for dimensions, English, metric and DXF variable name */
	{ "1.0", "25.0", "$DIMTXT" },
	{ "0.8", "20.0", "$DIMASZ"}
};

/**
* Build and format layer name. The name is created by appending the layer number
* to the basic layer name.
*
* \param result OUT buffer for result
* \param name IN base part of  name
* \param layer IN layer number
*/

void DxfLayerName(DynString *result, char *name, int layer)
{
	DynStringPrintf(result, DXF_INDENT "8\n%s%d\n", name, layer);
}

/**
* Build and format an integer.
*
* \param result OUT buffer for result
* \param type IN type of integer following DXF specs
* \param value IN position
*/

void DxfFormatInteger(DynString *result, int type, int value)
{
	DynStringPrintf(result, DXF_INDENT "%d\n%d\n", type, value);
}

/**
* Build and format a position. If it specifies a point the value
* is assumed to be in inches and will be converted to millimeters
* if the metric system is active.
* If it is an angle (group codes 50 to 59) it is not converted.
*
* \param result OUT buffer for result
* \param type IN type of position following DXF specs
* \param value IN position
*/

void DxfFormatPosition(DynString *result, int type, double value)
{
	if (units == 1) {
		if( type < 50 || type > 58 ) {
			value *= 25.4;
		}
	}

	DynStringPrintf(result, DXF_INDENT "%d\n%0.6f\n", type, value);
}

/**
* Build and format the line style definition
*
* \param result OUT buffer for result
* \param type IN line style
*/

void DxfLineStyle(DynString *result, int style)
{
	char* s = "CONTINUOUS";
	switch ( style ) {
	case 1: s = "DASHEDTINY"; break;
	case 2: s = "DOTTINY"; break;
	}
	DynStringPrintf(result, DXF_INDENT "6\n%s\n", s);
}

/**
* Build and format layer name. The name is created by appending the layer number
* to the basic layer name. The result is appended to the existing result buffer.
*
* \param output OUT buffer for result
* \param basename IN base part of  name
* \param layer IN layer number
*/

static void
DxfAppendLayerName(DynString *output, int layer)
{
	DynString formatted = NaS;
	DynStringMalloc(&formatted, 0);
	DxfLayerName(&formatted, sProdNameUpper, layer);
	DynStringCatStr(output, &formatted);
	DynStringFree(&formatted);
}

/**
* Build and format a position. The result is appended to the existing result buffer.
*
* \param output OUT buffer for result
* \param type IN type of position following DXF specs
* \param value IN position
*/

static void
DxfAppendPosition(DynString *output, int type, double value)
{
	DynString formatted = NaS;
	DynStringMalloc(&formatted, 0);
	DxfFormatPosition(&formatted, type, value);
	DynStringCatStr(output, &formatted);
	DynStringFree(&formatted);
}

/**
* Build and format an integer. The result is appended to the existing result buffer.
*
* \param output OUT buffer for result
* \param type IN type of integer following DXF specs
* \param value IN position
*/

static void
DxfAppendInteger(DynString *output, int type, int value)
{
	DynString formatted = NaS;
	DynStringMalloc(&formatted, 0);
	DxfFormatInteger(&formatted, type, value);
	DynStringCatStr(output, &formatted);
	DynStringFree(&formatted);
}

/**
* Build and format the line style definition. The result is appended to the existing result buffer.
*
* \param result OUT buffer for result
* \param type IN line style 0 CONTINUOUS, 1 DASHED or 2 DOT
*/

static void
DxfAppendLineStyle(DynString *output, int style)
{
	DynString formatted = NaS;
	DynStringMalloc(&formatted, 0);
	DxfLineStyle(&formatted, style);
	DynStringCatStr(output, &formatted);
	DynStringFree(&formatted);
}

/**
* Format a complete LINE command after DXF spec
*
* \param result OUT buffer for the completed command
* \param layer IN number part of the layer
* \param x0, y0 IN first endpoint
* \param x1, y1 IN second endpoint
* \param style IN line style, TRUE for dashed, FALSE for continuous
* \param color IN line color
*/

void
DxfLineCommand(DynString *result, int layer, double x0,
               double y0, double x1, double y1, int style, int color)
{
	DynStringCatCStr(result, DXF_INDENT "0\nLINE\n");
	DxfAppendLayerName(result, layer);
	DxfAppendPosition(result, 10, x0);
	DxfAppendPosition(result, 20, y0);
	DxfAppendPosition(result, 11, x1);
	DxfAppendPosition(result, 21, y1);
	DxfAppendLineStyle(result, style);
	DxfAppendInteger(result, 420, color);
}

/**
* Format a complete CIRCLE command after DXF spec
*
* \param result OUT buffer for the completed command
* \param layer IN number part of the layer
* \param x, y IN center point
* \param r IN radius
* \param style IN line style, TRUE for dashed, FALSE for continuous
* \param color IN line color
*/

void
DxfCircleCommand(DynString *result, int layer, double x,
                 double y, double r, int style, int color)
{
	DynStringCatCStr(result, DXF_INDENT "0\nCIRCLE\n");
	DxfAppendPosition(result, 10, x);
	DxfAppendPosition(result, 20, y);
	DxfAppendPosition(result, 40, r);
	DxfAppendLayerName(result, layer);
	DxfAppendLineStyle(result, style);
	DxfAppendInteger(result, 420, color);
}

/**
* Format a complete ARC command after DXF spec
*
* \param result OUT buffer for the completed command
* \param layer IN number part of the layer
* \param x, y IN center point
* \param r IN radius
* \param a0 IN starting angle
* \param a1 IN ending angle
* \param style IN line style, TRUE for dashed, FALSE for continuous
* \param color IN line color
*/

void
DxfArcCommand(DynString *result, int layer, double x, double y,
              double r, double a0, double a1, int style, int color)
{
	DynStringCatCStr(result, DXF_INDENT "0\nARC\n");
	DxfAppendPosition(result, 10, x);
	DxfAppendPosition(result, 20, y);
	DxfAppendPosition(result, 40, r);
	DxfAppendPosition(result, 50, a0);
	DxfAppendPosition(result, 51, a0+a1);
	DxfAppendLayerName(result, layer);
	DxfAppendLineStyle(result, style);
	DxfAppendInteger(result, 420, color);
}

/**
* Format a complete TEXT command after DXF spec
*
* \param result OUT buffer for the completed command
* \param layer IN number part of the layer
* \param x, y IN text position
* \param size IN font size
* \param text IN text
* \param color IN text color
*/

void
DxfTextCommand(DynString *result, int layer, double x,
               double y, double size, char *text, int color)
{
	DynStringCatCStr(result, DXF_INDENT "0\nTEXT\n");
	DynStringCatCStrs(result, DXF_INDENT "1\n", text, "\n", NULL);
	DxfAppendPosition(result, 10, x);
	DxfAppendPosition(result, 20, y);
	DxfAppendPosition(result, 40, size/72.0);
	DxfAppendLayerName(result, layer);
	DxfAppendInteger(result, 420, color);
}

/**
 * Append the header lines needed to define the measurement system. This includes the
 * definition of the measurement system (metric or English) vie the $MEASUREMENT variable
 * and the units i.e. inches for English and mm for metric.
 *
 * \PARAM result OUT buffer for the completed command
 */

void
DxfUnits(DynString *result)
{
	char *value;
	DynStringCatCStr(result, DXF_INDENT "9\n$MEASUREMENT\n  70\n");

	if (units == 1) {
		value = "1\n";
	} else {
		value = "0\n";
	}

	DynStringCatCStr(result, value);
	DynStringCatCStr(result, DXF_INDENT "9\n$INSUNITS\n  70\n");

	if (units == 1) {
		value = "4\n";
	} else {
		value = "1\n";
	}

	DynStringCatCStr(result, value);
}

/**
* Define a size of dimensions. The default values are taken from
* static array dxfDimensionDefaults
*
* \PARAM result OUT the completed command is appended to this buffer
* \PARAM dimension IN dimension variable to set
*/

void
DxfDimensionSize(DynString *result, enum DXF_DIMENSIONS dimension )
{
	DynString formatted;
	DynStringMalloc(&formatted, 0);

	DynStringPrintf(&formatted,
	                DXF_INDENT "9\n%s\n  40\n%s\n",
	                dxfDimensionDefaults[dimension][2],
	                dxfDimensionDefaults[dimension][units]);

	DynStringCatStr(result, &formatted);
	DynStringFree(&formatted);
}

/**
* Create the complete prologue for a DXF file. Includes the header section,
* a table for line styles and a table for layers.
*
* \param result OUT buffer for the completed command
* \param layerCount IN count of defined layers
* \param x0, y0 IN minimum (left bottom) position
* \param x1, y1 IN maximum (top right) position
*/

void
DxfPrologue(DynString *result, int layerCount, double x0, double y0, double x1,
            double y1)
{
	int i;
	DynString layer = NaS;
	DynStringMalloc(&layer, 0);
	DynStringCatCStr(result, "\
  0\nSECTION\n\
  2\nHEADER\n\
  9\n$ACADVER\n  1\nAC1009\n");
	DxfUnits(result);
	DxfDimensionSize(result, DXF_DIMTEXTSIZE);
	DxfDimensionSize(result, DXF_DIMARROWSIZE);

	DynStringCatCStr(result, "  9\n$EXTMIN\n");
	DxfAppendPosition(result, 10, x0);
	DxfAppendPosition(result, 20, y0);
	DynStringCatCStr(result, "  9\n$EXTMAX\n");
	DxfAppendPosition(result, 10, x1);
	DxfAppendPosition(result, 20, y1);
	DynStringCatCStr(result, "\
  9\n$TEXTSTYLE\n  7\nSTANDARD\n\
  0\nENDSEC\n\
  0\nSECTION\n\
  2\nTABLES\n\
  0\nTABLE\n\
  2\nLTYPE\n\
  0\nLTYPE\n  2\nCONTINUOUS\n  70\n0\n\
  3\nSolid line\n\
  72\n65\n  73\n0\n  40\n0\n\
  0\nLTYPE\n  2\nDASHED\n  70\n0\n\
  3\n__ __ __ __ __ __ __ __ __ __ __ __ __ __ __\n\
  72\n65\n  73\n2\n  40\n0.15\n  49\n0.1\n  49\n-0.05\n\
  0\nLTYPE\n  2\nDOT\n  70\n0\n\
  3\n...............................................\n\
  72\n65\n  73\n2\n  40\n0.1\n  49\n0\n  49\n-0.05\n\
  0\nENDTAB\n\
  0\nTABLE\n\
  2\nLAYER\n\
  70\n0\n");

	for (i = 0; i < layerCount; i++) {
		DynStringPrintf(&layer,
		                DXF_INDENT"0\nLAYER\n  2\n%s%d\n  6\nCONTINUOUS\n  62\n7\n  70\n0\n",
		                sProdNameUpper, i + 1);
		DynStringCatStr(result, &layer);
	}

	DynStringCatCStr(result, "\
  0\nENDTAB\n\
  0\nENDSEC\n\
  0\nSECTION\n\
  2\nENTITIES\n");
}

/**
* Create the file footer for a DXF file. Closes the open section and places
* an end-of-file marker
*
* \param result OUT buffer for the completed command
*/

void
DxfEpilogue(DynString *result)
{
	DynStringCatCStr(result, DXF_INDENT "0\nENDSEC\n" DXF_INDENT "0\nEOF\n");
}
