/*
 * gEDA - GNU Electronic Design Automation
 * This file is a part of gerbv.
 *
 *   Copyright (C) 2008 Julian Lamb
 *
 * $Id$
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111 USA
 */

/** \file export-rs274x.c
    \brief Functions to export an image to a RS274X file
    \ingroup libgerbv
*/


#include "gerbv.h"

#include <math.h>
#include <glib/gstdio.h>

#include "common.h"

#define dprintf if(DEBUG) printf

#define round(x) floor(x+0.5)

void
export_rs274x_write_macro (FILE *fd, gerbv_aperture_t *currentAperture,
			gint apertureNumber) {
	gerbv_simplified_amacro_t *ls = currentAperture->simplified;

	/* write the macro portion first */
	fprintf(fd, "%%AMMACRO%d*\n",apertureNumber);
	while (ls != NULL) {
		if (ls->type == GERBV_APTYPE_MACRO_CIRCLE) {
			fprintf(fd, "1,%d,%f,%f,%f*\n",(int) ls->parameter[CIRCLE_EXPOSURE],
				ls->parameter[CIRCLE_DIAMETER],ls->parameter[CIRCLE_CENTER_X],
				ls->parameter[CIRCLE_CENTER_Y]);
		}
		else if (ls->type == GERBV_APTYPE_MACRO_OUTLINE) {
			int pointCounter;
			int numberOfPoints = (int) ls->parameter[OUTLINE_NUMBER_OF_POINTS];
			
			/* for Flatcam no new line after this 3 digits */
			fprintf(fd, "4,%d,%d,",(int) ls->parameter[OUTLINE_EXPOSURE],
				numberOfPoints);
			/* add 1 point for the starting point here */
			for (pointCounter=0; pointCounter <= numberOfPoints; pointCounter++) {
			    fprintf(fd, "%f,%f,",ls->parameter[pointCounter * 2 + OUTLINE_FIRST_X],
					   ls->parameter[pointCounter * 2 + OUTLINE_FIRST_Y]);
			}
			fprintf(fd, "%f*\n",ls->parameter[pointCounter * 2 + OUTLINE_FIRST_X]);
		}
		else if (ls->type == GERBV_APTYPE_MACRO_POLYGON) {
			fprintf(fd, "5,%d,%d,%f,%f,%f,%f*\n",(int) ls->parameter[POLYGON_EXPOSURE],
				(int) ls->parameter[POLYGON_NUMBER_OF_POINTS],
				ls->parameter[POLYGON_CENTER_X],ls->parameter[POLYGON_CENTER_Y],
				ls->parameter[POLYGON_DIAMETER],ls->parameter[POLYGON_ROTATION]);
		}
		else if (ls->type == GERBV_APTYPE_MACRO_MOIRE) {
			fprintf(fd, "6,%f,%f,%f,%f,%f,%d,%f,%f,%f*\n",ls->parameter[MOIRE_CENTER_X],
				ls->parameter[MOIRE_CENTER_Y],ls->parameter[MOIRE_OUTSIDE_DIAMETER],
				ls->parameter[MOIRE_CIRCLE_THICKNESS],ls->parameter[MOIRE_GAP_WIDTH],
				(int) ls->parameter[MOIRE_NUMBER_OF_CIRCLES],ls->parameter[MOIRE_CROSSHAIR_THICKNESS],
				ls->parameter[MOIRE_CROSSHAIR_LENGTH],ls->parameter[MOIRE_ROTATION]);
		}
		else if (ls->type == GERBV_APTYPE_MACRO_THERMAL) {
			fprintf(fd, "7,%f,%f,%f,%f,%f,%f*\n",ls->parameter[THERMAL_CENTER_X],
				ls->parameter[THERMAL_CENTER_Y],ls->parameter[THERMAL_OUTSIDE_DIAMETER],
				ls->parameter[THERMAL_INSIDE_DIAMETER],ls->parameter[THERMAL_CROSSHAIR_THICKNESS],
				ls->parameter[THERMAL_ROTATION]);
		}
		else if (ls->type == GERBV_APTYPE_MACRO_LINE20) {
			fprintf(fd, "20,%d,%f,%f,%f,%f,%f,%f*\n",(int) ls->parameter[LINE20_EXPOSURE],
				ls->parameter[LINE20_LINE_WIDTH],ls->parameter[LINE20_START_X],
				ls->parameter[LINE20_START_Y],ls->parameter[LINE20_END_X],
				ls->parameter[LINE20_END_Y],ls->parameter[LINE20_ROTATION]);
		}
		else if (ls->type == GERBV_APTYPE_MACRO_LINE21) {
			fprintf(fd, "21,%d,%f,%f,%f,%f,%f*\n",(int) ls->parameter[LINE21_EXPOSURE],
				ls->parameter[LINE21_WIDTH],ls->parameter[LINE21_HEIGHT],
				ls->parameter[LINE21_CENTER_X],ls->parameter[LINE21_CENTER_Y],
				ls->parameter[LINE21_ROTATION]);
		}
		else if (ls->type == GERBV_APTYPE_MACRO_LINE22) {
			fprintf(fd, "22,%d,%f,%f,%f,%f,%f*\n",(int) ls->parameter[LINE22_EXPOSURE],
				ls->parameter[LINE22_WIDTH],ls->parameter[LINE22_HEIGHT],
				ls->parameter[LINE22_LOWER_LEFT_X],ls->parameter[LINE22_LOWER_LEFT_Y],
				ls->parameter[LINE22_ROTATION]);
		}
		ls = ls->next;
	}
	fprintf(fd, "%%\n");
	/* and finally create an aperture definition to use the macro */
	fprintf(fd, "%%ADD%dMACRO%d*%%\n",apertureNumber,apertureNumber);
}

void
export_rs274x_write_apertures (FILE *fd, gerbv_image_t *image) {
	gerbv_aperture_t *currentAperture;
	gint numberOfRequiredParameters=0,numberOfOptionalParameters=0,i,j;
	
	/* the image should already have been cleaned by a duplicate_image call, so we can safely
	   assume the aperture range is correct */
	for (i=APERTURE_MIN; i<APERTURE_MAX; i++) {
		gboolean writeAperture=TRUE;
		
		currentAperture = image->aperture[i];
		
		if (!currentAperture)
			continue;
		
		switch (currentAperture->type) {
			case GERBV_APTYPE_CIRCLE:
				fprintf(fd, "%%ADD%d",i);
				fprintf(fd, "C,");
				numberOfRequiredParameters = 1;
				numberOfOptionalParameters = 2;
				break;
			case GERBV_APTYPE_RECTANGLE:
				fprintf(fd, "%%ADD%d",i);
				fprintf(fd, "R,");
				numberOfRequiredParameters = 2;
				numberOfOptionalParameters = 2;
				break;
			case GERBV_APTYPE_OVAL:
				fprintf(fd, "%%ADD%d",i);
				fprintf(fd, "O,");
				numberOfRequiredParameters = 2;
				numberOfOptionalParameters = 2;
				break;
			case GERBV_APTYPE_POLYGON:
				fprintf(fd, "%%ADD%d",i);
				fprintf(fd, "P,");
				numberOfRequiredParameters = 2;
				numberOfOptionalParameters = 3;
				break;
			case GERBV_APTYPE_MACRO:
				export_rs274x_write_macro (fd, currentAperture, i);
				writeAperture=FALSE;
				break;
			default:
				writeAperture=FALSE;
				break;
		}
		if (writeAperture) {
			/* write the parameter list */
			for (j=0; j<(numberOfRequiredParameters + numberOfOptionalParameters); j++) {
				if ((j < numberOfRequiredParameters) || (currentAperture->parameter[j] != 0)) {
					/* print the "X" character to separate the parameters */
					if (j>0)
						fprintf(fd, "X");
					fprintf(fd, "%.4f",currentAperture->parameter[j]);
				}
			}
			fprintf(fd, "*%%\n");
		}
	}
}

void
export_rs274x_write_layer_change (gerbv_layer_t *oldLayer, gerbv_layer_t *newLayer, FILE *fd) {
	if (oldLayer->polarity != newLayer->polarity) {
		/* polarity changed */
		if ((newLayer->polarity == GERBV_POLARITY_CLEAR))
			fprintf(fd, "%%LPC*%%\n");
		else
			fprintf(fd, "%%LPD*%%\n");
	}
}

void
export_rs274x_write_state_change (gerbv_netstate_t *oldState, gerbv_netstate_t *newState, FILE *fd) {


}

gboolean
gerbv_export_rs274x_file_from_image (const gchar *filename, gerbv_image_t *inputImage,
		gerbv_user_transformation_t *transform)
{
	const double decimal_coeff = 1e6;
	FILE *fd;
	gerbv_netstate_t *oldState;
	gerbv_layer_t *oldLayer;
	gboolean insidePolygon=FALSE;
	gerbv_user_transformation_t *thisTransform;

	// force gerbv to output decimals as dots (not commas for other locales)
	setlocale(LC_NUMERIC, "C");

	if (transform != NULL) {
		thisTransform = transform;
	} else {
		static gerbv_user_transformation_t identityTransform =
						{0,0,1,1,0,FALSE,FALSE,FALSE};
		thisTransform = &identityTransform;
	}
	if ((fd = g_fopen(filename, "w")) == NULL) {
		GERB_COMPILE_ERROR(_("Can't open file for writing: %s"),
				filename);
		return FALSE;
	}
	
	/* duplicate the image, cleaning it in the process */
	gerbv_image_t *image = gerbv_image_duplicate_image (inputImage, thisTransform);
	
	/* write header info */
	fprintf(fd, "G04 This is an RS-274x file exported by *\n");
	fprintf(fd, "G04 gerbv version %s *\n",VERSION);
	fprintf(fd, "G04 More information is available about gerbv at *\n");
	fprintf(fd, "G04 https://gerbv.github.io/ *\n");
	fprintf(fd, "G04 --End of header info--*\n");
	fprintf(fd, "%%MOIN*%%\n");
	fprintf(fd, "%%FSLAX36Y36*%%\n");
	
	/* check the image info struct for any non-default settings */
	/* image offset */
	if ((image->info->offsetA > 0.0) || (image->info->offsetB > 0.0))
		fprintf(fd, "%%IOA%fB%f*%%\n",image->info->offsetA,image->info->offsetB);
	/* image polarity */
	if (image->info->polarity == GERBV_POLARITY_CLEAR)
		fprintf(fd, "%%IPNEG*%%\n");
	else
		fprintf(fd, "%%IPPOS*%%\n");
	/* image name */
	if (image->info->name)
		fprintf(fd, "%%IN%s*%%\n",image->info->name);
	/* plotter film */
	if (image->info->plotterFilm)
		fprintf(fd, "%%PF%s*%%\n",image->info->plotterFilm);

	/* image rotation */
	if ((image->info->imageRotation != 0.0)
	||  (thisTransform->rotation != 0.0))
		fprintf(fd, "%%IR%d*%%\n",
			(int)(RAD2DEG(image->info->imageRotation +
					thisTransform->rotation))%360);

	if ((image->info->imageJustifyTypeA != GERBV_JUSTIFY_NOJUSTIFY)
	||  (image->info->imageJustifyTypeB != GERBV_JUSTIFY_NOJUSTIFY)) {
		fprintf(fd, "%%IJA");
		if (image->info->imageJustifyTypeA == GERBV_JUSTIFY_CENTERJUSTIFY)
			fprintf(fd, "C");
		else 
			fprintf(fd, "%.4f",image->info->imageJustifyOffsetA);
		fprintf(fd, "B");
		if (image->info->imageJustifyTypeB == GERBV_JUSTIFY_CENTERJUSTIFY)
			fprintf(fd, "C");
		else 
			fprintf(fd, "%.4f",image->info->imageJustifyOffsetB);
		fprintf(fd, "*%%\n");

	}
	/* handle scale user orientation transforms */
	if (fabs(thisTransform->scaleX - 1) > GERBV_PRECISION_LINEAR_INCH
	||  fabs(thisTransform->scaleY - 1) > GERBV_PRECISION_LINEAR_INCH) {
		fprintf(fd, "%%SFA%.4fB%.4f*%%\n",thisTransform->scaleX,thisTransform->scaleY);
	}
	/* handle mirror image user orientation transform */
	if ((thisTransform->mirrorAroundX)||(thisTransform->mirrorAroundY)) {
		fprintf(fd, "%%MIA%dB%d*%%\n",thisTransform->mirrorAroundY,thisTransform->mirrorAroundX);
	}
	
	/* define all apertures */
	fprintf(fd, "G04 --Define apertures--*\n");
	export_rs274x_write_apertures (fd, image);
	
	/* write rest of image */
	fprintf(fd, "G04 --Start main section--*\n");
	gint currentAperture = 0;
	gerbv_net_t *currentNet;
	
	oldLayer = image->layers;
	oldState = image->states;
	/* skip the first net, since it's always zero due to the way we parse things */
	for (currentNet = image->netlist->next; currentNet; currentNet = currentNet->next){
		/* check for "layer" changes (RS274X commands) */
		if (currentNet->layer != oldLayer)
			export_rs274x_write_layer_change (oldLayer, currentNet->layer, fd);
		
		/* check for new "netstate" (more RS274X commands) */
		if (currentNet->state != oldState)
			export_rs274x_write_state_change (oldState, currentNet->state, fd);
		
		/* check for tool changes */
		/* also, make sure the aperture number is a valid one, since sometimes
		   the loaded file may refer to invalid apertures */
		if ((currentNet->aperture != currentAperture)&&
			(image->aperture[currentNet->aperture] != NULL)) {
			fprintf(fd, "G54D%02d*\n",currentNet->aperture);
			currentAperture = currentNet->aperture;
		}
		
		oldLayer = currentNet->layer;
		oldState = currentNet->state;
		
		long xVal,yVal,endX,endY,centerX,centerY;
		switch (currentNet->interpolation) {
			case GERBV_INTERPOLATION_LINEARx1 :
			case GERBV_INTERPOLATION_LINEARx10 :
			case GERBV_INTERPOLATION_LINEARx01 :
			case GERBV_INTERPOLATION_LINEARx001 :
				/* see if we need to write an "aperture off" line to get
				   the pen to the right start point */
				if ((!insidePolygon) && (currentNet->aperture_state == GERBV_APERTURE_STATE_ON)) {
					xVal = (long) round(currentNet->start_x * decimal_coeff);
					yVal = (long) round(currentNet->start_y * decimal_coeff);
					fprintf(fd, "G01X%07ldY%07ldD02*\n",xVal,yVal);
				}
				xVal = (long) round(currentNet->stop_x * decimal_coeff);
				yVal = (long) round(currentNet->stop_y * decimal_coeff);
				fprintf(fd, "G01X%07ldY%07ld",xVal,yVal);
				/* and finally, write the esposure value */
				if (currentNet->aperture_state == GERBV_APERTURE_STATE_OFF)
					fprintf(fd, "D02*\n");
				else if (currentNet->aperture_state == GERBV_APERTURE_STATE_ON)
					fprintf(fd, "D01*\n");
				else
					fprintf(fd, "D03*\n");
				break;
			case GERBV_INTERPOLATION_CW_CIRCULAR :
			case GERBV_INTERPOLATION_CCW_CIRCULAR :
				/* see if we need to write an "aperture off" line to get
				   the pen to the right start point */
				if ((!insidePolygon) && (currentNet->aperture_state == GERBV_APERTURE_STATE_ON)) {
					xVal = (long) round(currentNet->start_x * decimal_coeff);
					yVal = (long) round(currentNet->start_y * decimal_coeff);
					fprintf(fd, "G01X%07ldY%07ldD02*\n",xVal,yVal);
				}
				centerX= (long) round((currentNet->cirseg->cp_x - currentNet->start_x) * decimal_coeff);
				centerY= (long) round((currentNet->cirseg->cp_y - currentNet->start_y) * decimal_coeff);
				endX = (long) round(currentNet->stop_x * decimal_coeff);
				endY = (long) round(currentNet->stop_y * decimal_coeff);
				
				/* always use multi-quadrant, since it's much easier to export */
				/*  and most all software should support it */
				fprintf(fd, "G75*\n");

				if (currentNet->interpolation == GERBV_INTERPOLATION_CW_CIRCULAR)
					fprintf(fd, "G02");	/* Clockwise */
				else
					fprintf(fd, "G03");	/* Counter clockwise */

				/* don't write the I and J values if the exposure is off */
				if (currentNet->aperture_state == GERBV_APERTURE_STATE_ON)
					fprintf(fd, "X%07ldY%07ldI%07ldJ%07ld",endX,endY,centerX,centerY);
				else
					fprintf(fd, "X%07ldY%07ld",endX,endY);
				/* and finally, write the esposure value */
				if (currentNet->aperture_state == GERBV_APERTURE_STATE_OFF)
					fprintf(fd, "D02*\n");
				else if (currentNet->aperture_state == GERBV_APERTURE_STATE_ON)
					fprintf(fd, "D01*\n");
				else
					fprintf(fd, "D03*\n");
				break;
			case GERBV_INTERPOLATION_PAREA_START:
				fprintf(fd, "G36*\n");
				insidePolygon = TRUE;
				break;
			case GERBV_INTERPOLATION_PAREA_END:
				fprintf(fd, "G37*\n");
				insidePolygon = FALSE;
				break;
			default:
				break;
		}
	}
	
	fprintf(fd, "M02*\n");
	
	gerbv_destroy_image (image);
	fclose(fd);
	
	// return to the default locale
	setlocale(LC_NUMERIC, "");
	return TRUE;
}
