/*
 *                            COPYRIGHT
 *
 *  pcb-rnd, interactive printed circuit board design - Rubber Band Stretch Router
 *  Copyright (C) 2024,2025 Tibor 'Igor2' Palinkas
 *  (Supported by NLnet NGI0 Entrust in 2024 and 2025)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; 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/pcb-rnd
 *    lead developer: http://repo.hu/projects/pcb-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 */

#include <math.h>
#include <genht/hash.h>
#include <librnd/core/safe_fs.h>
#include <librnd/core/math_helper.h>
#include <librnd/core/rnd_conf.h>
#include <librnd/hid/hid_inlines.h>
#include <librnd/hid/grid.h>
#include <libgrbs/route.h>
#include <libgrbs/debug.h>
#include <libgrbs/geo.h>
#include "obj_pstk_inlines.h"
#include "obj_subc_list.h"
#include "layer_ui.h"
#include "draw.h"
#include "draw_wireframe.h"

static const char rbs_routing_map_cookie[] = "rbs_routing map.c";

static void setup_ui_layer(rbsr_map_t *rbs);

/* Create center point, two endpoints and blocking lines for a round cap
   line shaped padstack in the grbs model */
RND_INLINE void make_point_from_rline_pstk(rbsr_map_t *rbs, pcb_pstk_t *ps, pcb_pstk_shape_t *shp, double coprad, rnd_coord_t clr)
{
	rnd_coord_t x, y, xc, yc;
	grbs_point_t *pt, *pt2, *cpt;
	grbs_line_t *line;
	grbs_2net_t *tn;

	x = ps->x + shp->data.line.x1;
	y = ps->y + shp->data.line.y1;
	pt = grbs_point_new(&rbs->grbs, RBSR_R2G(x), RBSR_R2G(y), RBSR_R2G(coprad), RBSR_R2G(clr));
	pt->user_data = ps;

	x = ps->x + shp->data.line.x2;
	y = ps->y + shp->data.line.y2;
	pt2 = grbs_point_new(&rbs->grbs, RBSR_R2G(x), RBSR_R2G(y), RBSR_R2G(coprad), RBSR_R2G(clr));
	pt2->user_data = ps;


	/* add the center point for incident lines */
	xc = ps->x + rnd_round((shp->data.line.x1 + shp->data.line.x2)/2);
	yc = ps->y + rnd_round((shp->data.line.y1 + shp->data.line.y2)/2);
	cpt = grbs_point_new(&rbs->grbs, RBSR_R2G(xc), RBSR_R2G(yc), RBSR_R2G(coprad), RBSR_R2G(clr));
	cpt->user_data = ps;
	htpp_set(&rbs->term4incident, ps, cpt);

	tn = grbs_2net_new(&rbs->grbs, 0, 0);
	line = grbs_line_realize(&rbs->grbs, tn, pt, cpt);
	line->immutable = 1;
	line->RBSR_WIREFRAME_COPIED_BACK = 1; /* pretend the line is already copied back so that install.c won't create it */

	tn = grbs_2net_new(&rbs->grbs, 0, 0);
	line = grbs_line_realize(&rbs->grbs, tn, pt2, cpt);
	line->immutable = 1;
	line->RBSR_WIREFRAME_COPIED_BACK = 1; /* pretend the line is already copied back so that install.c won't create it */
}

/* Create a grbs corner pt and a blocking line to the center for a square cap */
RND_INLINE void make_point_from_qline_corner(rbsr_map_t *rbs, pcb_pstk_t *ps, grbs_point_t *cpt, double x, double y, rnd_coord_t clr)
{
	grbs_point_t *pt;
	grbs_line_t *line;
	grbs_2net_t *tn;

	pt = grbs_point_new(&rbs->grbs, RBSR_R2G(x), RBSR_R2G(y), 1, RBSR_R2G(clr));
	pt->user_data = ps;

	tn = grbs_2net_new(&rbs->grbs, 0, 0);
	line = grbs_line_realize(&rbs->grbs, tn, pt, cpt);
	line->immutable = 1;
	line->RBSR_WIREFRAME_COPIED_BACK = 1; /* pretend the line is already copied back so that install.c won't create it */
}

/* Create center point, four corner points and blocking lines for a square cap
   line shaped padstack in the grbs model */
RND_INLINE void make_point_from_qline_pstk(rbsr_map_t *rbs, pcb_pstk_t *ps, pcb_pstk_shape_t *shp, double coprad, rnd_coord_t clr)
{
	double vx, vy, nx, ny, len, x, y;
	rnd_coord_t xc, yc;
	grbs_point_t *cpt;


	/* add the center point for incident lines */
	xc = ps->x + rnd_round((shp->data.line.x1 + shp->data.line.x2)/2);
	yc = ps->y + rnd_round((shp->data.line.y1 + shp->data.line.y2)/2);
	cpt = grbs_point_new(&rbs->grbs, RBSR_R2G(xc), RBSR_R2G(yc), RBSR_R2G(coprad), RBSR_R2G(clr));
	cpt->user_data = ps;
	htpp_set(&rbs->term4incident, ps, cpt);

	vx = shp->data.line.x2 - shp->data.line.x1;
	vy = shp->data.line.y2 - shp->data.line.y1;
	len = vx*vx + vy*vy;
	if (len == 0) {
		rnd_message(RND_MSG_ERROR, "Invalid zero length square cap line shape in padstack at %$mm;%$mm\n", ps->x, ps->y);
		return;
	}
	len = sqrt(len);
	vx /= len;
	vy /= len;
	nx = -vy;
	ny = vx;

	x = ps->x + shp->data.line.x1 + (-vx+nx) * coprad;
	y = ps->y + shp->data.line.y1 + (-vy+ny) * coprad;
	make_point_from_qline_corner(rbs, ps, cpt, x, y, clr);

	x = ps->x + shp->data.line.x1 + (-vx-nx) * coprad;
	y = ps->y + shp->data.line.y1 + (-vy-ny) * coprad;
	make_point_from_qline_corner(rbs, ps, cpt, x, y, clr);

	x = ps->x + shp->data.line.x2 + (+vx+nx) * coprad;
	y = ps->y + shp->data.line.y2 + (+vy+ny) * coprad;
	make_point_from_qline_corner(rbs, ps, cpt, x, y, clr);

	x = ps->x + shp->data.line.x2 + (+vx-nx) * coprad;
	y = ps->y + shp->data.line.y2 + (+vy-ny) * coprad;
	make_point_from_qline_corner(rbs, ps, cpt, x, y, clr);
}

/* Create a grbs point (if round; otherwise a system of grbs points and blocking
   lines) for a padstack for a specific layer */
RND_INLINE void make_point_from_pstk_shape(rbsr_map_t *rbs, pcb_pstk_t *ps, pcb_pstk_shape_t *shp, pcb_layer_t *ly)
{
	pcb_board_t *pcb = rbs->pcb;
	rnd_coord_t clr = obj_pstk_get_clearance(pcb, ps, ly);
	rnd_coord_t copdia, x, y, xc, yc;
	double coprad;
	grbs_line_t *line;
	grbs_point_t *pt, *cpt;
	grbs_2net_t *tn;
	unsigned int tries = 0, n;


	retry:;
	switch(shp->shape) {
		case PCB_PSSH_LINE:
			coprad = (double)shp->data.line.thickness / 2.0;
			if (shp->data.line.square)
				make_point_from_qline_pstk(rbs, ps, shp, coprad, clr);
			else
				make_point_from_rline_pstk(rbs, ps, shp, coprad, clr);
			break;

		case PCB_PSSH_CIRC:
			coprad = (double)shp->data.circ.dia / 2.0;
			x = ps->x + shp->data.circ.x;
			y = ps->y + shp->data.circ.y;
			pt = grbs_point_new(&rbs->grbs, RBSR_R2G(x), RBSR_R2G(y), RBSR_R2G(coprad), RBSR_R2G(clr));
			htpp_set(&rbs->term4incident, ps, pt);
			break;
			
		case PCB_PSSH_HSHADOW:
			copdia = 0;
			shp = pcb_pstk_shape_mech_at(pcb, ps, ly);
			if (shp == NULL)
				break; /* no shape on this layer */
			tries++;
			if (tries < 2)
				goto retry;

			rnd_message(RND_MSG_ERROR, "rbs_routing: failed to figure padstack shape around %mm;%mm in make_point_from_pstk_shape(): infinite recursion\n", ps->x, ps->y);
			break;

		case PCB_PSSH_POLY:
			copdia = 0;

			/* figure center */
			xc = yc = 0;
			for(n = 0; n < shp->data.poly.len; n++) {
				xc += shp->data.poly.x[n];
				yc += shp->data.poly.y[n];
			}

			/* add the center point for incident lines */
			xc = ps->x + rnd_round((double)xc/(double)shp->data.poly.len);
			yc = ps->y + rnd_round((double)yc/(double)shp->data.poly.len);
			cpt = grbs_point_new(&rbs->grbs, RBSR_R2G(xc), RBSR_R2G(yc), RBSR_R2G(copdia/2.0), RBSR_R2G(clr));
			cpt->RBSR_INCIDENT_ONLY = 1; /* center of the pad can be hit by an incident line but there's no sense in routing aronud it, route around corners */
			cpt->user_data = ps;
			htpp_set(&rbs->term4incident, ps, cpt);

			/* add corner points and radial blocking lines */
			for(n = 0; n < shp->data.poly.len; n++) {
				x = ps->x + shp->data.poly.x[n];
				y = ps->y + shp->data.poly.y[n];
				pt = grbs_point_new(&rbs->grbs, RBSR_R2G(x), RBSR_R2G(y), 1, RBSR_R2G(clr));
				pt->user_data = ps;
				tn = grbs_2net_new(&rbs->grbs, 1, 1);
				line = grbs_line_realize(&rbs->grbs, tn, cpt, pt);
				line->immutable = 1;
				line->RBSR_WIREFRAME_COPIED_BACK = 1; /* pretend the line is already copied back so that install.c won't create it */
			}
			break;
	}
}

RND_INLINE int map_pstks(rbsr_map_t *rbs, pcb_data_t *data)
{
	pcb_board_t *pcb = rbs->pcb;
	pcb_layer_t *ly = pcb_get_layer(pcb->Data, rbs->lid);
	pcb_pstk_t *ps;

	assert(ly != NULL);

	/* add all padstacks as points */
	for(ps = padstacklist_first(&data->padstack); ps != NULL; ps = ps->link.next) {
		pcb_pstk_shape_t *shp;
		shp = pcb_pstk_shape_at(pcb, ps, ly); /* prefer copper shape */
		if (shp == NULL)
			shp = pcb_pstk_shape_mech_at(pcb, ps, ly);
		if (shp != NULL)
			make_point_from_pstk_shape(rbs, ps, shp, ly);
	}
	

	TODO("also add all non-padstack terminals; then rename the func");
	return 0;
}

/* When a new currarc is created, this function looks back if there is a
   pending prevline from prevarc and finalizes it. Also updates prevarc. */
static int bind_prev_line(rbsr_map_t *rbs, grbs_arc_t **prevarc, grbs_line_t **prevline, grbs_arc_t *currarc)
{
	if (*prevline != NULL) {
		assert(*prevarc != NULL); assert(currarc != NULL);

		grbs_line_attach(&rbs->grbs, *prevline, *prevarc, 1);
		grbs_line_attach(&rbs->grbs, *prevline, currarc, 2);
		grbs_line_bbox(*prevline);
		grbs_line_reg(&rbs->grbs, *prevline);
	}
	*prevarc = currarc;
	return 0;
}

/* jump into an incident as next step: create r==0 arc there, hook up a
   prevline between prevard and current arc */
static int map_2nets_mkincident(rbsr_map_t *rbs, grbs_2net_t *tn, grbs_point_t *pt, pcb_any_obj_t *obj, grbs_arc_t **prevarc, grbs_line_t **prevline)
{
	grbs_arc_t *a;

	a = grbs_arc_new(&rbs->grbs, pt, 0, 0, 0, 0);
	gdl_append(&tn->arcs, a, link_2net);
	a->user_data = obj;
	a->in_use = 1;

	bind_prev_line(rbs, prevarc, prevline, a);

	return 0;
}

/* next object is a terminal, draw incident line into it */
static int map_2nets_incident(rbsr_map_t *rbs, grbs_2net_t *tn, pcb_any_obj_t *obj, grbs_arc_t **prevarc, grbs_line_t **prevline)
{
	grbs_point_t *pt;

	if (obj == NULL) {
		rnd_message(RND_MSG_ERROR, "rbs map internal error: null object (incident object)\n");
		return -2;
	}

	pt = htpp_get(&rbs->term4incident, obj);
	if (pt == NULL) {
		rnd_message(RND_MSG_ERROR, "rbs map internal error: null object (incident point)\n");
		return -4;
	}

	return map_2nets_mkincident(rbs, tn, pt, obj, prevarc, prevline);
}

/* How far the target point could be, +- manhattan distance */
#define FIND_PT_DELTA   2
#define FIND_PT_DELTA2  RBSR_R2G(FIND_PT_DELTA * FIND_PT_DELTA)

/* return already existing point for cx;cy - low level */
RND_INLINE grbs_point_t *rbsr_find_point_(rbsr_map_t *rbs, rnd_coord_t cx_, rnd_coord_t cy_, double bestd2, double delta)
{
	double cx = RBSR_R2G(cx_), cy = RBSR_R2G(cy_);
	grbs_point_t *pt, *best = NULL;
	grbs_rtree_it_t it;
	grbs_rtree_box_t bbox;

	bbox.x1 = cx - delta;
	bbox.y1 = cy - delta;
	bbox.x2 = cx + delta;
	bbox.y2 = cy + delta;

	for(pt = grbs_rtree_first(&it, &rbs->grbs.point_tree, &bbox); pt != NULL; pt = grbs_rtree_next(&it)) {
		double dx = cx - pt->x, dy = cy - pt->y, d2 = dx*dx + dy*dy;
		if (d2 < bestd2) {
			bestd2 = d2;
			best = pt;
		}
	}

	return best;
}

grbs_point_t *rbsr_find_point_by_center(rbsr_map_t *rbs, rnd_coord_t cx, rnd_coord_t cy)
{
	return rbsr_find_point_(rbs, cx, cy, FIND_PT_DELTA2+1, FIND_PT_DELTA);
}

grbs_point_t *rbsr_find_point(rbsr_map_t *rbs, rnd_coord_t cx, rnd_coord_t cy)
{
	return rbsr_find_point_(rbs, cx, cy, RND_COORD_MAX, FIND_PT_DELTA);
}

grbs_point_t *rbsr_find_point_thick(rbsr_map_t *rbs, rnd_coord_t cx, rnd_coord_t cy, rnd_coord_t delta)
{
	return rbsr_find_point_(rbs, cx, cy, RND_COORD_MAX, delta);
}

/* got next non-incident object for a 2net */
static int map_2nets_intermediate(rbsr_map_t *rbs, grbs_2net_t *tn, pcb_any_obj_t *prev, pcb_any_obj_t *obj, grbs_arc_t **prevarc, grbs_line_t **prevline, int reversed)
{
	grbs_arc_t *a;
	grbs_point_t *pt;
	pcb_arc_t *arc;
	double r, da, sa;

	if (obj == NULL) {
		rnd_message(RND_MSG_ERROR, "rbs map internal error: NULL intermediate object in 2net\n");
		return -32;
	}

	switch(obj->type) {
		case PCB_OBJ_LINE:
			if ((prev != NULL) && (prev->type == PCB_OBJ_LINE)) {
				rnd_message(RND_MSG_ERROR, "rbs map internal error: line-line in a 2net\n");
				return -8;
			}
			*prevline = grbs_line_new(&rbs->grbs);
			(*prevline)->user_data = obj;
			(*prevline)->RBSR_REVERSED = reversed;
			htpp_set(&rbs->robj2grbs, obj, *prevline);
			break;
		case PCB_OBJ_ARC:
			if ((prev != NULL) && (prev->type == PCB_OBJ_ARC)) {
				rnd_message(RND_MSG_ERROR, "rbs map internal error: arc-arc in a 2net\n");
				return -16;
			}

			arc = (pcb_arc_t *)obj;
			pt = rbsr_find_point_by_center(rbs, arc->X, arc->Y);
			if (pt == NULL) {
				/* An arc without a point in the center; it's either really just an arc
				   out in the wilderness or it's curving around some non-padstack/non-terminal
				   object (floating 2net) that has not yet been mapped. Create the
				   point - if a loating 2net also needs it later it will update sizes
				   of existing points. */
				pt = grbs_point_new(&rbs->grbs, RBSR_R2G(arc->X), RBSR_R2G(arc->Y), 1, 1);
			}
			if (pt == NULL) {
				rnd_message(RND_MSG_ERROR, "rbs map internal error: failed to create point for arc center at %$mm %$mm\n", arc->X, arc->Y);
				return -64;
			}

			sa = -(arc->StartAngle - 180.0) / RND_RAD_TO_DEG;
			da = -(arc->Delta / RND_RAD_TO_DEG);
			r = RBSR_R2G(arc->Height);

			/* the pcb object's arc may not be oriented to suit the sequential
			   order; this is detected by prev line's endpoint matching not the
			   starting endpoint (sa) of the arc but the ending endpoint (sa+da);
			   in this case swap arc orientation */
			if (*prevline != NULL) {
				double e2x, e2y, l1x, l1y, l2x, l2y, ea;
				pcb_line_t *line = (*prevline)->user_data;

				assert(line->type == PCB_OBJ_LINE);

				ea = sa+da;
				l1x = line->Point1.X;
				l1y = line->Point1.Y;
				l2x = line->Point2.X;
				l2y = line->Point2.Y;
				e2x = arc->X + cos(ea) * arc->Height;
				e2y = arc->Y + sin(ea) * arc->Height;

				/* if either endpoint of the line is too close to the sa+da end of
				   the arc we need to swap angles */
				if ((rcrdeq(l1x, e2x) && rcrdeq(l1y, e2y)) || (rcrdeq(l2x, e2x) && rcrdeq(l2y, e2y))) {
					sa = ea;
					da = -da;
				}
			}

			a = grbs_arc_new(&rbs->grbs, pt, 0, r, sa, da);
			assert(a != NULL);
			gdl_append(&tn->arcs, a, link_2net);
			a->user_data = obj;
			a->RBSR_REVERSED = reversed;
			a->in_use = 1;
			a->copper = tn->copper;
			a->clearance = tn->clearance;
			grbs_arc_bbox(a);
			grbs_arc_reg(&rbs->grbs, a);

			htpp_set(&rbs->robj2grbs, arc, a);
			bind_prev_line(rbs, prevarc, prevline, a);
			break;
		default:
			return -128;
	}
	return 0;
}

/* finalize a 2net: make sure incident angles are properly set */
RND_INLINE int tn_postproc(rbsr_map_t *rbs, grbs_2net_t *tn)
{
	grbs_arc_t *last, *first;

	first = gdl_first(&tn->arcs);
	if (first == NULL)
		return -256;
	grbs_inc_ang_update(&rbs->grbs, first);

	last = gdl_last(&tn->arcs);
	if ((first != last) && (last->r == 0))
		grbs_inc_ang_update(&rbs->grbs, last);

	return 0;
}

/* for sorting arcs around a parent point to figure their segments */
static int cmp_arc_ang(const void *A, const void *B)
{
	const grbs_arc_t * const *a = A;
	const grbs_arc_t * const *b = B;

	if ((*a)->r == (*b)->r)
		return (fabs((*a)->da) > fabs((*b)->da)) ? -1 : +1;
	return (fabs((*a)->r) < fabs((*b)->r)) ? -1 : +1;
}

/* all arcs are in seg[0]; sort them into segments and insert sentinels */
RND_INLINE int map_2nets_postproc_point(rbsr_map_t *rbs, grbs_point_t *pt, vtp0_t *tmp)
{
	grbs_arc_t *a, *snt;
	long n;
	int segid, res = 0;

#if 0
TODO("this is not correct: won't separate segments; matters only for the (*) case"
     "we also need to accept sligthly broken segments of arcs going out of range and intersect (for stretch to work)");

	tmp->used = 0;

	while((a = gdl_first(&pt->arcs[0])) != NULL) {
		gdl_remove(&pt->arcs[0], a, link_point);
		vtp0_append(tmp, a);
	}

	qsort(tmp->array, tmp->used, sizeof(void *), cmp_arc_ang);

	for(n = 0; n < tmp->used; n++) {
		a = tmp->array[n];
		segid = grbs_get_seg_idx(&rbs->grbs, pt, a->sa + a->da/2.0, 0);
		if (segid < 0) segid = grbs_get_seg_idx(&rbs->grbs, pt, a->sa, 0);
		if (segid < 0) segid = grbs_get_seg_idx(&rbs->grbs, pt, a->sa + a->da, 0);
		if (segid < 0) {
			/* need to create a new seg; we are inserting the largest arc first so
			   it is safe to create the sentinel */

			snt = grbs_new_sentinel(&rbs->grbs, pt, a->sa, a->da, &segid);
			if (snt == NULL) {
				rnd_message(RND_MSG_ERROR, "map_2nets_postproc_point(): failed to create sentinel, probably ran out of segments\n");
				res = 1;
				continue;
			}

			/* normalize sentinel angles */
			if (snt->da < 0) { /* swap endpoints so da is always positive */
				snt->sa = snt->sa + snt->da;
				snt->da = -snt->da;
			}
			if (snt->sa < 0)
				snt->sa += 2.0*GRBS_PI;
			else if (snt->sa > 2.0*GRBS_PI)
				snt->sa -= 2.0*GRBS_PI;
		}
		gdl_append(&pt->arcs[segid], a, link_point);
	}
#else
	/* insert sentinel */
	for(segid = 0; segid < GRBS_MAX_SEG; segid++) {
		a = gdl_first(&pt->arcs[segid]);
		if ((a != NULL) && a->in_use) {
			/* can't use grbs_new_sentinel() here because it tries to create a new segment */
			snt = grbs_arc_new(&rbs->grbs, pt, segid, GRBS_MAX(pt->copper, 0.0001), a->sa, a->da);
			snt->clearance = pt->clearance;
		}
	}
#endif

	return res;
}

RND_INLINE int map_2nets_postproc_points(rbsr_map_t *rbs)
{
	grbs_point_t *pt;
	grbs_rtree_it_t it;
	vtp0_t tmp = {0};
	int res = 0;

	for(pt = grbs_rtree_all_first(&it, &rbs->grbs.point_tree); pt != NULL; pt = grbs_rtree_all_next(&it))
		res |= map_2nets_postproc_point(rbs, pt, &tmp);

	vtp0_uninit(&tmp);
	return res;
}

typedef struct j2n_ctx_s {
	rbsr_map_t *rbs;
	grbs_2net_t *tn;
	grbs_arc_t *prevarc;
	grbs_line_t *prevline;
	pcb_any_obj_t *prev_obj;
	grbs_point_t *pending_start_pt; /* when a new twonet is started from a junction (point) geometry is not yet known - tune this after the first line/arc object */
	int err;
	unsigned ignore:1; /* ignore this two-net (e.g. not on target layer) */
} j2n_ctx_t;

/* Set (or verify) geometry of a twonet after encountering the first or
   subsequent trace object */
RND_INLINE void j2tn_set_geo(j2n_ctx_t *j2ctx, rnd_coord_t copdia, rnd_coord_t cleardia)
{
	double rcop, rclr;

	rcop = RBSR_R2G(copdia) / 2.0;
	rclr = RBSR_R2G(cleardia) / 2.0;
	if (j2ctx->tn->copper == 0) {
		j2ctx->tn->copper = rcop;
		j2ctx->tn->clearance = rclr;
	}
	else {
		if ((rcop != j2ctx->tn->copper) || (rclr != j2ctx->tn->clearance))
			rnd_message(RND_MSG_ERROR, "rbs_routing: two-net with variable thickness or clearance\n");
	}

	/* the twonet was really created as a point at the first junction that's
	   stored in j2ctx->pending_start_pt; in case of a floating copper twonet,
	   when creating the point the geometry is not yet known because no trace
	   object is reported yet. Now that we have a trace object, revisit the
	   first point and fix it up */
	if (j2ctx->pending_start_pt != NULL) {
		if (j2ctx->pending_start_pt->copper < rcop)
			j2ctx->pending_start_pt->copper = rcop;
		if (j2ctx->pending_start_pt->clearance < rclr)
			j2ctx->pending_start_pt->clearance = rclr;
		j2ctx->pending_start_pt = NULL;
	}
}

/* register and verify line and arc object copper width and clearance for current
   2net being built in j2ctx */
RND_INLINE void j2update_copper_clearance(j2n_ctx_t *j2ctx, pcb_any_obj_t *obj)
{

	if (obj->type == PCB_OBJ_LINE) {
		pcb_line_t *line = (pcb_line_t *)obj;
		j2tn_set_geo(j2ctx, line->Thickness, line->Clearance);
	}
	else if (obj->type == PCB_OBJ_ARC) {
		pcb_arc_t *arc = (pcb_arc_t *)obj;
		j2tn_set_geo(j2ctx, arc->Thickness, arc->Clearance);
	}
}

static void map2nets_begin(pcb_j2netmap_t *map, pcb_j2n_junc_t *junc)
{
	j2n_ctx_t *j2ctx = map->user_data;

	j2ctx->tn = grbs_2net_new(&j2ctx->rbs->grbs, 0, 0);
	j2ctx->prevarc = NULL;
	j2ctx->prevline = NULL;
	j2ctx->prev_obj = NULL;
	j2ctx->ignore = 0;
	j2ctx->pending_start_pt= NULL;
}


static void map2nets_obj(pcb_j2netmap_t *map, pcb_any_obj_t *obj, int reversed, pcb_j2n_junc_t *incident_junc)
{
	j2n_ctx_t *j2ctx = map->user_data;

	if (j2ctx->ignore)
		return;

	TODO("for stretch-moving vias/padstacks we'll probably need a separate map per layer");
	if ((obj->type & PCB_OBJ_CLASS_LAYER) == obj->type) {
		rnd_layer_id_t my_lid;
		pcb_layer_t *my_layer;

		my_layer = pcb_layer_get_real(obj->parent.layer);
		my_lid = pcb_layer2id(map->pcb->Data, my_layer);
		if (my_lid != j2ctx->rbs->lid) {
			j2ctx->ignore = 1;
			if (j2ctx->pending_start_pt != NULL) {
				grbs_point_free(&j2ctx->rbs->grbs, j2ctx->pending_start_pt);
				j2ctx->pending_start_pt = NULL;
			}
			return; /* ignore layer objects on a layer not the one we are mapping */
		}
	}

	j2update_copper_clearance(j2ctx, obj);

	if (incident_junc != NULL)
		j2ctx->err |= map_2nets_incident(j2ctx->rbs, j2ctx->tn, obj, &j2ctx->prevarc, &j2ctx->prevline);
	else
		j2ctx->err |= map_2nets_intermediate(j2ctx->rbs, j2ctx->tn, j2ctx->prev_obj, obj, &j2ctx->prevarc, &j2ctx->prevline, reversed);

	j2ctx->prev_obj = obj;
}

static void map2nets_vjunct(pcb_j2netmap_t *map, pcb_any_obj_t *obj, pcb_j2n_junc_t *junc)
{
	grbs_point_t *pt = junc->user_data;
	j2n_ctx_t *j2ctx = map->user_data;

	if (j2ctx->ignore)
		return;

	if (pt == NULL) {
		/* add the center point for incident lines */
		pt = rbsr_find_point_by_center(j2ctx->rbs, junc->key.x, junc->key.y);
		if (pt == NULL)
			pt = grbs_point_new(&j2ctx->rbs->grbs, RBSR_R2G(junc->key.x), RBSR_R2G(junc->key.y), j2ctx->tn->copper, j2ctx->tn->clearance);
		junc->user_data = pt;

		if (j2ctx->tn->copper != 0) {
			if (pt->copper < j2ctx->tn->copper)
				pt->copper = j2ctx->tn->copper;
			if (pt->clearance < j2ctx->tn->clearance)
				pt->clearance = j2ctx->tn->clearance;
		}
		else {
			/* revisit this point after the first object of the new twonet is read;
			   obj from our args can be coming from the previous twonet on a T junction */
			assert(j2ctx->pending_start_pt == NULL);
			j2ctx->pending_start_pt = pt;
		}
	}

	/* finish at incident pt */
	map_2nets_mkincident(j2ctx->rbs, j2ctx->tn, pt, obj, &j2ctx->prevarc, &j2ctx->prevline);
}


static void map2nets_end(pcb_j2netmap_t *map, pcb_j2n_junc_t *junc)
{
	j2n_ctx_t *j2ctx = map->user_data;
	if (!j2ctx->ignore)
		tn_postproc(j2ctx->rbs, j2ctx->tn);
}


RND_INLINE int map_2nets(rbsr_map_t *rbs)
{
	static const pcb_j2nets_crawl_t cr = {map2nets_begin, map2nets_obj, map2nets_vjunct, map2nets_end};
	j2n_ctx_t j2ctx = {0};
	int res;

	j2ctx.rbs = rbs;
	rbs->j2nets.user_data = &j2ctx;
	res = pcb_map_j2nets_crawl(&rbs->j2nets, &cr);
	res |= map_2nets_postproc_points(rbs);
	res |= j2ctx.err;

	/* j2ctx doesn't have any allocated fields */
	return res;
}

static int map_subc_child(rbsr_map_t *dst, pcb_data_t *data)
{
	pcb_subc_t *subc;
	int res = 0;

	for(subc = pcb_subclist_first(&data->subc); subc != NULL; subc = subc->link.next) {
		res |= map_pstks(dst, subc->data);
		res |= map_subc_child(dst, subc->data); /* subc-in-subc */
	}
	
	return res;
}

int rbsr_map_pcb(rbsr_map_t *dst, pcb_board_t *pcb, rnd_layer_id_t lid)
{
	int res;
	pcb_layer_t *ly = pcb_get_layer(pcb->Data, lid);

	dst->pcb = pcb;
	dst->lid = lid;

	if ((ly == NULL) || (ly->is_bound)) {
		rnd_msg_error("rbs_routing: failed to resolve layer\n");
		return -1;
	}

	dst->j2nets.find_rats = 0;
	dst->j2nets.grbs_law = 1;

	if (pcb_map_j2nets_init(&dst->j2nets, pcb) != 0) {
		rnd_msg_error("rbs_routing: failed to map 2-nets\n");
		return -1;
	}

	htpp_init(&dst->term4incident, ptrhash, ptrkeyeq);
	htpp_init(&dst->robj2grbs, ptrhash, ptrkeyeq);
	grbs_init(&dst->grbs);

	res = map_pstks(dst, pcb->Data);
	res |= map_subc_child(dst, pcb->Data);
	res |= map_2nets(dst);
	res |= grbs_sanity(&dst->grbs, 0);

	rbsr_map_debug_draw(dst, "rbsq0.svg");
	rbsr_map_debug_dump(dst, "rbsq0.dump");
	rbsr_map_debug_save_test(dst, "rbsq0.grbs");


	setup_ui_layer(dst);
	ly->meta.real.vis = 0;

	return res;
}


void rbsr_map_uninit(rbsr_map_t *dst)
{
	pcb_layer_t *ly = pcb_get_layer(dst->pcb->Data, dst->lid);

	ly->meta.real.vis = 1;

	htpp_uninit(&dst->term4incident);
	htpp_uninit(&dst->robj2grbs);
	pcb_map_j2nets_uninit(&dst->j2nets);
	dst->pcb = NULL;
	dst->lid = -1;

	grbs_uninit(&dst->grbs);
	pcb_uilayer_free(dst->ui_layer);
}

/*** grbs model draw code ***/

static rnd_hid_gc_t collGC;

grbs_rtree_dir_t draw_point(void *cl, void *obj, const grbs_rtree_box_t *box)
{
	grbs_point_t *pt = obj;
/*	pcb_draw_info_t *info = cl;*/
	rnd_coord_t x = RBSR_G2R(pt->x), y = RBSR_G2R(pt->y);

	if (pt->RBSR_COLL_FLAG) {
		rnd_coord_t r = RBSR_G2R(pt->copper);
		rnd_hid_set_line_width(collGC, -2);
		rnd_render->draw_arc(collGC, x, y, r, r, 0, 360);
	}
	else {
		rnd_hid_set_line_width(pcb_draw_out.fgGC, RBSR_G2R(pt->copper*2.0));
		rnd_render->draw_line(pcb_draw_out.fgGC, x, y, x, y);
	}

	rnd_hid_set_line_width(pcb_draw_out.fgGC, 1);
	pcb_draw_wireframe_line(pcb_draw_out.fgGC, x, y, x, y, RBSR_G2R(pt->copper*2.0+pt->clearance*2.0), 0);


	return rnd_RTREE_DIR_FOUND_CONT;
}

grbs_rtree_dir_t draw_line(void *cl, void *obj, const grbs_rtree_box_t *box)
{
	grbs_line_t *line = obj;
/*	pcb_draw_info_t *info = cl;*/
	rnd_coord_t x1 = RBSR_G2R(line->x1), y1 = RBSR_G2R(line->y1);
	rnd_coord_t x2 = RBSR_G2R(line->x2), y2 = RBSR_G2R(line->y2);
	grbs_2net_t *tn = NULL;
	double copper = 1, clearance = 1;
	int is_wireframe = line->RBSR_WIREFRAME_FLAG || (line->user_data == NULL);

	if (line->a1 != NULL) tn = grbs_arc_parent_2net(line->a1);
	if (line->a2 != NULL) tn = grbs_arc_parent_2net(line->a2);
	if (tn != NULL) {
		copper = tn->copper;
		clearance = tn->clearance;
	}


	if (line->RBSR_COLL_FLAG) {
		rnd_hid_set_line_width(collGC, -2);
		pcb_draw_wireframe_line(collGC, x1, y1, x2, y2, RBSR_G2R(copper*2), -1);
	}
	else if (!is_wireframe && !line->RBSR_COLL_FLAG) {
		rnd_hid_set_line_width(pcb_draw_out.fgGC, RBSR_G2R(copper*2));
		rnd_render->draw_line(pcb_draw_out.fgGC, x1, y1, x2, y2);
	}

	rnd_hid_set_line_width(pcb_draw_out.fgGC, 1);
	if (is_wireframe)
		pcb_draw_wireframe_line(pcb_draw_out.fgGC, x1, y1, x2, y2, RBSR_G2R(copper*2), -1);
	else
		pcb_draw_wireframe_line(pcb_draw_out.fgGC, x1, y1, x2, y2, RBSR_G2R(copper*2+clearance*2), -1);

	return rnd_RTREE_DIR_FOUND_CONT;
}

grbs_rtree_dir_t draw_arc(void *cl, void *obj, const grbs_rtree_box_t *box)
{
	grbs_arc_t *arc = obj;
	pcb_arc_t tmparc;
/*	pcb_draw_info_t *info = cl;*/
	rnd_coord_t cx = RBSR_G2R(arc->parent_pt->x), cy = RBSR_G2R(arc->parent_pt->y);
	rnd_coord_t r = RBSR_G2R(arc->r);
	grbs_2net_t *tn = grbs_arc_parent_2net(arc);
	double copper = 1, clearance = 1, sa, da;
	int is_wireframe = arc->RBSR_WIREFRAME_FLAG || (arc->user_data == NULL);

	if (tn != NULL) {
		copper = tn->copper;
		clearance = tn->clearance;
	}

	sa = 180.0 - (arc->sa * RND_RAD_TO_DEG);
	da = - (arc->da * RND_RAD_TO_DEG);

	tmparc.X = cx; tmparc.Y = cy;
	tmparc.Width = tmparc.Height = r;
	tmparc.StartAngle = sa; tmparc.Delta = da;

	if (arc->RBSR_COLL_FLAG) {
		rnd_hid_set_line_width(collGC, -2);
		pcb_draw_wireframe_arc_(collGC, &tmparc, RBSR_G2R(copper*2), 0);
	}
	else if (!is_wireframe) {
		rnd_hid_set_line_width(pcb_draw_out.fgGC, RBSR_G2R(copper*2));
		rnd_render->draw_arc(pcb_draw_out.fgGC, cx, cy, r, r, sa, da);
	}

	rnd_hid_set_line_width(pcb_draw_out.fgGC, 1);

	if (is_wireframe)
		pcb_draw_wireframe_arc_(pcb_draw_out.fgGC, &tmparc, RBSR_G2R(copper*2), 0);
	else
		pcb_draw_wireframe_arc_(pcb_draw_out.fgGC, &tmparc, RBSR_G2R(copper*2+clearance*2), 0);

	TODO("this should be configurable");
	/* draw arc binding to center (pt) */
	{
		rnd_render->draw_line(pcb_draw_out.fgGC, cx, cy, cx + cos(arc->sa) * r, cy + sin(arc->sa) * r);
		rnd_render->draw_line(pcb_draw_out.fgGC, cx, cy, cx + cos(arc->sa+arc->da) * r, cy + sin(arc->sa+arc->da) * r);
	}

	return rnd_RTREE_DIR_FOUND_CONT;
}

static void rbsr_plugin_draw(pcb_draw_info_t *info, const pcb_layer_t *Layer)
{
	rbsr_map_t *rbs = Layer->plugin_draw_data;
	grbs_rtree_box_t gbox;

	gbox.x1 = RBSR_R2G(info->drawn_area->X1);
	gbox.y1 = RBSR_R2G(info->drawn_area->Y1);
	gbox.x2 = RBSR_R2G(info->drawn_area->X2);
	gbox.y2 = RBSR_R2G(info->drawn_area->Y2);

	collGC = rnd_hid_make_gc();
	rnd_hid_set_line_cap(collGC, rnd_cap_round);
	rnd_render->set_color(collGC, rnd_color_red);

	rnd_render->set_color(pcb_draw_out.fgGC, &Layer->meta.real.color);

	grbs_rtree_search_any(&rbs->grbs.point_tree, &gbox, NULL, draw_point, info, NULL);
	grbs_rtree_search_any(&rbs->grbs.line_tree, &gbox, NULL, draw_line, info, NULL);
	grbs_rtree_search_any(&rbs->grbs.arc_tree, &gbox, NULL, draw_arc, info, NULL);

	TODO("stretch routing: draw collided segments separately");

	rnd_hid_destroy_gc(collGC);
}

static void setup_ui_layer(rbsr_map_t *rbs)
{
	pcb_layer_t *ly = pcb_get_layer(rbs->pcb->Data, rbs->lid);
	rbs->ui_layer = pcb_uilayer_alloc(rbs->pcb, rbs_routing_map_cookie, "rbs_routing", &ly->meta.real.color);
	rbs->ui_layer->plugin_draw = rbsr_plugin_draw;
	rbs->ui_layer->plugin_draw_data = rbs;
}

/*** map dump/draw/debug tools ***/

void grbs_draw_wires(grbs_t *grbs, FILE *f);
void grbs_dump_wires(grbs_t *grbs, FILE *f);
void grbs_draw_2net(grbs_t *grbs, FILE *f, grbs_2net_t *tn);
void grbs_dump_2net(grbs_t *grbs, FILE *f, grbs_2net_t *tn);

void rbsr_map_debug_draw(rbsr_map_t *rbs, const char *fn)
{
	FILE *f = rnd_fopen(&rbs->pcb->hidlib, fn, "w");
	if (f == NULL) {
		rnd_message(RND_MSG_ERROR, "Failed to open debug output '%s' for write\n", fn);
		return;
	}

	grbs_draw_zoom = 0.001;

	grbs_draw_begin(&rbs->grbs, f);
	grbs_draw_points(&rbs->grbs, f);
	grbs_draw_wires(&rbs->grbs, f);
	grbs_draw_end(&rbs->grbs, f);

	grbs_draw_zoom = 1;

	fclose(f);
}

void rbsr_map_debug_dump(rbsr_map_t *rbs, const char *fn)
{
	FILE *f = rnd_fopen(&rbs->pcb->hidlib, fn, "w");
	if (f == NULL) {
		rnd_message(RND_MSG_ERROR, "Failed to open debug output '%s' for write\n", fn);
		return;
	}

	grbs_dump_points(&rbs->grbs, f);
	grbs_dump_wires(&rbs->grbs, f);

	fclose(f);
}


void rbsr_map_debug_save_test(rbsr_map_t *rbs, const char *fn)
{
	FILE *f = rnd_fopen(&rbs->pcb->hidlib, fn, "w");
	if (f == NULL) {
		rnd_message(RND_MSG_ERROR, "Failed to open debug output '%s' for write\n", fn);
		return;
	}

	grbs_dump_test(&rbs->grbs, f, 0.001);

	fclose(f);
}

void rbsr_ui_save(rbsr_map_t *rbs)
{
	/* save original grid and go gridless */
	rbs->grid_save = rnd_conf.editor.grid;
	rnd_hid_set_grid(&rbs->pcb->hidlib, 1, rnd_false, 0, 0);
}

void rbsr_ui_restore(rbsr_map_t *rbs)
{
	rnd_hid_set_grid(&rbs->pcb->hidlib, rbs->grid_save, rnd_false, 0, 0);
}

