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

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

/* update the sentinel (segment info) so that delta angle is always positive and
   matches the first arc's (narc) */
static void update_seg_sentinel_angles(grbs_arc_t *sentinel, grbs_arc_t *narc)
{
	double sa = narc->sa, da = narc->da;

	if (da < 0) { /* normalize angles so the sentinel arc is always positive */
		sa = sa + da;
		da = -da;
	}

	if (da == 0) { /* special case: inserted an unfinished net on the lowest orbit */
		double sentinel_ea = sentinel->sa + sentinel->da;

		/* normalize start angle */
		if (sa > 2*GRBS_PI)
			sa -= 2*GRBS_PI;
		else if (sa < 0)
			sa += 2*GRBS_PI;

		/* extend the original sentinel instead of overwriting; however, this
		   normally shouldn't happen as an unfinished net's dangling endpoint
		   will be in a new segment if it is outside of this one and the new
		   segment will be merged only when the delta is known */
		if (sa < sentinel->sa)
			sentinel->sa = sa;
		else if (sa > sentinel_ea)
			sentinel->da = sa - sentinel->sa;
	}
	else {
		sentinel->sa = sa;
		sentinel->da = da;
	}
}

/* if narc is the lowest orbit, update the sentinel, assuming narc has changed */
static void auto_update_seg_sentinel_angles(grbs_arc_t *narc)
{
	grbs_arc_t *sentinel = narc->link_point.parent->first;
	if ((sentinel != NULL) && (sentinel->link_point.next == narc))
		update_seg_sentinel_angles(sentinel, narc);
}

/* Move one end point of arc to match ang */
static void grbs_angle_update_ang_end(grbs_t *grbs, grbs_arc_t *arc, int at_end, double ang, grbs_arc_t *from, int notify)
{
	if (notify)
		CHG_PRE(grbs, arc);
	if (arc->r == 0) {
		double ex, ey, ea;
		assert(from != NULL);
		ea = at_end ? from->sa : from->sa + from->da;
		ex = from->parent_pt->x + cos(ea) * from->r;
		ey = from->parent_pt->y + sin(ea) * from->r;
		arc->sa = atan2(ey - arc->parent_pt->y, ex - arc->parent_pt->x);
	}
	else if (at_end) {
		arc->da = grbs_arc_get_delta(arc->sa, ang, arc->da > 0 ? +1 : -1); /* don't change the other side angle */
	}
	else {
		double old = arc->sa;
		arc->sa = ang;
		arc->da = grbs_arc_get_delta(arc->sa, old + arc->da, arc->da > 0 ? +1 : -1); /* don't change the other side angle */
	}
	if (notify)
		CHG_POST(grbs, arc);
}

/* Radius of "arc" changed, tune the angle knowing the adjacent arc on route;
   returns whether any angle has been changed */
static int grbs_angle_update_ang1(grbs_t *grbs, grbs_arc_t *arc, int at_end, grbs_arc_t *next)
{
	double a[4], nextr;
	int ang_side, next_ang_side, next_at_end, crossbelt = 0;
	grbs_arc_t *from;

	if (((arc->da > 0) && (next->da < 0)) || ((arc->da < 0) && (next->da > 0)))
		crossbelt = 1;

	nextr = next->r;

	if (grbs_bicycle_angles(arc->parent_pt->x, arc->parent_pt->y, arc->r, next->parent_pt->x, next->parent_pt->y, nextr, a, crossbelt) != 0)
		return 0;

	grbs_gen_bicycle_idx(arc, next, crossbelt, at_end, &ang_side, &next_ang_side);

	{
		next_at_end = !at_end;
		if (next->r == 0)
			next_ang_side = !at_end; /* incident line special casing */
	}

	/* tune the angle this side */
	from = ((at_end == 0) ? arc->link_2net.prev : arc->link_2net.next);
	grbs_angle_update_ang_end(grbs, arc, at_end, a[ang_side], from, 0);

	/* tune the angle of the other side (next) */
	grbs_angle_update_ang_end(grbs, next, next_at_end, a[next_ang_side], arc, 1);
	auto_update_seg_sentinel_angles(next);
	return 1;
}

/* Radius of "arc" changed, tune the angles */
static void grbs_angle_update(grbs_t *grbs, grbs_arc_t *arc)
{
	grbs_arc_t *next, *prev;
	int changed = 0;

	next = arc->link_2net.next;
	prev = arc->link_2net.prev;


	if (prev != NULL) changed += grbs_angle_update_ang1(grbs, arc, 0, prev);
	if (next != NULL) changed += grbs_angle_update_ang1(grbs, arc, 1, next);

	if (changed)
		auto_update_seg_sentinel_angles(arc);
}

/* Calculate incident angles (e.g. on start/end, after a route has been realized) */
void grbs_inc_ang_update(grbs_t *grbs, grbs_arc_t *a)
{
	double cx, cy, nx, ny, ang;
	grbs_arc_t *next;

	assert(a->r == 0);

	/* get the endpoint of the next arc segment */
	if (a->link_2net.next != NULL) {
		next = a->link_2net.next;
		ang = next->sa;
	}
	else if (a->link_2net.prev != NULL) {
		next = a->link_2net.prev;
		ang = next->sa + next->da;
	}
	else
		return; /* placed incident arc first, no next yet */

	cx = a->parent_pt->x;
	cy = a->parent_pt->y;
	nx = next->parent_pt->x + next->r * cos(ang);
	ny = next->parent_pt->y + next->r * sin(ang);

	CHG_PRE(grbs, a);
	a->sa = atan2(ny - cy, nx - cx);
	CHG_POST(grbs, a);
}

static void realize_maybe_detach(grbs_t *grbs, grbs_arc_t *arc)
{
	if ((arc != NULL) && (arc->r > 0)) {
		double min_r2 = grbs_self_isect_convex_r2(grbs, arc);
		if (arc->r * arc->r < min_r2) {
			tprintf("   realize_maybe_detach: too small, would become concave, detach (%f;%f)!\n", arc->parent_pt->x, arc->parent_pt->y);
			grbs_force_detach(grbs, arc, 1);
			return;
		}
		if ((arc->da < -5.5) || (arc->da > 5.5)) {
			tprintf("   realize_maybe_detach: invalid delta angle\n");
#warning TODO: it would be better to handle this where it happens: grbs_angle_update() on other-side (next or prev) arc
			grbs_force_detach(grbs, arc, 1);
			return;
		}
	}
}

/* When a new arc is inserted, increase radius of existing arcs and tune
   their end angles. Returns 0 on success, 1 on collision. */
static int bump_seg_radii(grbs_t *grbs, grbs_arc_t *from, double incr, double last_copr, double last_clr, int shrinking, int dry, int removing)
{
	grbs_arc_t *a;
	int no_wrong = (last_copr < 0);

	if (!dry) {
		for(a = from; a != NULL; a = a->link_point.next) {
			double oldr = a->r;
			grbs_2net_t *atn = grbs_arc_parent_2net(a);
			int shrink_checked = 0;

			CHG_PRE(grbs, a);
			if (a->wrong_r) {
				/* arc was force-attached so the spacing between it and the previous
				   is bigger than needed; recalculate radius from scratch using the
				   last copper outer radius and last clearance we kept track on */

				if (removing)
					return grbs_force_detach(grbs, a, 1);

				assert(!no_wrong);
				a->r = last_copr + atn->copper + GRBS_MAX(last_clr, atn->clearance);
				shrink_checked = 1;
				if (a->r < a->min_r) {
					tprintf("  %f -> %f a=%p %p (wrong); smaller than minimum %f, detach\n", oldr, a->r, a, a->link_point.next, a->min_r);
					return grbs_force_detach(grbs, a, 1);
				}
				tprintf("  %f -> %f a=%p %p (wrong)\n", oldr, a->r, a, a->link_point.next);
				a->wrong_r = 0;
			}
			else {
				a->r += incr;
				tprintf("  %f -> %f a=%p %p (normal)\n", oldr, a->r, a, a->link_point.next);
				
			}

			if (shrinking && !shrink_checked) {
				double min_r2 = grbs_self_isect_convex_r2(grbs, a);
				if (a->r * a->r < min_r2) {
					tprintf("   shrink: too small, would become concave, detach!\n");
					return grbs_force_detach(grbs, a, 1);
				}
			}

			grbs_angle_update(grbs, a);
			CHG_POST(grbs, a);
			last_copr = a->r + atn->copper;
			last_clr = atn->clearance;
			realize_maybe_detach(grbs, a->link_2net.prev);
			realize_maybe_detach(grbs, a->link_2net.next);
		}
	}
	else {
		/* in case of dry, it is enough to check the outmost arc */
		gdl_list_t *arcs = from->link_point.parent;
		grbs_arc_t *limit = from->link_point.prev;

		/* check the last (biggest) arc that is in use _above_ 'from' */
		for(a = gdl_last(arcs); (a != NULL) && (a != limit); a = a->link_point.prev) {
			if (a->in_use) {
				int cr;
				double oldr = a->r;
				grbs_rtree_box_t old_bbox = a->bbox;

				/* pretend the arc is already as large as it will be after realization
				   to see if it would collide with anything */
				a->r += incr;
				grbs_arc_bbox(a);
				cr = coll_check_arc(grbs, grbs_arc_parent_2net(a), a, 0);
				a->r = oldr;
				a->bbox = old_bbox;

				if (cr) {
					tprintf("fail/collision\n");
					return 1;
				}
				return 0;
			}
		}

		/* there was no existing arc above ours in the same sector; this means
		   our new arc is on top, check it */
		if (coll_check_arc(grbs, grbs_arc_parent_2net(a), a, 1)) {
			tprintf("fail/collision\n");
			return 1;
		}
	}

	return 0;
}

/* if there is anything above the new arc, make room for it by
   pushing the above-arcs further (by exactly as much as the new arc
   is going to take) */
static double bump_seg_radii_any(grbs_t *grbs, grbs_2net_t *tn, grbs_arc_t *arc, int *need_narc_post_update, double *narc_oldr_out, int dry, int *coll_out, int removing)
{
	grbs_arc_t *next;
	double prev_copper, prev_clearance, newr, incr, prevr;
	double narc_oldr = 0, narcr = arc->new_r, lastcopr;

	*coll_out = 0;

	/* simple case: convex without convex segments below:
	   previous radius:copper:clearance is the same-segment arc below or the point */
	if (!arc->in_use) {
		prev_copper = arc->parent_pt->copper;
		prev_clearance = arc->parent_pt->clearance;
		prevr = 0;
	}
	else {
		prev_copper = arc->copper;
		prev_clearance = arc->clearance;
		prevr = arc->r;
	}

	next = grbs_next_arc_in_use(arc);
	if (next == NULL) {
		*narc_oldr_out = 0;
		return narcr;
	}

	newr = prevr + prev_copper + tn->copper + GRBS_MAX(prev_clearance, tn->clearance); /* new arc added on top of the previous */
	tprintf("newr=%f != %f (prev=%f/%f curr=%f/%f)\n", newr, narcr, prev_copper, prev_clearance, tn->copper, tn->clearance);
	if (newr != narcr) {
		narc_oldr = narcr;
		narcr = newr; /* may replace an arc with smaller or larger spacing reqs... */
		tprintf(" new narc r: %f -> %f\n",  narc_oldr, narcr);
		*need_narc_post_update = 1;
	}

	lastcopr = narcr + tn->copper;
	newr = lastcopr + next->copper + GRBS_MAX(next->clearance, tn->clearance); /* new radius of the next route */
	incr = newr - next->r;
	tprintf("incr; narc->r=%f newr=%f oldr=%f incr=%f W: next=%f/%f curr=%f/%f\n", narcr, newr, next->r, incr, next->copper, next->clearance, tn->copper, tn->clearance);

	*coll_out = bump_seg_radii(grbs, next, incr, lastcopr, tn->clearance, 0, dry, removing);
	*narc_oldr_out = narc_oldr;
	return narcr;
}

static grbs_arc_t *grbs_path_realize_(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *addr, int reverse, int dry, grbs_arc_t *ins_after)
{
	grbs_arc_t *arc, *narc, *a, *first;
	static grbs_arc_t narc_dry;
	grbs_point_t *pt;
	int rev = 1, need_narc_post_update = 0, type = (addr->type & 0x0F);
	int coll = 0, coll_tmp;
	double narcr, narc_oldr;

	switch(type) {
		case ADDR_ARC_CONVEX:
			arc = addr->obj.arc;
			assert(arc->new_in_use);

			pt = arc->parent_pt;
			first = gdl_first(&pt->arcs[arc->segi]);

			/* make room for the new arc */
			narcr = bump_seg_radii_any(grbs, tn, arc, &need_narc_post_update, &narc_oldr, dry, &coll_tmp, 0);
			coll |= coll_tmp;

			/* now that we have the room: insert the new arc */
			if (!dry) {
				if (rev)
					narc = grbs_arc_new(grbs, arc->parent_pt, arc->segi, narcr, arc->new_sa + arc->new_da, -arc->new_da);
				else
					narc = grbs_arc_new(grbs, arc->parent_pt, arc->segi, narcr, arc->new_sa, arc->new_da);

				arc->new_r = arc->new_sa = arc->new_da = 0;
				arc->new_in_use = 0;
			}
			else
				narc = &narc_dry;


			if ((arc == first) && !dry)
				update_seg_sentinel_angles(first, narc);

			narc->copper = tn->copper;
			narc->clearance = tn->clearance;

			if (!dry) {
				grbs_arc_bbox(narc);
				if (coll_check_arc(grbs, tn, narc, 0)) {
					grbs_del_arc(grbs, narc);
					return NULL;
				}
			}

			break;

		case ADDR_POINT:
			pt = addr->obj.pt;
			/* this will insert the new arc in the ->incs list becuase r=0 (segi doesn't matter) */
			if (!dry)
				narc = grbs_arc_new(grbs, pt, 0, 0, 0, 0);
			else
				narc = &narc_dry;

			break;


		case ADDR_ARC_VCONCAVE:
			return NULL; /* do not realize any virtual concave - it's temporary and the final line will go far away from this point */

		default: abort();
	}


	if (coll) {
		if (!dry)
			grbs_del_arc(grbs, narc);
		return NULL;
	}

	if (!dry) {
		narc->in_use = 1;
		narc->copper = tn->copper;
		narc->clearance = tn->clearance;

		/* insert in 2net */
		if (ins_after == NULL) {
			if (reverse)
				gdl_insert(&tn->arcs, narc, link_2net);
			else
				gdl_append(&tn->arcs, narc, link_2net);
		}
		else
			gdl_insert_after(&tn->arcs, ins_after, narc, link_2net);


		/* update neighbors */
		if (narc->r == 0) {
			grbs_inc_ang_update(grbs, narc);
			/* there is no sentinel under an incident */
		}
		else {
			if (need_narc_post_update)
				grbs_angle_update(grbs, narc);
			auto_update_seg_sentinel_angles(narc);
		}
	}

	if (!dry) {
		if (!narc->registered)
			CHG_NEW(grbs, narc);
		grbs_line_create(grbs, narc);
	}

	/* when the second arc is placed, go back and calc the incident angle of
	   the first which was unknown by the time it was created, if route start
	   is incident (TODO: else it's a backrolled partial route's last arc which) */
	if (!dry && (gdl_length(&tn->arcs) == 2)) {
		a = gdl_first(&tn->arcs);
		if (a->r == 0)
			grbs_inc_ang_update(grbs, a);
	}

	return narc;
}

grbs_arc_t *grbs_path_realize(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *addr, int reverse)
{
	return grbs_path_realize_(grbs, tn, addr, reverse, 0, NULL);
}

int grbs_path_dry_realize(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *addr, int reverse)
{
	if ((addr->type & 0x0F) == ADDR_ARC_VCONCAVE)
		return 0;
	if (grbs_path_realize_(grbs, tn, addr, reverse, 1, NULL) == NULL)
		return -1;
	return 0;
}


grbs_line_t *grbs_line_realize(grbs_t *grbs, grbs_2net_t *tn, grbs_point_t *p1, grbs_point_t *p2)
{
	grbs_arc_t *a1, *a2;
	grbs_line_t *l;

	a1 = grbs_arc_new(grbs, p1, 0, 0, 0, 0);
	a2 = grbs_arc_new(grbs, p2, 0, 0, 0, 0);

	gdl_append(&tn->arcs, a1, link_2net);
	gdl_append(&tn->arcs, a2, link_2net);

	a1->in_use = a2->in_use = 1;

	grbs_inc_ang_update(grbs, a1);
	grbs_inc_ang_update(grbs, a2);

	l = grbs_line_create(grbs, a2);
	grbs_line_bbox(l);
	grbs_line_reg(grbs, l);
	return l;
}


int grbs_path_validate(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *prev_addr, grbs_addr_t *addr, grbs_addr_t *next_addr)
{
	int ty, type = (addr->type & 0x0F);
	grbs_arc_t *arc = addr->obj.arc;
	grbs_point_t *pt;
	g2d_cline_t a, b;

	switch(type) {
		case ADDR_ARC_CONVEX:

		/* check for self intersecting lines */
		if ((arc->new_da <= GRBS_PI) && (arc->new_da >= -GRBS_PI))
			return 0; /* no intersection possible if angle is below 180 deg */

		/* do not do anything if there are no two exit lines */
		if ((prev_addr == NULL) || (next_addr == NULL))
			return 0;

		/* determine local arc endpoints of the exit lines */
		pt = arc->parent_pt;
		a.p1.x = pt->x + cos(arc->new_sa + arc->new_da) * arc->new_r;
		a.p1.y = pt->y + sin(arc->new_sa + arc->new_da) * arc->new_r;
		b.p1.x = pt->x + cos(arc->new_sa) * arc->new_r;
		b.p1.y = pt->y + sin(arc->new_sa) * arc->new_r;

		/* determine remote arc endpoints of the exit lines */
		ty = (prev_addr->type & 0x0F);
		if (ty == ADDR_ARC_VCONCAVE)
			return 0;
		if (ty == ADDR_POINT) {
			a.p2.x = prev_addr->obj.pt->x;
			a.p2.y = prev_addr->obj.pt->y;
		}
		else {
			grbs_arc_t *prev = prev_addr->obj.arc;
			a.p2.x = prev->parent_pt->x + cos(prev->new_sa) * prev->new_r;
			a.p2.y = prev->parent_pt->y + sin(prev->new_sa) * prev->new_r;
		}

		ty = (next_addr->type & 0x0F);
		if (ty == ADDR_ARC_VCONCAVE)
			return 0;
		if (ty == ADDR_POINT) {
			b.p2.x = next_addr->obj.pt->x;
			b.p2.y = next_addr->obj.pt->y;
		}
		else {
			grbs_arc_t *next = next_addr->obj.arc;
			b.p2.x = next->parent_pt->x + cos(next->new_sa + next->new_da) * next->new_r;
			b.p2.y = next->parent_pt->y + sin(next->new_sa + next->new_da) * next->new_r;
		}

		return g2d_isc_cline_cline(&a, &b);
	}
	return 0;
}

void grbs_path_cleanup_all(grbs_t *grbs)
{
	grbs_arc_t *a, *next, *first = gdl_first(&grbs->all_arcs);

	for(a = first; a != NULL; a = next) {
		int tune = (a->new_in_use && (a == first));
		next = gdl_next(&grbs->all_arcs, a);
		a->new_in_use = 0;
		grbs_clean_unused_sentinel_seg(grbs, a->parent_pt, a->segi, tune);
	}
	uall_stacks_clean(&grbs->stk_addrs);
}

#define CL_FORCE 2000
static void grbs_path_cleanup_addr_(grbs_t *grbs, grbs_addr_t *addr, int call_cnt)
{
	switch(addr->type & 0x0F) {
		case ADDR_ARC_VCONCAVE:
			if (call_cnt == CL_FORCE)
				return;
			assert(call_cnt == 0);
			assert(addr->last_real != NULL); /* vconcave must have a known last real where the path last existed */
			grbs_path_cleanup_addr_(grbs, addr->last_real, 0);
			break;
		case ADDR_ARC_CONVEX:
			addr->obj.arc->new_in_use = 0;
			grbs_clean_unused_sentinel_seg(grbs, addr->obj.arc->parent_pt, addr->obj.arc->segi, (addr->obj.arc->link_point.prev == NULL));
			if ((call_cnt < 2) && (addr->last_real != NULL))
				grbs_path_cleanup_addr_(grbs, addr->last_real, call_cnt+1);
			break;
		case ADDR_POINT:
			break;
	}
}

void grbs_path_cleanup_addr(grbs_t *grbs, grbs_addr_t *addr)
{
	grbs_path_cleanup_addr_(grbs, addr, 0);
}

void grbs_path_cleanup_by_tn(grbs_t *grbs, grbs_2net_t *tn)
{
	grbs_addr_t *addr;

	for(addr = gdl_first(&tn->arcs); addr != NULL; addr = gdl_next(&tn->arcs, addr))
		grbs_path_cleanup_addr_(grbs, addr, CL_FORCE);
	uall_stacks_clean(&grbs->stk_addrs);
}

#undef CL_FORCE
