/*
 * Copyright (C) 2015 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

/*
 * For info about svg see:
 * http://www.w3.org/TR/SVG11/
 */

#define DEBUG   0

#define COLOR_WARN		0xff,0x00,0x00
#define COLOR_GENERIC		0x00,0xff,0x00
#define COLOR_GENERIC_NAME	0x00,0x80,0x00
#define COLOR_PORT		0x00,0xff,0x00
#define COLOR_PORT_NAME		0x00,0x80,0x00
#define COLOR_SIGNAL		0x00,0x00,0xff
#define COLOR_SIGNAL_NAME	0x00,0x00,0x80
#define COLOR_COMP		0x00,0x00,0x00
#define COLOR_COMP_NAME		0x00,0x00,0x00

#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "xml-schem.h"
#include "xml-svg.h"

char *progname;
int opt_d;
char *inname;

static struct image {
	struct xml_svg *svg;
	double min_x, min_y;
	double max_x, max_y;
} image;

static struct {
	double x, y;
	unsigned int count;
} conn[64*1024];
static unsigned int nconns;

static void
image_open(void)
{
	image.min_x -= 100;
	image.min_y -= 100;
	image.max_x += 100;
	image.max_y += 100;

	image.svg = xml_svg_alloc(image.max_x - image.min_x,
			image.max_y - image.min_y);
}

static void
image_close(void)
{
	char name[1024];
	FILE *fp;
	int ret;

	strcpy(name, inname);
	*strrchr(name, '.') = '\0';
	strcat(name, ".svg");

	fp = fopen(name, "w");
	assert(fp);

	fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
	xml_svg_write(fp, image.svg);

	ret = fclose(fp);
	assert(0 <= ret);
}

static void
image_text(
	double x,
	double y,
	int align,
	int angle,
	int r, int g, int b,
	const char *str
)
{
#define FONT_SIZE	10
	struct xml_svg *s;

	x -= image.min_x;
	y -= image.min_y;

#if 1	/* Hack. Vertical aligment not working properly. FIXME */
	switch (align) {
	case 0: case 3: case 6:
		y += 0;
		break;
	case 1: case 4: case 7:
		y += FONT_SIZE / 2;
		break;
	case 2: case 5: case 8:
		y += FONT_SIZE;
		break;
	default:
		assert(0); /* Mustn't happen. */
	}
#endif

	s = xml_svg_text_alloc(x, y, str);
	xml_svg_length_set(&s->font_size, 10);
	xml_svg_string_set(&s->font_style, "normal");
	xml_svg_string_set(&s->font_variant, "normal");
	xml_svg_string_set(&s->font_weight, "normal");
	xml_svg_string_set(&s->font_stretch, "normal");
	xml_svg_color_argb_set(&s->stroke, 0xff, r, g, b);
	switch (align) {
	case 0: case 1: case 2:
		xml_svg_string_set(&s->text_anchor, "start");
		// xml_svg_string_set(&s->text_align, "left");
		break;
	case 3: case 4: case 5:
		xml_svg_string_set(&s->text_anchor, "middle");
		// xml_svg_string_set(&s->text_align, "center");
		break;
	case 6: case 7: case 8:
		xml_svg_string_set(&s->text_anchor, "end");
		// xml_svg_string_set(&s->text_align, "right");
		break;
	default:
		assert(0); /* Mustn't happen. */
	}
#if 0 /* See above. */
	switch (align) {
	case 0: case 3: case 6:
		xml_svg_string_set(&s->vertical_align, "baseline");
		break;
	case 1: case 4: case 7:
		xml_svg_string_set(&s->vertical_align, "middle");
		break;
	case 2: case 5: case 8:
		xml_svg_string_set(&s->vertical_align, "top");
		break;
	default:
		assert(0); /* Mustn't happen. */
	}
#endif
	if (angle != 0) {
		xml_svg_transform_rotate(s->transform, s->transform,
				360.0 - angle, x, y);
	}

	xml_svg_append(image.svg, s);
}

static void
image_line(
	double x0,
	double y0,
	double x1,
	double y1,
	int r, int g, int b
)
{
	struct xml_svg *s;

	x0 -= image.min_x;
	y0 -= image.min_y;
	x1 -= image.min_x;
	y1 -= image.min_y;

	s = xml_svg_line_alloc(x0, y0, x1, y1);
	xml_svg_color_argb_set(&s->stroke, 0xff, r, g, b);
	xml_svg_length_set(&s->stroke_width, 1.0);
	xml_svg_string_set(&s->stroke_linecap, "butt");
	
	xml_svg_append(image.svg, s);
}

static void
image_circle(
	double x,
	double y,
	double radius,
	int r, int g, int b,
	int fill
)
{
	struct xml_svg *s;

	x -= image.min_x;
	y -= image.min_y;

	s = xml_svg_circle_alloc(x, y, radius);
	if (fill) {
		xml_svg_color_argb_set(&s->fill, 0xff, r, g, b);
	} else {
		xml_svg_color_argb_set(&s->fill, 0x00, 0, 0, 0x01);
	}
	xml_svg_color_argb_set(&s->stroke, 0xff, r, g, b);
	xml_svg_length_set(&s->stroke_width, 1.0);
	xml_svg_string_set(&s->stroke_linecap, "butt");

	xml_svg_append(image.svg, s);
}

static void
ge_gen(struct xml_schem_ge *ge, const char *type, int r, int g, int b)
{
	for ( ; ge; ge = ge->next) {
		switch (ge->type) {
		case GE_LINE:
			image_line(ge->line.x0, ge->line.y0,
					ge->line.x1, ge->line.y1,
					r, g, b);
			break;

		case GE_BOX:
			image_line(ge->box.x0, ge->box.y0,
					ge->box.x1, ge->box.y0,
					r, g, b);
			image_line(ge->box.x1, ge->box.y0,
					ge->box.x1, ge->box.y1,
					r, g, b);
			image_line(ge->box.x1, ge->box.y1,
					ge->box.x0, ge->box.y1,
					r, g, b);
			image_line(ge->box.x0, ge->box.y1,
					ge->box.x0, ge->box.y0,
					r, g, b);
			break;

		case GE_CIRCLE:
			image_circle(ge->circle.x, ge->circle.y,
					ge->circle.r,
					r, g, b, 0);
			break;

		case GE_TEXT:
			image_text(ge->text.x, ge->text.y,
					ge->text.align, ge->text.angle,
					r, g, b, ge->text.str);
			break;
		}
	}
}

static void
get_box(struct xml_schem *schem)
{
	xml_schem_bbox(schem,
			&image.min_x, &image.min_y,
			&image.max_x, &image.max_y);
}

static void
corner_add(double x, double y)
{
	unsigned int n;

	for (n = 0; ; n++) {
		if (n == nconns) {
			conn[n].x = x;
			conn[n].y = y;
			conn[n].count = 1;
			nconns++;
			break;
		}
		if (conn[n].x == x
		 && conn[n].y == y) {
			conn[n].count++;
			break;
		}
	}
}

static void
sig_end(
	struct xml_schem_ge *ge,
	double x,
	double y,
	double *xp,
	double *yp
)
{
	double d_best = 1e100;
	double x_best = 0.0;
	double y_best = 0.0;
	double line_x0, line_y0;
	double line_x1, line_y1;

	for ( ; ge; ge = ge->next) {
		double dx, dy, d;

		if (ge->type != GE_LINE) continue;

		line_x0 = ge->line.x0;
		line_y0 = ge->line.y0;
		line_x1 = ge->line.x1;
		line_y1 = ge->line.y1;
		
		dx = x - line_x0;
		dy = y - line_y0;
		d = dx*dx + dy*dy;
		if (d < d_best) {
			d_best = d;
			x_best = line_x0;
			y_best = line_y0;
		}
		dx = x - line_x1;
		dy = y - line_y1;
		d = dx*dx + dy*dy;
		if (d < d_best) {
			d_best = d;
			x_best = line_x1;
			y_best = line_y1;
		}
	}
	assert(d_best < 1e99);

	*xp = x_best;
	*yp = y_best;
}

static void
sig_gen(struct xml_schem_signal *sig)
{
	struct xml_schem_ge *ge;

	ge_gen(sig->ge_first, sig->type, COLOR_SIGNAL);

	for (ge = sig->ge_first; ge; ge = ge->next) {
		if (ge->type != GE_LINE) continue;

		corner_add(ge->line.x0, ge->line.y0);
		corner_add(ge->line.x1, ge->line.y1);
	}

	for (ge = sig->ge_first; ge; ge = ge->next) {
		double line_x0, line_y0;

		if (ge->type != GE_TEXT) continue;

		sig_end(sig->ge_first, ge->text.x, ge->text.y,
				&line_x0, &line_y0);
		corner_add(line_x0, line_y0);

		if (opt_d) {
			image_line(ge->text.x, ge->text.y,
					line_x0, line_y0,
					COLOR_SIGNAL_NAME);
		}
	}
}

static void
generic_gen(struct xml_schem_generic *gen)
{
	char str[64*1024];

	strcpy(str, gen->name);
	strcat(str, "=");
	strcat(str, gen->value);

	ge_gen(gen->ge_first, gen->type, COLOR_GENERIC); /* FIXME */
}

static void
port_gen(struct xml_schem_port *port)
{
	struct xml_schem_ge *ge;

	ge_gen(port->ge_first, NULL, COLOR_PORT);

	for (ge = port->ge_first; ge; ge = ge->next) {
		if (ge->type != GE_LINE) continue;

		corner_add(ge->line.x0, ge->line.y0);
		if (ge->line.x0 != ge->line.x1
		 || ge->line.y0 != ge->line.y1) {
			corner_add(ge->line.x1, ge->line.y1);
		}
	}

	for (ge = port->ge_first; ge; ge = ge->next) {
		double line_x0, line_y0;

		if (ge->type != GE_TEXT) continue;

		sig_end(port->ge_first, ge->text.x, ge->text.y,
				&line_x0, &line_y0);
		corner_add(line_x0, line_y0);

		if (opt_d) {
			image_line(ge->text.x, ge->text.y,
					line_x0, line_y0,
					COLOR_PORT_NAME);
		}
	}
}

static void
comp_gen(struct xml_schem_component *comp)
{
	struct xml_schem_port *port;
	struct xml_schem_ge *ge;

	ge_gen(comp->ge_first, comp->type, COLOR_COMP);

	for (port = comp->port_first; port; port = port->next) {
		ge_gen(port->ge_first, NULL, COLOR_COMP);

		for (ge = port->ge_first; ge; ge = ge->next) { 
			if (ge->type != GE_LINE) continue;

			corner_add(ge->line.x0, ge->line.y0);
			if (ge->line.x1 != ge->line.x0
			 || ge->line.y1 != ge->line.y0) {
				corner_add(ge->line.x1, ge->line.y1);
			}
		}
	}

	if (opt_d) {
		double minx, miny;
		double maxx, maxy;
		double x, y;

		xml_schem_component_bbox(comp, &minx, &miny, &maxx, &maxy);
		x = (minx + maxx) / 2.0;
		y = (miny + maxy) / 2.0;

		for (ge = comp->ge_first; ge; ge = ge->next) {
			if (ge->type != GE_TEXT) continue;

			image_line(ge->text.x, ge->text.y,
					x, y,
					COLOR_COMP_NAME);
		}

		for (port = comp->port_first; port; port = port->next) {
			for (ge = port->ge_first; ge; ge = ge->next) {
				if (ge->type != GE_TEXT) continue;

				sig_end(port->ge_first,
						ge->text.x, ge->text.y,
						&x, &y);

				image_line(ge->text.x, ge->text.y,
						x, y,
						COLOR_COMP_NAME);
			}
		}
	}

#if 0
	if (opt_d
	 && ! name) {
		double cx, cy;

		comp_center(xml, &cx, &cy);

		image_text(cx, cy, 4, 0, 0xff, 0x00, 0x00, "No Name");
	}
#endif
}

static void
conn_gen(void)
{
	unsigned int n;

	for (n = 0; n < nconns; n++) {
		if (conn[n].count == 1
		 && opt_d) {
			image_circle(conn[n].x, conn[n].y, 5.0,
					COLOR_WARN, 1);
		} else if (3 <= conn[n].count) {
			image_circle(conn[n].x, conn[n].y, 5.0,
					COLOR_SIGNAL, 1);
		}
	}
}

static void
gen_svg_list(struct xml_schem *schem)
{
	struct xml_schem_generic *gen;
	struct xml_schem_port *port;
	struct xml_schem_signal *sig;
	struct xml_schem_component *comp;

	for (gen = schem->gen_first; gen; gen = gen->next) {
		generic_gen(gen);
	}
	for (port = schem->port_first; port; port = port->next) {
		port_gen(port);
	}
	for (sig = schem->sig_first; sig; sig = sig->next) {
		sig_gen(sig);
	}
	for (comp = schem->comp_first; comp; comp = comp->next) {
		comp_gen(comp);
	}
}

static void
gen_svg(struct xml_schem *schem)
{
	get_box(schem);

	image_open();

	gen_svg_list(schem);
	conn_gen();

	image_close();
}

static void __attribute__((noreturn))
usage(int retval)
{
	fprintf(stderr, "Usage: %s [-d] <*.xml>\n", progname);
	exit(retval);
}

int
main(int argc, char **argv)
{
	int c;
	FILE *fp;
	struct xml_schem *schem;

	/*
	 * Get program name.
	 */
	progname = *argv;

	/*
	 * Get options.
	 */
	while ((c = getopt(argc, argv, "d")) != -1) {
		switch (c) {
		case 'd':
			opt_d = 1;
			break;
		default:
			usage(1);
		}
	}
	argc -= optind;
	argv += optind;

	/*
	 * Get parameter.
	 */
	if (0 < argc) {
		inname = *argv;
		argc--;
		argv++;
	} else {
		usage(1);
	}
	if (argc != 0) {
		usage(1);
	}

	/*
	 * Check parameter.
	 */
	if (! strchr(inname, '.')) {
		usage(1);
	}

	/*
	 * Read xml-schem file.
	 */
	fp = fopen(inname, "r");
	if (! fp) {
		fprintf(stderr, "ERROR: %s: %s: %s.\n", progname,
				inname, strerror(errno));
		exit(1);
	}
	assert(fp);

	schem = xml_schem_read(fp);

	fclose(fp);

	/*
	 * Generate <type>.svg File
	 */
	gen_svg(schem);

	return 0;
}
