/*  libgrbs - geometric rubber band sketch model
    Copyright (C) 2021  Tibor 'Igor2' Palinkas
    (Supported by NLnet NGI0 PET Fund in 2021)

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA

    Contact:
      Project page: http://repo.hu/projects/libgrbs
      lead developer: http://repo.hu/projects/pcb-rnd/contact.html
*/

#include <math.h>
#include "debug.h"
#include "geo.h"
#include "route.h"

#define COLOR_COPPER_POINT "#A05050"
#define COLOR_CLR_POINT    "#D0A070"
#define COLOR_COPPER_WIRE  "#902020"
#define COLOR_CLR_WIRE     "#907050"
#define COLOR_SECT         "#111111"
#define SECTOR_EXTEND      2

double grbs_draw_zoom = 1.0;
#define ZOOM(v) ((v) * grbs_draw_zoom)

void grbs_draw_begin(grbs_t *grbs, FILE *f)
{
	fprintf(f, "<?xml version=\"1.0\"?>\n");
	fprintf(f, "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.0\">\n");
}

void grbs_draw_end(grbs_t *grbs, FILE *f)
{
	fprintf(f, "</svg>");
}

void grbs_svg_fill_circle(FILE *f, double x, double y, double sr, const char *color)
{
	fprintf(f, "	<circle cx='%f' cy='%f' r='%f' stroke='none' fill='%s'/>\n",
		ZOOM(x), ZOOM(y), ZOOM(sr), color);
}

void grbs_svg_wf_circle(FILE *f, double x, double y, double sr, const char *color)
{
	fprintf(f, "	<circle cx='%f' cy='%f' r='%f' stroke='%s' stroke-width='0.1' fill='none'/>\n",
		ZOOM(x), ZOOM(y), ZOOM(sr), color);
}

void grbs_svg_fill_line(FILE *f, double x1, double y1, double x2, double y2, double sr, const char *color)
{
	fprintf(f, "	<line x1='%f' y1='%f' x2='%f' y2='%f' stroke-width='%f' stroke='%s' stroke-linecap='round'/>\n",
		ZOOM(x1), ZOOM(y1), ZOOM(x2), ZOOM(y2), ZOOM(sr*2.0), color);
}

void grbs_svg_wf_line(FILE *f, double x1, double y1, double x2, double y2, double sr, const char *color)
{
	int sweep = 0;
	double dx, dy, len, vx, vy, nx, ny;

	dx = x2 - x1;
	dy = y2 - y1;
	if ((dx == 0) && (dy == 0)) {
		grbs_svg_wf_circle(f, x1, y1, sr, color);
		return;
	}

	len = sqrt(dx*dx + dy*dy);
	vx = dx / len;
	vy = dy / len;
	nx = -vy;
	ny = vx;

	fprintf(f, "	<path stroke-width='0.1' stroke='%s' stroke-linecap='round' fill='none' d='", color);
	fprintf(f, "M %f %f   L %f %f   A %f %f 0 %d %d %f %f   L %f %f   A %f %f 0 %d %d %f %f",
		ZOOM(x1 + nx * sr), ZOOM(y1 + ny * sr), ZOOM(x2 + nx * sr), ZOOM(y2 + ny * sr), /* line from p1 to p2 */
		ZOOM(sr), ZOOM(sr), 0, sweep, ZOOM(x2 - nx * sr), ZOOM(y2 - ny * sr),           /* arc around p2 */
		ZOOM(x1 - nx * sr), ZOOM(y1 - ny * sr),                                         /* line from p2 to p1 */
		ZOOM(sr), ZOOM(sr), 0, sweep, ZOOM(x1 + nx * sr), ZOOM(y1 + ny * sr)            /* arc around p1 */
		);
	fprintf(f, "'/>\n");
}

void grbs_svg_fill_arc(FILE *f, double cx, double cy, double r, double sa, double da, double sr, const char *color)
{
	double ea = sa + da;
	double x1, y1, x2, y2;
	int large = (fabs(da) > GRBS_PI);
	int sweep = (da > 0.0);

	x1 = cx + cos(sa) * r; y1 = cy + sin(sa) * r;

	if (fabs(da) < 0.0001) {
		grbs_svg_fill_circle(f, x1, y1, sr, color);
		return;
	}

	x2 = cx + cos(ea) * r; y2 = cy + sin(ea) * r;

	fprintf(f, "	<path fill='none' stroke-width='%f' stroke='%s' stroke-linecap='round' d='M %f %f  A %f %f 0 %d %d %f %f'/>\n",
		ZOOM(sr*2.0), color,
		ZOOM(x1), ZOOM(y1), ZOOM(r), ZOOM(r), large, sweep, ZOOM(x2), ZOOM(y2));
}

void grbs_svg_wf_arc(FILE *f, double cx, double cy, double r, double sa, double da, double sr, const char *color)
{
	double ea = sa + da;
	double x1i, y1i, x2i, y2i, x1o, y1o, x2o, y2o, ri, ro;
	double cs, sn;
	int large = (fabs(da) > GRBS_PI);
	int sweep = (da > 0.0);

	ri = r - sr; ro = r + sr;

	if ((fabs(da) < 0.0001) || (ri < 0)) {
		double x1 = cx + cos(sa) * r, y1 = cy + sin(sa) * r;
		grbs_svg_wf_circle(f, x1, y1, sr, color);
		return;
	}


	cs = cos(sa); sn = sin(sa);
	x1i = cx + cs * ri; y1i = cy + sn * ri;
	x1o = cx + cs * ro; y1o = cy + sn * ro;

	cs = cos(ea); sn = sin(ea);
	x2i = cx + cs * ri; y2i = cy + sn * ri;
	x2o = cx + cs * ro; y2o = cy + sn * ro;

	fprintf(f, "	<path fill='none' stroke-width='%f' stroke='%s' stroke-linecap='round' d='",
		ZOOM(0.1), color);

	fprintf(f, "M %f %f  A %f %f 0 %d %d %f %f",
		ZOOM(x1o), ZOOM(y1o), ZOOM(ro), ZOOM(ro), large, sweep, ZOOM(x2o), ZOOM(y2o)); /* outer large arc 1->2 */

	fprintf(f, "  A %f %f 0 %d %d %f %f",
		ZOOM(sr), ZOOM(sr), 0, sweep, ZOOM(x2i), ZOOM(y2i)); /* endcap around 2 */

	fprintf(f, "  A %f %f 0 %d %d %f %f",
		ZOOM(ri), ZOOM(ri), large, !sweep, ZOOM(x1i), ZOOM(y1i)); /* inner large arc 2->1 */

	fprintf(f, "  A %f %f 0 %d %d %f %f",
		ZOOM(sr), ZOOM(sr), 0, sweep, ZOOM(x1o), ZOOM(y1o)); /* endcap around 1 */

	fprintf(f, "'/>\n");
}


void grbs_draw_points(grbs_t *grbs, FILE *f)
{
	grbs_point_t *p;
	int s;
	for(p = gdl_first(&grbs->all_points); p != NULL; p = gdl_next(&grbs->all_points, p)) {
		grbs_svg_fill_circle(f, p->x, p->y, p->copper, COLOR_COPPER_POINT);
		grbs_svg_wf_circle(f, p->x, p->y, p->copper+p->clearance, COLOR_CLR_POINT);
		/* draw sentinels */
		for(s = 0; s < GRBS_MAX_SEG; s++) {
			grbs_arc_t *first = gdl_first(&p->arcs[s]), *last = gdl_last(&p->arcs[s]);
			if (first != NULL) {
				double sx, sy, r = last->r + last->copper + last->clearance + SECTOR_EXTEND;
				sx = p->x + cos(first->sa) * r;
				sy = p->y + sin(first->sa) * r;
				grbs_svg_fill_line(f, p->x, p->y, sx, sy, 0.05, COLOR_SECT);
				sx = p->x + cos(first->sa + first->da) * r;
				sy = p->y + sin(first->sa + first->da) * r;
				grbs_svg_fill_line(f, p->x, p->y, sx, sy, 0.05, COLOR_SECT);

				grbs_svg_fill_arc(f, p->x, p->y, r, first->sa, first->da, 0.05, COLOR_SECT);
			}
		}
	}
}

static const char *arcty(grbs_arc_t *a)
{
	if (a->vconcave) return "vcnc";
	return "cvx";
}

void grbs_dump_point(grbs_point_t *p, FILE *f)
{
	int segi;

	fprintf(f, " pt %f;%f cop;clr=%f;%f\n", p->x, p->y, p->copper, p->clearance);

	for(segi = 0; segi < GRBS_MAX_SEG; segi++) {
		grbs_arc_t *a = gdl_first(&(p->arcs[segi]));
		if (a != NULL) {
			fprintf(f, "  seg convex %d:\n", segi);
			for(; a != NULL; a = gdl_next(&(p->arcs[segi]), a)) {
				grbs_2net_t *tn = grbs_arc_parent_2net(a);
				assert(a->parent_pt == p);
				if (a->in_use) { assert(a->link_2net.parent != NULL); }
				fprintf(f, "   arc %s use=%d %f:%f -> %f [%f %f]", arcty(a), a->in_use, a->r, a->sa, a->da, GRBS_MIN(a->sa, a->sa+a->da), GRBS_MAX(a->sa, a->sa+a->da));
				if (tn != NULL)
					fprintf(f, " tn=%ld", tn->uid);
				if (a->new_in_use)
					fprintf(f, " new: %f:%f -> %f [%f %f]", a->new_r, a->new_sa, a->new_da, GRBS_MIN(a->new_sa, a->new_sa + a->new_da), GRBS_MAX(a->new_sa, a->new_sa + a->new_da));
				fprintf(f, "\n");
			}
		}
	}
}

void grbs_dump_points(grbs_t *grbs, FILE *f)
{
	grbs_point_t *p;

	for(p = gdl_first(&grbs->all_points); p != NULL; p = gdl_next(&grbs->all_points, p))
		grbs_dump_point(p, f);
}

void grbs_dump_test(grbs_t *grbs, FILE *f, double scale)
{
	grbs_point_t *p;
	grbs_2net_t *tn;
	for(p = gdl_first(&grbs->all_points); p != NULL; p = gdl_next(&grbs->all_points, p))
		fprintf(f, "point_new P%ld %.3f %.3f %.3f %.3f\n", p->uid, p->x*scale, p->y*scale, p->copper*scale, p->clearance*scale);

	for(tn = gdl_first(&grbs->all_2nets); tn != NULL; tn = gdl_next(&grbs->all_2nets, tn)) {
		grbs_arc_t *a, *first = gdl_first(&tn->arcs), *last = gdl_last(&tn->arcs);
		fprintf(f, "2net_new n%ld %.3f %.3f from P%ld", tn->uid, tn->copper*scale, tn->clearance*scale, first->parent_pt->uid);

		for(a = gdl_next(&tn->arcs, first); (a != last) && (a != NULL); a = gdl_next(&tn->arcs, a)) {
			const char *dir = a->da > 0 ? "cw" : "ccw"; /* NOTE: this doesn't handle the concave case */
			fprintf(f, " %s P%ld", dir, a->parent_pt->uid);
		}
		fprintf(f, " to P%ld\n", last->parent_pt->uid);
	}
}


static void svg_draw_inc_angle(grbs_t *grbs, FILE *f, grbs_arc_t *a)
{
	double cx, cy, R = 3;

	assert(a->r == 0);

	cx = a->parent_pt->x;
	cy = a->parent_pt->y;

	grbs_svg_fill_line(f, cx, cy, cx + R*cos(a->sa), cy + R*sin(a->sa), 0.05, COLOR_SECT);
}

void grbs_draw_2net(grbs_t *grbs, FILE *f, grbs_2net_t *tn)
{
	grbs_arc_t *a, *first = gdl_first(&tn->arcs);
	double cx, cy;

	for(a = first; a != NULL; a = gdl_next(&tn->arcs, a)) {
		assert(a->in_use);

		cx = a->parent_pt->x;
		cy = a->parent_pt->y;

		if (a != first) {
			grbs_line_t *l = a->sline;
			if (l != NULL) {
				grbs_svg_fill_line(f, l->x1, l->y1, l->x2, l->y2, tn->copper, COLOR_COPPER_WIRE);
				grbs_svg_wf_line(f, l->x1, l->y1, l->x2, l->y2, tn->copper + tn->clearance, COLOR_CLR_WIRE);
			}
		}

		grbs_svg_fill_arc(f, cx, cy, a->r, a->sa, a->da, tn->copper, COLOR_COPPER_WIRE);
		grbs_svg_wf_arc(f, cx, cy, a->r, a->sa, a->da, tn->copper + tn->clearance, COLOR_CLR_WIRE);
	}

	a = gdl_first(&tn->arcs);
	if ((a != NULL) && (a->r == 0))
		svg_draw_inc_angle(grbs, f, a);
	
	a = gdl_last(&tn->arcs);
	if ((a != NULL) && (a->r == 0))
		svg_draw_inc_angle(grbs, f, a);
}

void grbs_draw_wires(grbs_t *grbs, FILE *f)
{
	grbs_2net_t *tn;
	for(tn = gdl_first(&grbs->all_2nets); tn != NULL; tn = gdl_next(&grbs->all_2nets, tn))
		grbs_draw_2net(grbs, f, tn);
}

void grbs_dump_line(grbs_t *grbs, FILE *f, grbs_line_t *l)
{
	double ax, ay;

	fprintf(f, "  line %f;%f .. %f;%f\n", l->x1, l->y1, l->x2, l->y2);

	/* check line endpoint match with arc endpoint match */
	if (l->a1 != NULL) {
		ax = l->a1->parent_pt->x + cos(l->a1->sa + l->a1->da) * l->a1->r;
		ay = l->a1->parent_pt->y + sin(l->a1->sa + l->a1->da) * l->a1->r;
		if ((ax != l->x1) || (ay != l->y1))
			fprintf(f, "   ERROR: line start point mismatch: %f;%f, arc ends at %f;%f\n", l->x1, l->y1, ax, ay);
	}
	if (l->a2 != NULL) {
		ax = l->a2->parent_pt->x + cos(l->a2->sa) * l->a2->r;
		ay = l->a2->parent_pt->y + sin(l->a2->sa) * l->a2->r;
		if ((ax != l->x2) || (ay != l->y2))
			fprintf(f, "   ERROR: line end point mismatch: %f;%f, arc ends at %f;%f\n", l->x2, l->y2, ax, ay);
	}
}

void grbs_dump_2net(grbs_t *grbs, FILE *f, grbs_2net_t *tn)
{
	grbs_arc_t *a, *first = gdl_first(&tn->arcs), *prev = NULL;

	for(a = first; a != NULL; prev = a, a = gdl_next(&tn->arcs, a)) {
		if (a != first) {
			if (a->sline == NULL)
				fprintf(f, "  ERROR: missing sline\n");
			else
				grbs_dump_line(grbs, f, a->sline);
			if (a->sline != prev->eline)
				fprintf(f, "  ERROR: ^^^ sline doesn't match previous arc's eline\n");
		}
		else if (a->sline != NULL)
			fprintf(f, "  ERROR: sline on first arc (shouldn't exist)\n");

		fprintf(f, "  arc %s use=%d center=%f;%f r=%f a=%f;%f\n", arcty(a), a->in_use, a->parent_pt->x, a->parent_pt->y, a->r, a->sa, a->da);
	}

	if ((prev != NULL) && (prev->eline != NULL))
		fprintf(f, "  ERROR: eline on last arc (shouldn't exist)\n");
}

void grbs_dump_wires(grbs_t *grbs, FILE *f)
{
	grbs_2net_t *tn;
	for(tn = gdl_first(&grbs->all_2nets); tn != NULL; tn = gdl_next(&grbs->all_2nets, tn)) {
		fprintf(f, " twonet %ld\n", tn->uid);
		grbs_dump_2net(grbs, f, tn);
	}
}


/* returns 1 if has unused sentinel */
static int grbs_has_unused_sentinel_seg(grbs_t *grbs, grbs_point_t *pt, int cnc, int segi)
{
	grbs_arc_t *seg = gdl_first(&pt->arcs[segi]);

	if ((seg != NULL) && !seg->in_use && !seg->new_in_use && (seg->link_point.next == NULL)) {
		grbs_del_arc(grbs, seg);
		return 1;
	}

	return 0;
}

int grbs_count_unused_sentinel_pt(grbs_t *grbs, grbs_point_t *pt)
{
	int segi, cnc, res = 0;

	for(cnc = 0; cnc < 2; cnc++)
		for(segi = 0; segi < GRBS_MAX_SEG; segi++)
			res += grbs_has_unused_sentinel_seg(grbs, pt, cnc, segi);

	return res;
}

long grbs_count_unused_sentinel(grbs_t *grbs)
{
	grbs_point_t *pt;
	long res = 0;

	for(pt = gdl_first(&grbs->all_points); pt != NULL; pt = gdl_next(&grbs->all_points, pt))
		res += grbs_count_unused_sentinel_pt(grbs, pt);
	return res;
}

/* returns 1 if has any new_in_use */
static int grbs_has_new_seg(grbs_t *grbs, grbs_point_t *pt, int cnc, int segi)
{
	grbs_arc_t *seg;

	for(seg = gdl_first(&pt->arcs[segi]); seg != NULL; seg = gdl_next(&pt->arcs[segi], seg))
		if ((seg != NULL) && seg->new_in_use)
			return 1;

	return 0;
}


int grbs_count_new_pt(grbs_t *grbs, grbs_point_t *pt)
{
	int segi, cnc, res = 0;

	for(cnc = 0; cnc < 2; cnc++)
		for(segi = 0; segi < GRBS_MAX_SEG; segi++)
			res += grbs_has_new_seg(grbs, pt, cnc, segi);

	return res;
}


long grbs_count_new(grbs_t *grbs)
{
	grbs_point_t *pt;
	long res = 0;

	for(pt = gdl_first(&grbs->all_points); pt != NULL; pt = gdl_next(&grbs->all_points, pt))
		res += grbs_count_new_pt(grbs, pt);
	return res;
}

void grbs_sentinel_check(grbs_point_t *pt)
{
	int segi;

	for(segi = 0; segi < GRBS_MAX_SEG; segi++) {
		grbs_arc_t *sent = pt->arcs[segi].first, *a;
		if (sent != NULL) {
			for(a = sent->link_point.next; a != NULL; a = a->link_point.next) {
				if (a->in_use) {
					assert(grbs_angle_in_arc(sent->sa, sent->da, a->sa, 1));
					assert(grbs_angle_in_arc(sent->sa, sent->da, a->sa + a->da, 1));
				}
			}
		}
	}
}

void grbs_sentinel_check_all(grbs_t *grbs)
{
	grbs_point_t *pt;

	for(pt = gdl_first(&grbs->all_points); pt != NULL; pt = gdl_next(&grbs->all_points, pt))
		grbs_sentinel_check(pt);
}

void grbs_line_check(grbs_line_t *line)
{
	if (line->a1 != NULL) {
		assert(line->a1->eline == line);
	}
	if (line->a2 != NULL) {
		assert(line->a2->sline == line);
	}
}

void grbs_line_check_all(grbs_t *grbs)
{
	grbs_line_t *l;

	for(l = gdl_first(&grbs->all_lines); l != NULL; l = gdl_next(&grbs->all_lines, l))
		grbs_line_check(l);
}



void grbs_arc_check(grbs_t *grbs, grbs_arc_t *arc)
{
	if (arc->in_use) {
		double min_r2 = grbs_self_isect_convex_r2(grbs, arc);
		assert(arc->r * arc->r >= min_r2);
	}
}

void grbs_arc_check_all(grbs_t *grbs)
{
	grbs_arc_t *a;

	for(a = gdl_first(&grbs->all_arcs); a != NULL; a = gdl_next(&grbs->all_arcs, a))
		grbs_arc_check(grbs, a);
}

