/*  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_NEXT_TRACE*/
/*#define GRBS_ROUTE_NEXT_FAIL_TRACE*/

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

#undef failprinf
#ifdef GRBS_ROUTE_NEXT_FAIL_TRACE
#include <stdio.h>
#define failprintf \
	printf("fail: %s L%d: ", __FUNCTION__, __LINE__); \
	printf
#else
#define failprintf grbs_nullprintf
#endif

/* Return whether an incident line is valid (not crossing any arc around pt) */
static int grbs_inc_line_is_valid(grbs_t *grbs, grbs_point_t *pt, double ang, double copper, double clearance, grbs_arc_t **coll)
{
	grbs_arc_t *a;
	int side;

	for(side = 0; side < GRBS_MAX_SEG; side++) {
		for(a = gdl_first(&pt->arcs[side]); a != NULL; a = gdl_next(&pt->arcs[side], a)) {
			double sa = a->sa, da = a->da;
			if (!a->in_use) continue;
			if (grbs_angle_in_arc(sa, da, ang, 0)) {
				*coll = a;
				return 0;
			}
			break; /* it is enough to check the first arc in use per side, further arcs are smaller */
		}
	}
	return 1;
}

static int grbs_inc_line2arc_is_valid(grbs_t *grbs, grbs_2net_t *tn, grbs_point_t *pt, grbs_addr_t *addr, double arc_ang, double copper, double clearance)
{
	grbs_arc_t *arc = addr->obj.arc, *coll;
	double r, ax, ay, vx, vy, ang;
	int valid;

	assert(!addr_is_incident(addr));
	r = arc->new_r;
	ax = arc->parent_pt->x + cos(arc_ang) * r;
	ay = arc->parent_pt->y + sin(arc_ang) * r;
	vx = ax - pt->x;
	vy = ay - pt->y;
	ang = atan2(vy, vx);

	valid = grbs_inc_line_is_valid(grbs, pt, ang, copper, clearance, &coll);
	if (!valid)
		coll_report_arc(grbs, tn, coll);

	return valid;
}

static int grbs_convex_turns_into_concave(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *from, double to_x, double to_y)
{
	if (((from->type & 0x0F) == ADDR_ARC_CONVEX) && (from->last_real != NULL) && (from->last_real->type != 0)) {
		double px, py, min_r2, r2;
		g2d_cline_t cline;
		grbs_point_t *from_pt = addr_point(from);

		if ((from->last_real->type & 0x0F) == ADDR_POINT) {
			px = from->last_real->obj.pt->x;
			py = from->last_real->obj.pt->y;
		}
		else
			grbs_get_new_arc_end(grbs, tn, from->last_real, 1, &px, &py);

		cline.p1.x = px; cline.p1.y = py;
		cline.p2.x = to_x; cline.p2.y = to_y;

		r2 = from->obj.arc->new_r * from->obj.arc->new_r;
		min_r2 = grbs_self_isect_convex_r2_cline(grbs, from_pt, from->obj.arc->new_da > 0, &cline);
		return (r2 < min_r2);
	}

	return 0;
}

static grbs_addr_t *grbs_implement_incident(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *from, double from_r, double from_a, grbs_point_t *to_pt, int segi, int new_at_from)
{
	grbs_point_t *from_pt = addr_point(from);
	double aex, aey;

	if (new_at_from) {
		/* when creating a new go-around arc, we first place start (new_sa), so
		   addr is set to ADDR_ARC_END which means next we will need to set the end
		   (delta) */
		grbs_addr_t *res;

		aex = from_pt->x + cos(from_a) * from_r;
		aey = from_pt->y + sin(from_a) * from_r;
		if (coll_check_line(grbs, tn, to_pt, to_pt->x, to_pt->y, from_pt, aex, aey, 1)) {
			failprintf("line collision\n");
			return NULL;
		}

		res = grbs_addr_new(grbs, ADDR_ARC_CONVEX | ADDR_ARC_END, from->obj.arc);
		res->obj.arc->segi = segi;
		res->obj.arc->new_r = from_r;
		res->obj.arc->new_sa = from_a;
		res->obj.arc->new_da = 0;
		res->obj.arc->new_adir = grbs_get_adir(to_pt->x, to_pt->y, from_pt->x, from_pt->y, from_r, from_a);
		res->obj.arc->new_in_use = 1;
		if (coll_check_arc(grbs, tn, res->obj.arc, 1)) {
			failprintf("arc collision failed\n");
			grbs_del_arc(grbs, res->obj.arc);
			grbs_addr_free_last(grbs);
			return NULL;
		}
		return res;
	}

	/* coming from an existing arc, tune the angle; we should be coming from an
	   arc end: order is point -> arc_start -> arc_end -> point */
	if (grbs_arc_tune4exit(grbs, tn, from, from_a) != 0) {
		failprintf("tune4exit failed\n");
		return NULL;
	}

	if (grbs_convex_turns_into_concave(grbs, tn, from, to_pt->x, to_pt->y)) {
		from->obj.arc->new_da = 0;
		failprintf("convex turned into concave (arc2inc)\n");
		return NULL;
	}


	grbs_get_new_arc_end(grbs, tn, from, 1, &aex, &aey);
	if (coll_check_line(grbs, tn, from_pt, aex, aey, to_pt, to_pt->x, to_pt->y, 1)) {
		failprintf("line collision\n");
		return NULL;
	}

	return grbs_addr_new(grbs, ADDR_POINT, to_pt);
}

static grbs_addr_t *grbs_implement_arc2arc(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *from, double from_r, double from_a, grbs_addr_t *to, double to_r, double to_a, int segi)
{
	grbs_point_t *from_pt = addr_point(from), *to_pt;
	grbs_arc_t *to_arc;
	double ex1, ey1, ex2, ey2;
	grbs_addr_t *res;

	assert(from->type & ADDR_ARC_CONVEX);
	assert(from->type & ADDR_ARC_END);
	assert(to->type & ADDR_ARC_CONVEX);

	/* coming from an existing arc, tune the angle */
	if (grbs_arc_tune4exit(grbs, tn, from, from_a) != 0) {
		failprintf("from exit fail\n");
		return NULL;
	}

	to_pt = addr_point(to);
	to_arc = to->obj.arc;
	to_arc->segi = segi;
	to_arc->new_r = to_r;
	to_arc->new_sa = to_a;
	to_arc->new_da = 0;
	to_arc->new_adir = grbs_get_adir(from_pt->x, from_pt->y, to_pt->x, to_pt->y, to_r, to_a);
	to_arc->new_in_use = 1;

	if (coll_check_arc(grbs, tn, to_arc, 1)) {
		failprintf("arc collision failed\n");
		to_arc->new_in_use = 0;
		return NULL;
	}

	grbs_get_new_arc_end(grbs, tn, from, 1, &ex1, &ey1);
	grbs_get_arc_end_(grbs, tn, to_arc, 0, &ex2, &ey2);

	if (grbs_convex_turns_into_concave(grbs, tn, from, ex2, ey2)) {
		from->obj.arc->new_da = 0;
		to->obj.arc->new_in_use = 0;
		failprintf("convex turned into concave (arc2arc)\n");
		return NULL;
	}


	if (coll_check_line(grbs, tn, from_pt, ex1, ey1, to_pt, ex2, ey2, 0)) {
		failprintf("line collision\n");
		to_arc->new_in_use = 0;
		return NULL;
	}

	res = grbs_addr_new(grbs, (to->type & 0x0F) | ADDR_ARC_END, to_arc);
	if (grbs_path_dry_realize(grbs, tn, res, 0) != 0) {
		failprintf("dry realize collision\n");
		grbs_addr_free_last(grbs);
		to_arc->new_in_use = 0;
		return NULL;
	}

	return res;
}

static grbs_addr_t *path_find_arc2inc(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *from, grbs_point_t *to_pt, int to_segi)
{
	grbs_point_t *from_pt = addr_point(from);
	double a[4], fromr_r;
	int new_at_from = 0;
	double from_ang;
	grbs_addr_type_t from_curving = (from->type & 0x0F);

	assert(from->type != ADDR_POINT);
	fromr_r = grbs_get_new_radius(grbs, tn, from);

	if (from_curving == ADDR_ARC_CONVEX) {
		int from_ang_side;

		/* order of from->to does not really matter here */
		if (grbs_bicycle_angles(from_pt->x, from_pt->y, fromr_r, to_pt->x, to_pt->y, 0, a, 0) != 0) {
			failprintf("bicycle angles failed\n");
			return NULL;
		}

		from_ang_side = from_arc_ang_side(grbs, from, to_pt->x, to_pt->y, a);
		if (from_ang_side < 0) {
			failprintf("from angle side\n");
			return NULL;
		}

		from_ang = a[from_ang_side];
	}
	else {
		assert(!"arc2inc: invalid curving; 'from' must be convex arc");
		return NULL;
	}

	/* check if the incident line at 'to' would cross any arc */
	if (!grbs_inc_line2arc_is_valid(grbs, tn, to_pt, from, from_ang, tn->copper, tn->clearance)) {
		failprintf("inc_line2arc_is_valid\n");
		return NULL;
	}

	return grbs_implement_incident(grbs, tn, from, fromr_r, from_ang, to_pt, to_segi, new_at_from);
}

static grbs_addr_t *path_find_inc2arc_convex(grbs_t *grbs, grbs_2net_t *tn, grbs_point_t *from_pt, grbs_addr_t *to, int to_adir, int to_segi)
{
	grbs_point_t *to_pt = addr_point(to);
	double a[4], tor_r;
	int new_at_from = 1;

	assert(to->type != ADDR_POINT);

	if (to->obj.arc->new_in_use) {/* spiral is invalid as it is not the shortest path to revisit an addr */
		failprintf("spiral\n");
		return NULL;
	}

	tor_r = grbs_get_new_radius(grbs, tn, to);

	/* order of from->to does not really matter here */
	if (grbs_bicycle_angles(from_pt->x, from_pt->y, 0, to_pt->x, to_pt->y, tor_r, a, 0) != 0) {
		failprintf("bicycle angles\n");
		return NULL;
	}

	assert(grbs_adir_is_arc(to_adir));

	if (!grbs_angle_visible_between_arcs(grbs, tn, to, a[to_adir])) {
		failprintf("angle visible between arcs\n");
		return NULL;
	}

	if (!grbs_inc_line2arc_is_valid(grbs, tn, from_pt, to, a[to_adir], tn->copper, tn->clearance)) {
		failprintf("inc_line2arc_is_valid\n");
		return NULL;
	}

	return grbs_implement_incident(grbs, tn, to, tor_r, a[to_adir], from_pt, to_segi, new_at_from);
}

static grbs_addr_t *path_find_inc2inc(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *from, grbs_point_t *to_pt)
{
	grbs_arc_t *coll;
	grbs_point_t *from_pt = addr_point(from);
	double vx = to_pt->x - from_pt->x, vy = to_pt->y - from_pt->y;
	double ang = atan2(vy, vx);

	if (!grbs_inc_line_is_valid(grbs, from_pt, ang, tn->copper, tn->clearance, &coll)) {
		failprintf("inc_line_is_valid: from\n");
		coll_report_arc(grbs, tn, coll);
		return NULL;
	}
	if (!grbs_inc_line_is_valid(grbs, to_pt, ang - GRBS_PI, tn->copper, tn->clearance, &coll)) {
		failprintf("inc_line_is_valid: to\n");
		coll_report_arc(grbs, tn, coll);
		return NULL;
	}

	if (coll_check_line(grbs, tn, from_pt, from_pt->x, from_pt->y, to_pt, to_pt->x, to_pt->y, 1)) {
		failprintf("line collision\n");
		return NULL;
	}

	return grbs_addr_new(grbs, ADDR_POINT, to_pt);
}

static void load_under_above_me(grbs_arc_t *arc, grbs_arc_t **addr_under_me, grbs_arc_t **addr_above_me)
{
	if (arc->in_use)
		*addr_under_me = arc;

	*addr_above_me = grbs_next_arc_in_use(arc);
}

/* Return the next (or previous) arc that continues the 'from' arc on its
   route to go around to_pt. If not found, return NULL. If found, set to_ang
   to the angle closer to 'from' on the path */
static grbs_arc_t *grbs_topo_guide_next(grbs_t *grbs, grbs_arc_t *from, grbs_point_t *to_pt, double *from_ang, double *to_ang)
{
	grbs_arc_t *arc;

	arc = from->link_2net.next;
	if ((arc != NULL) && (arc->parent_pt == to_pt)) {
		*from_ang = from->sa + from->da;
		*to_ang = arc->sa;
		return arc;
	}

	arc = from->link_2net.prev;
	if ((arc != NULL) && (arc->parent_pt == to_pt)) {
		*from_ang = from->sa;
		*to_ang = arc->sa + arc->da;
		return arc;
	}

	return NULL;
}

/* check if a potential guide between from_guide and to_guide exists, and if
   so, return 1 if it doesn't change cw/ccw the same way as 'me' */
static int topo_opposite_dir(int me_chdir, grbs_arc_t *from_guide, grbs_arc_t *to_guide)
{
	if ((from_guide != NULL) && (to_guide != NULL)) {
		int from_adir = (from_guide->da > 0), to_adir = (to_guide->da > 0);
		int guide_chdir = (from_adir != to_adir);

		return (guide_chdir != me_chdir);
	}

	return 0;
}

/* special case: our path is guided by an existing path: we are consistently
   above or below an existing route. This happens if our 'from' is below
   (or above) another net whose next (or previous) step is the same point as
   ours and has the same convexity as ours.

   In this case  do a quick topological routing: our net sure will end up below
   (or above) the guiding net at the 'to' address.

   Beside being an optimization this also fixes a bug: when a second parallel
   net is inserted or appended, purely by angles we can't decide if the new
   net should go below or above the existing same-angle net at 'to'. With
   the topological approach the under/above decision is easy, and since we
   are walkign the same path, we cna just copy the existing angles.
   */
static grbs_addr_t *path_find_arc2arc_topo(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *from, grbs_addr_t *to, grbs_arc_dir_t to_adir, int to_segi)
{
	grbs_arc_t *from_under_me = NULL, *from_above_me = NULL;
	grbs_arc_t *to_under_me = NULL, *to_above_me = NULL;
	grbs_arc_t *arc, *to_arc, *tou;
	double from_ang, to_ang, to_r;
	int swapped = 0, to_d, from_d, chdir;

	/* we use this only for the arc-arc case */
	assert((from->type & 0x0f) != ADDR_POINT);
	assert((to->type & 0x0f) != ADDR_POINT);

	/* figure what's under and above on the from side */
	load_under_above_me(from->obj.arc, &from_under_me, &from_above_me);
	if ((from_under_me == NULL) && (from_above_me == NULL)) {
		/* we are in a seg alone; a common reason for that is that we entered
		   beyond an existing segment and would merge with it once the exit
		   angle is known. This can happen only in one direction (forward in adir)
		   and only with the next segment. Try and see. 

		   Of course it may be that the next segment is unrelated (too far away,
		   which also means it's going somewhere else). That's not a problem
		   because then the 'to' part won't match and we will return as
		   'not guided' */
		arc = from->obj.arc;
		tprintf("TOPO-guide: *MERGE* need to step %p ", arc);
		arc = grbs_next_seg(arc->parent_pt, arc->segi, arc->new_sa, arc->new_adir);
		tprintf("-> %p\n", arc);
		if (arc == NULL)
			return NULL;

		load_under_above_me(arc, &from_under_me, &from_above_me);
	}

	tprintf("TOPO-guide: fume=%p fame=%p\n", from_under_me, from_above_me);

	if ((from_under_me == NULL) && (from_above_me == NULL)) /* we are truely alone */
		return NULL;

	if (from_under_me != NULL)
		to_under_me = grbs_topo_guide_next(grbs, from_under_me, to->obj.arc->parent_pt, &from_ang, &to_ang);
	if (from_above_me != NULL)
		to_above_me = grbs_topo_guide_next(grbs, from_above_me, to->obj.arc->parent_pt, &from_ang, &to_ang);

	if ((to_above_me != NULL) && (to_above_me->r == 0)) /* no guide: to_above_me is an incident line */
		to_above_me = NULL;
	if ((to_under_me != NULL) && (to_under_me->r == 0)) /* no guide: to_under_me is an incident line */
		to_under_me = NULL;

	/* Two paths are coming from a point A to a point B; A is under or above me,
	   B is me. If A chnaged from ccw/cw, B needs to change too, else it will
	   be coming from the opposite direction and can not be guided by A. See
	   regression/topo-oppdir.grbs */
	to_d = grbs_adir_is_CW(to_adir) ? +1 : -1;
	from_d = from->obj.arc->new_adir;
	chdir = from_d != to_d;
	if (topo_opposite_dir(chdir, from_under_me, to_under_me)) {
		failprintf("guide: opposite dirs at 'to' with original under\n");
		from_under_me = to_under_me = NULL;
	}
	if (topo_opposite_dir(chdir, from_above_me, to_above_me)) {
		failprintf("guide: opposite dirs at 'to' with original above\n");
		from_above_me = to_above_me = NULL;
	}

	if ((to_under_me == NULL) && (to_above_me == NULL)) /* no guide: under and above go out elsewhere */
		return NULL;

	if (to_d != from->obj.arc->new_adir) {
		arc = to_under_me;
		to_under_me = to_above_me;
		to_above_me = arc;
		swapped = 1;
		tprintf(" TOPO-guide: need to swap\n");
	}

	tprintf("TOPO-guide: tume=%p tame=%p\n", to_under_me, to_above_me);

	arc = (to_under_me != NULL) ? to_under_me : to_above_me; /* the original guiding route we are reproducing */
	tou = (to_under_me != NULL) ? to_under_me : to_above_me->link_point.prev; /* the to-arc we build on top */



	if (tou->new_in_use != 0) {
		failprintf("guided spiral\n");
		return NULL; /* spiral */
	}

	/* coming from an existing arc, tune the angle */
	if (grbs_arc_tune4exit(grbs, tn, from, from_ang) != 0) {
		failprintf("guided from exit fail\n");
		return NULL;
	}

	to_arc = tou;

	/* if we are above our guide, we need to invent a new radius above our
	   guide's radius */
	if (to_under_me != NULL) {
		grbs_addr_t addr;
		addr = *to;
		addr.obj.arc = to_under_me;
		to_r = grbs_get_new_radius(grbs, tn, &addr);
	}
	else
		to_r = arc->r;

	if (swapped) {
		grbs_arc_t *froma = from->obj.arc;
		double a[4];
		int from_adir_idx =  (froma->new_adir > 0) ? 1 : 0;
		int to_adir_idx = (to_d > 0) ? 1 : 0;

		/* recalculate angles */
		if (grbs_bicycle_angles(froma->parent_pt->x, froma->parent_pt->y, froma->new_r, to_arc->parent_pt->x, to_arc->parent_pt->y, to_r, a, 1) != 0) {
			failprintf("guided: swapped bicycle angle\n");
			return NULL;
		}

		to_ang = a[to_adir_idx+2]; /* in convex case it's from bicycle */

		tprintf("SWAP TUNE: from %f->%f   to %f->%f  to_ang: %f\n", 
			fmod(froma->new_sa + froma->new_da, 2*GRBS_PI), a[from_adir_idx],
			to_ang, a[to_adir_idx+2], to_ang);

		froma->new_da = grbs_arc_get_delta(froma->new_sa, a[from_adir_idx], froma->new_da > 0 ? +1 : -1);
	}

	if (grbs_adir_is_convex(to_adir)) { /* a final check on not going invalid convex */
		grbs_arc_t *froma = from->obj.arc;
		double ox, oy;

		ox = to_arc->parent_pt->x + cos(to_ang) * to_r;
		oy = to_arc->parent_pt->y + sin(to_ang) * to_r;

		if ((grbs_invalid_convex_(grbs, froma->parent_pt, froma->new_r, froma->new_sa + froma->new_da, (froma->new_adir > 0) ? +1 : -1, ox, oy))) {
			failprintf("guided: from invalid convex\n");
			return NULL;
		}
	}

	if (coll_check_arc(grbs, tn, from->obj.arc, 1)) {
		failprintf("arc collision failed\n");
		return NULL;
	}

	to_arc->segi = arc->segi;
	to_arc->new_r = to_r;
	to_arc->new_sa = to_ang;
	to_arc->new_da = 0;
	to_arc->new_adir = to_d;
	to_arc->new_in_use = 1;

	grbs_clean_unused_sentinel(grbs, from->obj.arc->parent_pt);
	grbs_clean_unused_sentinel(grbs, to_arc->parent_pt);

	return grbs_addr_new(grbs, (to->type & 0x0F) | ADDR_ARC_END, to_arc);
}


static grbs_addr_t *path_find_arc2arc(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *from, grbs_addr_t *to, grbs_arc_dir_t to_adir, int to_segi)
{
	grbs_point_t *from_pt = addr_point(from);
	grbs_point_t *to_pt   = addr_point(to);
	double a[4], fromr_r, tor_r, fromb_r, tob_r, to_x, to_y, from_x, from_y;
	int from_ang_side, crossbelt;
	int to_adir_idx = (to_adir >= 10) ? (to_adir - 10) : to_adir;
	grbs_addr_t *topo;

	assert((from->type & 0x0F) == ADDR_ARC_CONVEX);

	if (!addr_is_incident(to)) {
		if (to->obj.arc->new_in_use) { /* spiral is invalid as it is not the shortest path to revisit an addr */
			failprintf("spiral\n");
			return NULL;
		}
	}

	/* shorthand: if our route is being guided by an existing route, just copy
	   that instead of trying to invent it again. Beside being an optimization
	   this also fixes a bug (see at the function) */
	topo = path_find_arc2arc_topo(grbs, tn, from, to, to_adir, to_segi);
	if (topo != NULL)
		return topo;

	fromr_r = grbs_get_new_radius(grbs, tn, from);
	fromb_r = fromr_r;
	tor_r = grbs_get_new_radius(grbs, tn, to);
	tob_r = grbs_adir_is_convex(to_adir) ? tor_r : 0;

	crossbelt = (from->obj.arc->new_adir > 0) ? (to_adir_idx == 0) : (to_adir_idx != 0);

	if (grbs_bicycle_angles(from_pt->x, from_pt->y, fromb_r, to_pt->x, to_pt->y, tob_r, a, crossbelt) != 0) {
		failprintf("bicycle_angles\n");
		return NULL;
	}

	to_x = to->obj.arc->parent_pt->x + cos(a[to_adir_idx+2]) * tob_r;
	to_y = to->obj.arc->parent_pt->y + sin(a[to_adir_idx+2]) * tob_r;

	from_ang_side = from_arc_ang_side(grbs, from, to_x, to_y, a);
	if (from_ang_side < 0) {
		failprintf("from ang side\n");
		return NULL;
	}

	from_x = from->obj.arc->parent_pt->x + cos(a[from_ang_side]) * fromb_r;
	from_y = from->obj.arc->parent_pt->y + sin(a[from_ang_side]) * fromb_r;

	if (!grbs_adir_is_convex(to_adir))
		a[2] = a[3] = atan2(from_y - to_y, from_x - to_x); /* incident-target-angle */

	/* printf("arc2arc crossbelt: %d r: %f -> %f ang: %f %f\n", crossbelt, fromr_r, tor_r, a[0], a[2]); */

	if (!grbs_angle_visible_between_arcs(grbs, tn, from, a[from_ang_side])) {
		failprintf("between-arcs: from\n");
		return NULL;
	}
	if (!grbs_angle_visible_between_arcs(grbs, tn, to, a[to_adir_idx+2])) {
		failprintf("between-arcs: to\n");
		return NULL;
	}

	return grbs_implement_arc2arc(grbs, tn, from, fromr_r, a[from_ang_side], to, tor_r, a[to_adir_idx+2], to_segi);
}

static grbs_addr_t *grbs_path_next_arc(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *from, grbs_point_t *to_pt, grbs_arc_dir_t adir, int adir_idx)
{
	int to_sid = -1;
	grbs_point_t *from_pt = addr_point(from);
	double a[4];
	double to_r = to_pt->copper + GRBS_MAX(to_pt->clearance, tn->clearance) + tn->copper; /* precise centerline radius around 'to' on the innermost possible orbit */

	assert(grbs_adir_is_convex(adir));

	if (addr_is_incident(from)) { /* coming from an incident attachment */
		grbs_addr_t to_addr, *res;
		double to_ang;

		if (grbs_adir_is_convex(adir)) {
			if (grbs_bicycle_angles(from_pt->x, from_pt->y, 0, to_pt->x, to_pt->y, to_r, a, 0) != 0) {
				failprintf("bicycle angles\n");
				return NULL;
			}
			to_ang = a[adir_idx];
		}
		else
			to_ang = atan2(from_pt->y - to_pt->y, from_pt->x - to_pt->x); /* incident-target-angle */

		to_sid = grbs_get_seg_idx(grbs, to_pt, to_ang, 1);
		if (to_sid < 0) {
			failprintf("no seg\n");
			return NULL;
		}

		/* iterate with to_addr over possible orbits from in to out */
		for(to_addr.obj.arc = gdl_first(&to_pt->arcs[to_sid]); to_addr.obj.arc != NULL; to_addr.obj.arc = gdl_next(&to_pt->arcs[to_sid], to_addr.obj.arc)) {
			to_addr.type = ADDR_ARC_CONVEX | ADDR_ARC_START;
			res = path_find_inc2arc_convex(grbs, tn, from_pt, &to_addr, adir, to_sid);
			if (res != NULL)
				return res;
			to_addr.type = ADDR_ARC_CONVEX | ADDR_ARC_END;
			res = path_find_inc2arc_convex(grbs, tn, from_pt, &to_addr, adir, to_sid);
			if (res != NULL)
				return res;
		}
	}
	else { /* orbit-to-orbit */
		grbs_addr_t to_addr, *res;
		int crossbelt = (from->obj.arc->new_adir > 0) ? (grbs_adir_is_CCW(adir)) : (!grbs_adir_is_CCW(adir));
		double fromR = ((from->type & 0x0F) == ADDR_ARC_CONVEX) ? from->obj.arc->new_r : 0;
		double toR = grbs_adir_is_convex(adir) ? to_r : 0;
		grbs_addr_type_t to_curving = ADDR_ARC_CONVEX;

		assert(from->type & ADDR_ARC_END);

		if (grbs_bicycle_angles(from_pt->x, from_pt->y, fromR, to_pt->x, to_pt->y, toR, a, crossbelt) != 0) {
			failprintf("bicycle angles\n");
			return NULL;
		}
		to_sid = grbs_get_seg_idx(grbs, to_pt, a[adir_idx+2], 1);
		if (to_sid < 0) {
			failprintf("no seg\n");
			return NULL;
		}

		/* iterate with to_addr over possible orbits from in to out */
		for(to_addr.obj.arc = gdl_first(&to_pt->arcs[to_sid]); to_addr.obj.arc != NULL; to_addr.obj.arc = gdl_next(&to_pt->arcs[to_sid], to_addr.obj.arc)) {
			to_addr.type = to_curving | ADDR_ARC_START;
			res = path_find_arc2arc(grbs, tn, from, &to_addr, adir, to_sid);
			if (res != NULL)
				return res;
			to_addr.type = to_curving | ADDR_ARC_END;
			res = path_find_arc2arc(grbs, tn, from, &to_addr, adir, to_sid);
			if (res != NULL)
				return res;
		}
	}

	failprintf("invalid case\n");
	grbs_clean_unused_sentinel_seg(grbs, to_pt, to_sid, 1); /* remove sentinel that might be left behind from grbs_get_seg_idx() on to_pt */
	return NULL;
}

static grbs_addr_t *grbs_path_next_vconcave(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *from, grbs_point_t *to_pt)
{
	grbs_arc_t *arc;

	/* always accept: the first time geometry cna be checked will be when a
	   list of VCONCAVEs is terminated by a real address; then we will have a line
	   between the last non-VCONCAVE and the first non-VCONCAVE to check */

	arc = grbs_arc_new(grbs, to_pt, 0, 0, 0, 0);
	arc->vconcave = 1;
	return grbs_addr_new(grbs, ADDR_ARC_VCONCAVE, arc);
}

static grbs_addr_t *grbs_path_next_(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *from, grbs_point_t *to_pt, grbs_arc_dir_t adir)
{
	if ((from->type & 0x0F) == ADDR_ARC_VCONCAVE)
		from = from->last_real;

	if (adir == GRBS_ADIR_VCONCAVE)
		return grbs_path_next_vconcave(grbs, tn, from, to_pt);

	if (grbs_adir_is_convex(adir))
		return grbs_path_next_arc(grbs, tn, from, to_pt, adir, adir);

	/* if not the above two, we are going into to_pt as incident line */

	/* coming from an incident -> inc-to-inc */
	if (addr_is_incident(from))
		return path_find_inc2inc(grbs, tn, from, to_pt);

	/* exiting from an arc, going into a point */
	return path_find_arc2inc(grbs, tn, from, to_pt, from->obj.arc->segi);
}

grbs_addr_t *grbs_path_next(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *from, grbs_point_t *to_pt, grbs_arc_dir_t adir)
{
	grbs_addr_t *res = grbs_path_next_(grbs, tn, from, to_pt, adir);

	if (res != NULL) {
		if ((from->type & 0x0F) == ADDR_ARC_VCONCAVE)
			res->last_real = from->last_real;
		else
			res->last_real = from;
	}

	return res;
}

grbs_addr_t *grbs_path_next_to_addr(grbs_t *grbs, grbs_2net_t *tn, grbs_addr_t *from, grbs_addr_t *to, int adir_da_sign)
{
	grbs_addr_t *res = NULL;
	grbs_arc_dir_t d1, d2;

	switch(to->type & 0x0F) {
		case ADDR_ARC_CONVEX:
			/* which direction to prefer first depends on arc intended da direction */
			if (adir_da_sign > 0) {
				d1 = GRBS_ADIR_CONVEX_CW;
				d2 = GRBS_ADIR_CONVEX_CCW;
			}
			else {
				d1 = GRBS_ADIR_CONVEX_CCW;
				d2 = GRBS_ADIR_CONVEX_CW;
			}
			res = path_find_arc2arc(grbs, tn, from, to, d1, to->obj.arc->segi);
			if (res == NULL)
				res = path_find_arc2arc(grbs, tn, from, to, d2, to->obj.arc->segi);
			break;
		case ADDR_POINT:
			res = path_find_arc2inc(grbs, tn, from, to->obj.pt, from->obj.arc->segi);
			break;
	}

	if (res != NULL) {
		if ((from->type & 0x0F) == ADDR_ARC_VCONCAVE)
			res->last_real = from->last_real;
		else
			res->last_real = from;
	}

	return res;
}
