%{
/*
 * lang.y - Toolpath adaptation language
 *
 * Written 2010-2012 by Werner Almesberger
 * Copyright 2010-2012 by Werner Almesberger
 *
 * 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.
 */


#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include "path.h"
#include "ops.h"
#include "area.h"
#include "gnuplot.h"
#include "gerber.h"
#include "excellon.h"

#include "y.tab.h"


static double xo = 0, yo = 0, zo = 0;	/* origin */
static double rot = 0;
static struct path *paths = NULL;
static struct path *remain = NULL;


#define MIL2MM(mil)	((mil)/1000*25.4)


static void add_paths(struct path *new)
{
	struct path **anchor = &paths;

	while (*anchor)
		anchor = &(*anchor)->next;
	*anchor = new;
}


static void translate(struct path *list, double x, double y, double z)
{
	struct path *path;
	struct point *p;

	for (path = list; path; path = path->next)
		for (p = path->first; p; p = p->next) {
			p->x += x;
			p->y += y;
			p->z += z;
		}
}


static void rotate(struct path *list, double angle)
{
	double m[2][2], tmp;
	struct point *p;

	angle = angle/180.0*M_PI;
	m[0][0] = cos(angle);
	m[0][1] = -sin(angle);
	m[1][0] = -m[0][1];
	m[1][1] = m[0][0];
	while (list) {
		for (p = list->first; p; p = p->next) {
			tmp = p->x*m[0][0]+p->y*m[0][1];
			p->y = p->x*m[1][0]+p->y*m[1][1];
			p->x = tmp;
		}
		list = list->next;
	}
}


static double ref_pick_1(int ref, double a, double b)
{
	switch (ref) {
	case 0:
		return a;
	case 1:
		return (a+b)/2;
	case 2:
		return b;
	default:
		abort();
	}
}


static void ref_pick_xy(int ref, double xa, double ya, double xb, double yb,
    double *x, double *y)
{
	*x = ref_pick_1((ref-1) % 3, xa, xb);
	*y = ref_pick_1((ref-1)/3, ya, yb);
}


static void bbox(const struct path *path,
    double *xa, double *ya, double *xb, double *yb)
{
	const struct point *p;
	int first = 1;

	*xa = *ya = *xb = *yb = 0;

	while (path) {
		for (p = path->first; p; p = p->next) {
			if (first || p->x < *xa)
				*xa = p->x;
			if (first || p->x > *xb)
				*xb = p->x;
			if (first || p->y < *ya)
				*ya = p->y;
			if (first || p->y > *yb)
				*yb = p->y;
			first = 0;
		}
		path = path->next;
	}
}


static void align(int ref, double x, double y)
{
	double xa = 0, ya = 0, xb = 0, yb = 0;
	double xr, yr, xd, yd;
	int first = 1;

	bbox(paths, &xa, &ya, &xb, &yb);

	ref_pick_xy(ref, xa, ya, xb, yb, &xr, &yr);
	xd = x-xr;
	yd = y-yr;

	translate(paths, xd, yd, 0);
	xo += xd;
	yo += yd;
}


static void clear_paths(void)
{
	struct path *next;

	while (paths) {
		next = paths->next;
		path_free(paths);
		paths = next;
	}
}


static struct path **classify(struct path **anchor, struct path *path)
{
	struct path **walk, *next;

	if (!path)
		return &(*anchor)->next;
	for (walk = &paths; *walk; walk = &(*walk)->next);
	*walk = path;
	next = (*anchor)->next;
	path_free(*anchor);
	*anchor = next;
	return anchor;
}


%}


%union {
	double num;
	char *str;
	int flag;
	enum {
		OO_DOG		= 1 << 0,
		OO_INSIDE	= 1 << 1,
	} oopt;
};


%token		TOK_ALIGN TOK_ARRAY TOK_CLEAR TOK_DRILL TOK_EMPTY TOK_AREA
%token		TOK_MILL TOK_OFFSET TOK_OPTIMIZE TOK_REMAINDER TOK_RESET
%token		TOK_ROTATE TOK_STATS TOK_TRANSLATE TOK_Z
%token		TOK_APPEND TOK_GERBER TOK_GNUPLOT TOK_EXCELLON TOK_WRITE
%token		TOK_DOG TOK_INSIDE TOK_ANY

%token	<num>	NUM_EXP_MIL NUM_EXP_MM NUM_IMP_MIL NUM_IMP_MM REF
%token	<str>	STRING

%type	<str>	opt_filename
%type	<num>	dimen number x_size y_size
%type	<flag>	opt_any
%type	<oopt>	offset_options offset_option

%%

all:
	| command all
	;

command:
	TOK_ALIGN REF dimen dimen
		{
			align((int) $2, $3, $4);
		}
	| TOK_ALIGN REF dimen dimen dimen dimen
		{
			int ref = $2;
			double x, y;

			if ($3 > $5)
				yyerror("left edge > right edge");
			if ($4 > $6)
				yyerror("bottom > top");

			ref_pick_xy(ref, $3, $4, $5, $6, &x, &y);
			align(ref, x, y);
		}
	| TOK_ARRAY x_size y_size number number
		{
			double x = $2*$4;
			double y = $3*$5;

			translate(paths, x, y, 0);
			xo += x;
			yo += y;
		}
	| TOK_CLEAR
		{
			clear_paths();
		}
	| TOK_RESET
		{
			xo = yo = rot = 0;
		}
	| TOK_OFFSET offset_options
		{
			struct path *new;

			new = tool_comp_paths(paths,
			    !!($2 & OO_DOG), !!($2 & OO_INSIDE));
			clear_paths();
			paths = new;
		}
	| TOK_OPTIMIZE
		{
			paths = optimize_paths(paths);
		}
	| TOK_ROTATE number
		{
			rotate(paths, $2);
			rot += $2;
		}
	| TOK_STATS
		{
			path_stats(paths);
		}
	| TOK_TRANSLATE dimen dimen
		{
			translate(paths, $2, $3, 0);
			xo += $2;
			yo += $3;
		}
	| TOK_Z dimen
		{
			zo += $2;
		}
	| TOK_Z dimen dimen
		{
			zo += $3-$2;
		}
	| TOK_GERBER dimen opt_filename
		{
			struct path *new;

			new = gerber_read($3, $2/2);
			rotate(new, rot);
			translate(new, xo, yo, 0);
			add_paths(new);
		}
	| TOK_GNUPLOT dimen opt_filename
		{
			struct path *new;

			new = gnuplot_read($3, $2/2);
			rotate(new, rot);
			translate(new, xo, yo, 0);
			add_paths(new);
		}
	| TOK_EXCELLON opt_filename
		{
			struct path *new;

			new = excellon_read($2);
			rotate(new, rot);
			translate(new, xo, yo, 0);
			add_paths(new);
		}
	| TOK_WRITE opt_filename
		{
			translate(paths, 0, 0, zo);
			gnuplot_write($2, paths);
			translate(paths, 0, 0, -zo);
		}
	| TOK_APPEND opt_filename
		{
			translate(paths, 0, 0, zo);
			gnuplot_append($2, paths);
			translate(paths, 0, 0, -zo);
		}
	| TOK_DRILL dimen opt_comma dimen
		{
			struct path **walk;

			remain = paths;
			paths = NULL;
			walk = &remain;
			while (*walk)
				walk =
				    classify(walk, try_drill(*walk, $2, $4));
		}
	| TOK_AREA dimen opt_comma dimen
		{
			struct path *tmp;

			tmp = area(paths, $2/2, $4);
			clear_paths();
			paths = tmp;
		}
	| TOK_MILL opt_any dimen dimen
		{
			struct path **walk;

			remain = paths;
			paths = NULL;
			walk = &remain;
			while (*walk)
				walk = classify(walk,
				    try_mill(*walk, $3, $4, $2));
		}
	| TOK_REMAINDER
		{
			clear_paths();
			paths = remain;
			remain = NULL;
		}
	| TOK_EMPTY
		{
			if (paths)
				yyerror("path list is not empty");
		}
	;

opt_filename:
		{
			$$ = NULL;
		}
	| STRING
		{
			$$ = $1;
		}
	;

dimen:
	NUM_EXP_MM
		{
			$$ = $1;
		}
	| NUM_IMP_MM
		{
			$$ = $1;
		}
	| NUM_EXP_MIL
		{
			$$ = MIL2MM($1);
		}
	| NUM_IMP_MIL
		{
			$$ = MIL2MM($1);
		}
	;

x_size:
	dimen
		{
			$$ = $1;
		}
	| '+' dimen
		{
			double xa, ya, xb, yb;

			bbox(paths, &xa, &ya, &xb, &yb);
			$$ = xb-xa+$2;
		}
	;

y_size:
	dimen
		{
			$$ = $1;
		}
	| '+' dimen
		{
			double xa, ya, xb, yb;

			bbox(paths, &xa, &ya, &xb, &yb);
			$$ = yb-ya+$2;
		}
	;

number:
	NUM_IMP_MIL
		{
			$$ = $1;
		}
	| NUM_IMP_MM
		{
			$$ = $1;
		}
	;

offset_options:
		{
			$$ = 0;
		}
	| offset_option offset_options
		{
			$$ = $1 | $2;

		}
	;

offset_option:
	TOK_DOG
		{
			$$ = OO_DOG;
		}
	| TOK_INSIDE
		{
			$$ = OO_INSIDE;
		}
	;

opt_comma:
	| ','
	;

opt_any:
		{
			$$ = 0;
		}
	| TOK_ANY
		{
			$$ = 1;
		}
	;