/*  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 GRBS_ROUTE_ARC_TRACE*/

#undef tprinf
#ifdef GRBS_ROUTE_ARC_TRACE
#include <stdio.h>
#define tprintf printf
#else
#define tprintf grbs_nullprintf
#endif

#include "geo_isc.h"
#include <gengeo2d/carc.h>
#include <gengeo2d/sline.h>
#include <gengeo2d/intersect.h>

void grbs_arc_bbox(grbs_arc_t *a)
{
	g2d_box_t bb;
	g2d_carc_t carc;

	carc.c.x = a->parent_pt->x;
	carc.c.y = a->parent_pt->y;
	carc.r = a->r + a->copper + a->clearance; /* the outmost radius, clearance included */
	carc.start = a->sa; carc.delta = a->da;

	bb = g2d_carc_bbox(&carc);
	a->bbox.x1 = bb.p1.x; a->bbox.y1 = bb.p1.y;
	a->bbox.x2 = bb.p2.x; a->bbox.y2 = bb.p2.y;
}

void grbs_arc_reg(grbs_t *grbs, grbs_arc_t *a)
{
	assert(!a->registered);
	assert(a->link_2net.parent != NULL);
	grbs_rtree_insert(&grbs->arc_tree, a, &a->bbox);
	a->registered = 1;
}

static void grbs_arc_unreg(grbs_t *grbs, grbs_arc_t *a)
{
	long old = grbs->arc_tree.size;
	assert(a->registered);
	grbs_rtree_delete(&grbs->arc_tree, a, &a->bbox);
	if (old > 0) { assert(old > grbs->arc_tree.size); }
	a->registered = 0;
}

static int grbs_isc_sarc_linecop(g2d_sarc_t *sarc, grbs_line_t *line, grbs_2net_t *ltn)
{
	g2d_sline_t sline;
	double lcop = ltn->copper;


	sline.c.p1.x = line->x1; sline.c.p1.y = line->y1;
	sline.c.p2.x = line->x2; sline.c.p2.y = line->y2;
	sline.s.width = lcop + lcop - 0.001; /* leave a margin of 1 um so the test is exclusive */
	sline.s.cap = G2D_CAP_ROUND;

	if (g2d_isc_sline_sarc(&sline, sarc)) {
		tprintf("ISC: sline sarc 1 (%f;%f):\n", sarc->c.c.x, sarc->c.c.y);
		return 1;
	}
	return 0;
}

static int grbs_isc_line_sarc(g2d_sarc_t *sarc, grbs_line_t *line, grbs_2net_t *ltn)
{
	g2d_sline_t sline;
	g2d_cline_t cp, cn;
	double lcop = ltn->copper, lclr = ltn->clearance;


	sline.c.p1.x = line->x1; sline.c.p1.y = line->y1;
	sline.c.p2.x = line->x2; sline.c.p2.y = line->y2;
	sline.s.width = lcop + lcop + lclr + lclr - 0.001; /* leave a margin of 1 um so the test is exclusive */
	sline.s.cap = G2D_CAP_ROUND;

	g2d_sline_sides(&sline, &cp, &cn);
	if (g2d_isc_sline_sarc(&sline, sarc)) {
		tprintf("ISC: sline sarc 2 (%f;%f):\n", sarc->c.c.x, sarc->c.c.y);
		return 1;
	}
	return 0;
}

static grbs_rtree_box_t *get_arc_bb(grbs_2net_t *tn, grbs_arc_t *arc, int new, grbs_rtree_box_t *rbb, g2d_carc_t *carc)
{
	double cop = tn->copper, clr = tn->clearance;

	carc->c.x = arc->parent_pt->x;
	carc->c.y = arc->parent_pt->y;

	if (new) {
		g2d_box_t gb;

		carc->r = arc->new_r + cop + clr; /* the outmost radius, clearance included */
		carc->start = arc->new_sa;
		carc->delta = arc->new_da;

		gb = g2d_carc_bbox(carc);
		rbb->x1 = gb.p1.x; rbb->y1 = gb.p1.y;
		rbb->x2 = gb.p2.x; rbb->y2 = gb.p2.y;
		return rbb;
	}
	else {
		carc->r = arc->r + cop + clr; /* the outmost radius, clearance included */
		carc->start = arc->sa;
		carc->delta = arc->da;
		return &arc->bbox;
	}
}

static void grbs_arc_line_collision(grbs_t *grbs, grbs_2net_t *tn, grbs_arc_t *arc, int new)
{
	g2d_carc_t carc;
	g2d_sarc_t sarc;
	double origs, cops;
	g2d_box_t gb;
	grbs_rtree_box_t rbb, sbb;
	grbs_line_t *l;
	grbs_rtree_it_t it;

	get_arc_bb(tn, arc, new, &rbb, &carc);
	sarc.c = carc;
	sarc.s.cap = G2D_CAP_ROUND;
	cops = 2*tn->copper;
	sarc.s.width = origs = cops + 2*tn->clearance;

	gb = g2d_sarc_bbox(&sarc);
	sbb.x1 = gb.p1.x; sbb.y1 = gb.p1.y;
	sbb.x2 = gb.p2.x; sbb.y2 = gb.p2.y;

	/* check if the outer clearance arc cuts into any line copper */
	for(l = grbs_rtree_first(&it, &grbs->line_tree, &sbb); l != NULL; l = grbs_rtree_next(&it)) {
		grbs_2net_t *ltn = grbs_arc_parent_2net(l->a1);

		if (tn == ltn) continue; /* self-intersection, e.g. line end meets arc */

		/* lines starting or ending around our point are handled by arc angles;
		   they may temporarily intersect while bumping segments up */
		if ((l->a1->parent_pt == arc->parent_pt) || (l->a2->parent_pt == arc->parent_pt))
			continue;

		/* check if any arc clearance cuts into the line's copper */
		if (grbs_isc_sarc_linecop(&sarc, l, ltn)) {
			vtp0_append(&grbs->collobjs, l);
			continue;
		}

	/* check if any line outer clearance cline cuts arc cuts into the arc */
		sarc.s.width = cops;
		if (grbs_isc_line_sarc(&sarc, l, ltn)) {
			vtp0_append(&grbs->collobjs, l);
			continue;
		}

		sarc.s.width = origs;
	}
}

static int grbs_isc_carc_carc(g2d_carc_t *c1, g2d_carc_t *c2, int dbg)
{
	g2d_vect_t ip[2];

	if (g2d_iscp_carc_carc(c1, c2, ip, NULL)) {
		tprintf("ISC: carc carc %d: %f %f\n", dbg, ip[0].x, ip[0].y);
		return 1;
	}
	return 0;
}

static grbs_arc_t *grbs_arc_arc_collision(grbs_t *grbs, grbs_2net_t *tn, grbs_arc_t *arc, int new)
{
	g2d_carc_t carc, ca;
	double clr = tn->clearance, origr;
	grbs_rtree_box_t *bb, rbb;
	grbs_arc_t *a;
	grbs_rtree_it_t it;

	bb = get_arc_bb(tn, arc, new, &rbb, &carc);
	origr = carc.r;

	/* check if the outer clearance arc cuts into any line copper */
	for(a = grbs_rtree_first(&it, &grbs->arc_tree, bb); a != NULL; a = grbs_rtree_next(&it)) {
		grbs_2net_t *atn = grbs_arc_parent_2net(a);

		if (!a->in_use) continue;
		if (tn == atn) continue; /* self-intersection, e.g. line end meets arc */

		/* arcs around our point are handled by angles; they may temporarily
		   intersect while bumping segments up */
		if (a->parent_pt == arc->parent_pt)
			continue;

		ca.c.x = a->parent_pt->x; ca.c.y = a->parent_pt->y;
		ca.start = a->sa; ca.delta = a->da;

		/* check if our arc clearance cuts into the a's copper */
		ca.r = a->r + atn->copper;
		if (grbs_isc_carc_carc(&carc, &ca, 1))
			return a;

		/* check if our arc copper cuts into the a's clearance */
		carc.r -= clr;
		ca.r += atn->clearance;
		if (grbs_isc_carc_carc(&carc, &ca, 2))
			return a;
		carc.r = origr;
	}

	return NULL;
}


static grbs_point_t *grbs_arc_point_collision(grbs_t *grbs, grbs_2net_t *tn, grbs_arc_t *arc, int new)
{
	g2d_carc_t carc, ca;
	double clr = tn->clearance, origr;
	grbs_rtree_box_t *bb, rbb;
	grbs_point_t *p;
	grbs_rtree_it_t it;

	/* incident arc won't ever collide */
	if (new && (arc->new_r == 0))
			return 0;
	if (!new && (arc->r == 0))
		return 0;

	bb = get_arc_bb(tn, arc, new, &rbb, &carc);
	origr = carc.r;

	/* check if arc cuts into any point */
	for(p = grbs_rtree_first(&it, &grbs->point_tree, bb); p != NULL; p = grbs_rtree_next(&it)) {

		/* arcs around our point are handled by angles; they may temporarily
		   intersect while bumping segments up */
		if (arc->parent_pt == p)
			continue;

		if (p->temp_tn == tn) /* we are trying to reach this point, collision is acceptable */
			continue;

		ca.c.x = p->x; ca.c.y = p->y;
		ca.start = 0; ca.delta = 2*GRBS_PI;

		/* check if our arc clearance cuts into the p's copper */
		ca.r = p->copper;
		if (grbs_isc_carc_carc(&carc, &ca, 3))
			return p;

		/* check if our arc copper cuts into the a's clearance */
		carc.r -= clr;
		ca.r += p->clearance;
		if (grbs_isc_carc_carc(&carc, &ca, 4))
			return p;
		carc.r = origr;
	}

	return NULL;
}
