/*  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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

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

/*#define LINE_ISC_DEBUG 1*/

#include "geo_isc.h"
#include <gengeo2d/cline.h>
#include <gengeo2d/intersect.h>

#ifdef LINE_ISC_DEBUG
#include <stdio.h>
#endif

void grbs_line_bbox(grbs_line_t *l)
{
	grbs_2net_t *tn = NULL;
	double bloat = 0;

	if (l->a1 != NULL) tn = grbs_arc_parent_2net(l->a1);
	if (l->a2 != NULL) tn = grbs_arc_parent_2net(l->a2);
	if (tn != NULL)    bloat = tn->copper + tn->clearance;

	l->bbox.x1 = GRBS_MIN(l->x1, l->x2) - bloat;
	l->bbox.y1 = GRBS_MIN(l->y1, l->y2) - bloat;
	l->bbox.x2 = GRBS_MAX(l->x1, l->x2) + bloat;
	l->bbox.y2 = GRBS_MAX(l->y1, l->y2) + bloat;
}

void grbs_line_reg(grbs_t *grbs, grbs_line_t *l)
{
	grbs_rtree_insert(&grbs->line_tree, l, &l->bbox);
}

static void grbs_line_unreg(grbs_t *grbs, grbs_line_t *l)
{
	grbs_rtree_delete(&grbs->line_tree, l, &l->bbox);
}

void grbs_line_attach(grbs_t *grbs, grbs_line_t *line, grbs_arc_t *arc, int linend)
{
	switch(linend) {
		case 1:
			line->a1 = arc;
			line->x1 = arc->parent_pt->x + cos(arc->sa + arc->da) * arc->r;
			line->y1 = arc->parent_pt->y + sin(arc->sa + arc->da) * arc->r;
			arc->eline = line;
			break;
		case 2:
			line->a2 = arc;
			line->x2 = arc->parent_pt->x + cos(arc->sa) * arc->r;
			line->y2 = arc->parent_pt->y + sin(arc->sa) * arc->r;
			arc->sline = line;
			break;
		default:
			abort();
	}
}

static grbs_line_t *grbs_line_create(grbs_t *grbs, grbs_arc_t *arc)
{
	grbs_arc_t *prev;
	grbs_line_t *l;

	for(prev = arc->link_2net.prev; (prev != NULL) && prev->vconcave; prev = prev->link_2net.prev) ;

	arc->sline = NULL;

	if (prev == NULL)
		return NULL;

	l = grbs_line_new(grbs);
	grbs_line_attach(grbs, l, prev, 1);
	grbs_line_attach(grbs, l, arc, 2);
	grbs_line_bbox(l);
	grbs_line_reg(grbs, l);

	return l;
}

static void grbs_line_del(grbs_t *grbs, grbs_line_t *line)
{
	if (line->a1 != NULL) {
		assert(line->a1->eline == line);
		line->a1->eline = NULL;
	}
	if (line->a2 != NULL) {
		assert(line->a2->sline == line);
		line->a2->sline = NULL;
	}
	grbs_line_unreg(grbs, line);
	grbs_line_free(grbs, line);
}

static void grbs_line_line_collisions(grbs_t *grbs, grbs_2net_t *tn, double x1, double y1, double x2, double y2, double copper, double clearance)
{
	grbs_rtree_box_t bbox;
	grbs_line_t *l;
	grbs_rtree_it_t it;
	g2d_cline_t cl1, cl2;


	bbox.x1 = GRBS_MIN(x1, x2);
	bbox.y1 = GRBS_MIN(y1, y2);
	bbox.x2 = GRBS_MAX(x1, x2);
	bbox.y2 = GRBS_MAX(y1, y2);

	cl1.p1.x = x1; cl1.p1.y = y1;
	cl1.p2.x = x2; cl1.p2.y = y2;

	for(l = grbs_rtree_first(&it, &grbs->line_tree, &bbox); l != NULL; l = grbs_rtree_next(&it)) {
		cl2.p1.x = l->x1; cl2.p1.y = l->y1;
		cl2.p2.x = l->x2; cl2.p2.y = l->y2;

		if ((l->x1 == x1) && (l->y1 == y1)) continue;
		if ((l->x1 == x2) && (l->y1 == y2)) continue;
		if ((l->x2 == x1) && (l->y2 == y1)) continue;
		if ((l->x2 == x2) && (l->y2 == y2)) continue;

		if (g2d_isc_cline_cline(&cl1, &cl2)) {
			if ((grbs->coll_ingore_tn_line != NULL) && (grbs->coll_ingore_tn_line(grbs, tn, l)))
				continue;
		

#ifdef LINE_ISC_DEBUG
			g2d_vect_t ip[2];
			g2d_offs_t offs[2];
			g2d_iscp_cline_cline(&cl1, &cl2, ip, offs);
			FILE *f;
			printf("line-line isc at %f;%f!\n", ip[0], ip[0]);

			f = fopen("Line-line.svg", "w");
			grbs_draw_begin(grbs, f);
			grbs_draw_points(grbs, f);
			grbs_draw_wires(grbs, f);
			grbs_draw_end(grbs, f);
			fclose(f);
#endif
			vtp0_append(&grbs->collobjs, l);
		}
	}
}


static void grbs_line_arc_collisions(grbs_t *grbs, grbs_2net_t *tn, double x1, double y1, double x2, double y2, double copper, double clearance)
{
	double bloat = copper + clearance;
	grbs_rtree_box_t bbox;
	grbs_arc_t *a;
	grbs_rtree_it_t it;
	g2d_sline_t sline, sline_cop;
	g2d_sarc_t sarc;


	bbox.x1 = GRBS_MIN(x1, x2) - bloat;
	bbox.y1 = GRBS_MIN(y1, y2) - bloat;
	bbox.x2 = GRBS_MAX(x1, x2) + bloat;
	bbox.y2 = GRBS_MAX(y1, y2) + bloat;

	sline.c.p1.x = x1; sline.c.p1.y = y1;
	sline.c.p2.x = x2; sline.c.p2.y = y2;
	sline.s.cap = G2D_CAP_ROUND;
	sline_cop = sline;

	sline.s.width = bloat + bloat - 0.001; /* leave a margin of 1 um so the test is exclusive */
	sline_cop.s.width = copper + copper - 0.001; /* leave a margin of 1 um so the test is exclusive */

	for(a = grbs_rtree_first(&it, &grbs->arc_tree, &bbox); a != NULL; a = grbs_rtree_next(&it)) {
		grbs_2net_t *atn = grbs_arc_parent_2net(a);

		if (!a->in_use || (a->r == 0)) continue;
		if (atn == tn) continue; /* allow self intesections for now */

		sarc.c.c.x = a->parent_pt->x; sarc.c.c.y = a->parent_pt->y;
		sarc.c.r = a->r;
		sarc.c.start = a->sa; sarc.c.delta = a->da;
		sarc.s.width = tn->copper + tn->copper + tn->clearance + tn->clearance - 0.001; /* leave a margin of 1 um so the test is exclusive */
		sarc.s.cap = G2D_CAP_ROUND;

		/* line copper vs. arc clearance */
		if (g2d_isc_sline_sarc(&sline_cop, &sarc)) {
			if ((grbs->coll_ingore_tn_arc != NULL) && (grbs->coll_ingore_tn_arc(grbs, tn, a)))
				continue;
			vtp0_append(&grbs->collobjs, a);
			continue;
		}

		/* arc copper vs. line clearance */
		sarc.s.width = tn->copper + tn->copper - 0.001; /* leave a margin of 1 um so the test is exclusive */
		if (g2d_isc_sline_sarc(&sline, &sarc)) {
			if ((grbs->coll_ingore_tn_arc != NULL) && (grbs->coll_ingore_tn_arc(grbs, tn, a)))
				continue;
			vtp0_append(&grbs->collobjs, a);
			continue;
		}
	}
}

static grbs_point_t *grbs_line_point_collision(grbs_t *grbs, grbs_2net_t *tn, double x1, double y1, double x2, double y2, double copper, double clearance, const grbs_point_t *ignore1, const grbs_point_t *ignore2)
{
	double bloat = copper + clearance;
	grbs_rtree_box_t bbox;
	grbs_point_t *pt;
	grbs_rtree_it_t it;
	g2d_sline_t sline, sline_cop;

	bbox.x1 = GRBS_MIN(x1, x2) - bloat;
	bbox.y1 = GRBS_MIN(y1, y2) - bloat;
	bbox.x2 = GRBS_MAX(x1, x2) + bloat;
	bbox.y2 = GRBS_MAX(y1, y2) + bloat;

	sline.c.p1.x = x1; sline.c.p1.y = y1;
	sline.c.p2.x = x2; sline.c.p2.y = y2;
	sline.s.cap = G2D_CAP_ROUND;
	sline_cop = sline;

	sline.s.width = bloat + bloat - 0.001; /* leave a margin of 1 um so the test is exclusive */
	sline_cop.s.width = copper + copper - 0.001; /* leave a margin of 1 um so the test is exclusive */

	for(pt = grbs_rtree_first(&it, &grbs->point_tree, &bbox); pt != NULL; pt = grbs_rtree_next(&it)) {
		g2d_vect_t ptc;

		if ((pt == ignore1) || (pt == ignore2)) continue;
		if ((grbs->coll_ingore_tn_point != NULL) && (grbs->coll_ingore_tn_point(grbs, tn, pt))) continue;

		ptc.x = pt->x; ptc.y = pt->y;

		/* line copper vs. point clearance */
		if (g2d_isc_sline_circle(&sline_cop, ptc, pt->copper + pt->clearance - 0.001))
			return pt;

		/* point copper vs. line clearance */
		if (g2d_isc_sline_circle(&sline, ptc, pt->copper - 0.001))
			return pt;
	}

	return NULL;
}
