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

static void coll_report_arc_(grbs_t *grbs, grbs_2net_t *tn, grbs_2net_t *coll_tn, grbs_arc_t *coll_arc)
{
	if ((grbs->coll_report_arc_cb != NULL) && (coll_tn != NULL))
		grbs->coll_report_arc_cb(grbs, tn, coll_tn, coll_arc);
}

static void coll_report_line_(grbs_t *grbs, grbs_2net_t *tn, grbs_2net_t *coll_tn, grbs_line_t *coll_line)
{
	if ((grbs->coll_report_line_cb != NULL) && (coll_tn != NULL))
		grbs->coll_report_line_cb(grbs, tn, coll_tn, coll_line);
}

static void coll_report_pt_(grbs_t *grbs, grbs_2net_t *tn, grbs_point_t *coll_pt)
{
	if (grbs->coll_report_pt_cb != NULL)
		grbs->coll_report_pt_cb(grbs, tn, coll_pt);
}


static void coll_report_arc(grbs_t *grbs, grbs_2net_t *tn, grbs_arc_t *coll_arc)
{
	coll_report_arc_(grbs, tn, grbs_arc_parent_2net(coll_arc), coll_arc);
}

static void coll_report_line(grbs_t *grbs, grbs_2net_t *tn, grbs_line_t *coll_line)
{
	coll_report_line_(grbs, tn, grbs_arc_parent_2net(coll_line->a1), coll_line);
}

static void coll_report_pt(grbs_t *grbs, grbs_2net_t *tn, grbs_point_t *coll_pt)
{
	coll_report_pt_(grbs, tn, coll_pt);
}

static void coll_report_check(grbs_t *grbs, grbs_2net_t *tn, grbs_2net_t *coll_tn)
{
	if (grbs->coll_report_check_cb != NULL)
		grbs->coll_report_check_cb(grbs, tn, coll_tn);
}

#define REPORT_ARC(coll_arc)   coll_report_arc(grbs, tn, coll_arc)
#define REPORT_LINE(coll_line) coll_report_line(grbs, tn, coll_line)
#define REPORT_PT(coll_pt) coll_report_pt(grbs, tn, coll_pt)
#define REPORT_CHECK(coll_tn) coll_report_check(grbs, tn, coll_tn)

/* Returns 1 if arc has collided */
static int coll_check_arc(grbs_t *grbs, grbs_2net_t *tn, grbs_arc_t *arc, int new)
{
	grbs_arc_t *coll_arc;
	grbs_point_t *coll_point;
	double da;

	/* need to check all arcs, not only the outmost: a bottom arc can be much
	   larger than a narrow top arc and the bottom arc can collide on the
	   extending long end */

	coll_arc = grbs_arc_arc_collision(grbs, tn, arc, new);
	if (coll_arc != NULL) {
		REPORT_ARC(coll_arc);
		return 1;
	}

	coll_point = grbs_arc_point_collision(grbs, tn, arc, new);
	if (coll_point != NULL) {
		REPORT_PT(coll_point);
		return 1;
	}

	da = new ? arc->new_da : arc->da;
	if (da != 0) {
		long n;

		grbs->collobjs.used = 0;
		grbs_arc_line_collision(grbs, tn, arc, new);
		for(n = 0; n < grbs->collobjs.used; n++) {
			grbs_line_t *coll_line = grbs->collobjs.array[n];
			if (grbs_force_attach_line_to_pt(grbs, coll_line, arc->parent_pt, arc->r + tn->copper, tn->clearance, arc->segi) != 0) {
				REPORT_LINE(coll_line);
				return 1;
			}
		}
	}

	if (grbs->coll_check_arc != NULL) {
		grbs_2net_t *ctn = grbs->coll_check_arc(grbs, tn, arc, new);
		if (ctn != NULL) {
			REPORT_CHECK(ctn);
			return 1;
		}
	}

	return 0;
}

/* Returns 1 if line has collided */
static int coll_check_line(grbs_t *grbs, grbs_2net_t *tn, grbs_point_t *pt1, double x1, double y1, grbs_point_t *pt2, double x2, double y2, int incident)
{
	grbs_point_t *pt;
	long n;

	grbs->collobjs.used = 0;
	grbs_line_line_collisions(grbs, tn, x1, y1, x2, y2, tn->copper, tn->clearance);
	for(n = 0; n < grbs->collobjs.used; n++) {
		grbs_line_t *line = grbs->collobjs.array[n];
		REPORT_LINE(line);
		return 1;
	}

	grbs->collobjs.used = 0;
	grbs_line_arc_collisions(grbs, tn, x1, y1, x2, y2, tn->copper, tn->clearance);
	for(n = 0; n < grbs->collobjs.used; n++) {
		grbs_arc_t *arc = grbs->collobjs.array[n];

		/* special case: line vs. arc crosses around points are checked locally using angles */
		if ((pt1 == arc->parent_pt) || (pt2 == arc->parent_pt))
			continue;

		REPORT_ARC(arc);
		return 1;
	}

	pt = grbs_line_point_collision(grbs, tn, x1, y1, x2, y2, tn->copper, tn->clearance, pt1, pt2);
	if (pt != NULL) {
		REPORT_PT(pt);
		return 1;
	}


	if (grbs->coll_check_line != NULL) {
		grbs_2net_t *ctn = grbs->coll_check_line(grbs, tn, pt1, x1, y1, pt2, x2, y2);
		if (ctn != NULL) {
			REPORT_CHECK(ctn);
			return 1;
		}
	}

	return 0;
}

int grbs_is_target_pt_routable(grbs_t *grbs, grbs_2net_t *tn, grbs_point_t *tpt)
{
	grbs_point_t *cpt;
	grbs_arc_t *carc;
	int res = 1;

	cpt = grbs_endcap_point_collision(grbs, tn, tpt);
	if (cpt != NULL) {
		/* can not report collision as points are fixed objects */
		return 0; /* return before reporting any arc - no reason to blame it on arcs and remove arcs if we can't route it at the end */
	}

	carc = grbs_endcap_arc_collision(grbs, tn, tpt);
	if (cpt != NULL) {
		REPORT_ARC(carc);
		res = 0;
	}


	return res;
}

#undef REPORT_ARC
#undef REPORT_LINE
#undef REPORT_PT
#undef REPORT_CHECK


/* rtree based collision handling (centerline only) */

static int grbs_arc_is_sentinel(grbs_arc_t *arc)
{
	return (arc->link_point.prev == NULL);
}

static void CHG_PRE(grbs_t *grbs, grbs_arc_t *arc)
{
	if (grbs_arc_is_sentinel(arc))
		return;
	assert(arc->old_in_use == 0);
	arc->old_in_use = 1;
	arc->old_r = arc->r;
	arc->old_sa = arc->sa;
	arc->old_da = arc->da;
}

static void CHG_POST(grbs_t *grbs, grbs_arc_t *arc)
{
	int chg_start = 0, chg_end = 0;

	if (grbs_arc_is_sentinel(arc))
		return;

	assert(arc->old_in_use == 1);
	arc->old_in_use = 0;
	if (arc->r != arc->old_r) {
		chg_start = 1;
		chg_end = 1;
	}
	else {
		if (arc->sa != arc->old_sa)
			chg_start = 1;
		if (arc->sa + arc->da != arc->old_sa + arc->old_da)
			chg_end = 1;
	}

	if (chg_start && (arc->sline != NULL)) { /* sline is NULL in start incident arc */
		grbs_line_unreg(grbs, arc->sline);
		grbs_line_attach(grbs, arc->sline, arc, 2);
		grbs_line_bbox(arc->sline);
		grbs_line_reg(grbs, arc->sline);
	}
	if (chg_end && (arc->eline != NULL)) { /* eline is NULL during initial realize of a net before the next arc is realized */
		grbs_line_unreg(grbs, arc->eline);
		grbs_line_attach(grbs, arc->eline, arc, 1);
		grbs_line_bbox(arc->eline);
		grbs_line_reg(grbs, arc->eline);
	}

	if (chg_start || chg_end) {
		if (arc->registered)
			grbs_arc_unreg(grbs, arc);
		grbs_arc_bbox(arc);
		grbs_arc_reg(grbs, arc);
	}
}

static void CHG_NEW(grbs_t *grbs, grbs_arc_t *arc)
{
	grbs_arc_bbox(arc);
	grbs_arc_reg(grbs, arc);
	arc->old_in_use = 0;
}

