/*  libgrbs - geometric rubber band sketch model
    Copyright (C) 2025  Tibor 'Igor2' Palinkas
    (Supported by NLnet NGI0 Entrust in 2024)

    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., 31 Milk Street, # 960789 Boston, MA 02196 USA

    Contact:
      Project page: http://repo.hu/projects/libgrbs
      lead developer: http://repo.hu/projects/pcb-rnd/contact.html
*/

#define GRBS_ROUTE_MODIFY_TRACE

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

static void mkaddr(grbs_addr_t *dst, grbs_arc_t *arc, int end)
{
	if (arc->r == 0) {
		/* incident */
		dst->type = ADDR_POINT;
	dst->obj.pt = arc->parent_pt;
	}
	else {
		dst->type = (end ? ADDR_ARC_END : ADDR_ARC_START) | ADDR_ARC_CONVEX;
		dst->obj.arc = arc->link_point.prev;
	}
}

static void mknew(grbs_arc_t *arc)
{
	grbs_arc_t *under;

/*		TODO("it is not clear why we are makinf this for the arc we mark unused and not on the arc under it");*/
	arc->in_use = 0;
	arc->new_in_use = 1;
	arc->new_r = arc->r;
	arc->new_sa = arc->sa;
	arc->new_da = arc->da;
	arc->new_adir = arc->da > 0 ? +1 : -1;

	under = arc->link_point.prev;
	if ((under != NULL) && (under->in_use == 0)) {
		/* sentinel is under -  */
/*		TODO("we shouldn't mess with this - test with pcb-rnd |```| case");*/
		under->new_adir = arc->new_adir;
	}
}

static void reverse_arc_new(grbs_arc_t *arc)
{
	if (arc->new_da != 0) {
		double sa = arc->new_sa, da = arc->new_da;
		arc->new_sa = sa + da;
		arc->new_da = -da;
	}
	arc->new_adir = -arc->new_adir;
}

static void reverse_arc(grbs_arc_t *arc)
{
	if (arc->da != 0) {
		double sa = arc->sa, da = arc->da;
		arc->sa = sa + da;
		arc->da = -da;
	}
}

/* Detect arc that became concave: outgoing lines cross; detach them */
static int arc_fix_became_concave(grbs_t *grbs, grbs_arc_t *arc)
{
	if (fabs(arc->da) > GRBS_PI) {
		grbs_line_t *la = arc->sline, *lb = arc->eline;
		g2d_cline_t cla, clb;

		cla.p1.x = la->x1; cla.p1.y = la->y1; cla.p2.x = la->x2; cla.p2.y = la->y2;
		clb.p1.x = lb->x1; clb.p1.y = lb->y1; clb.p2.x = lb->x2; clb.p2.y = lb->y2;
		if (g2d_isc_cline_cline(&cla, &clb)) {
			grbs_arc_t *ap = arc->link_2net.prev, *an = arc->link_2net.next;
			grbs_line_t *lin;

			/* lin is the line we are going to preserve, going from ap to an */
			lin = arc->eline;

			/* remove lin's startpoint from arc's endpoint so arc removal doesn't ruin it */
			arc->eline = NULL;
			lin->a1 = NULL;

			grbs_force_detach(grbs, arc, 0);

			/* rebind lin's startpoint to ap's endpoint */
			lin->a1 = ap;
			ap->eline = lin;

			/* do this twice because one change may slightly change the other end as well */
			if (an != NULL) grbs_angle_update(grbs, an);
			if (ap != NULL) grbs_angle_update(grbs, ap);
			if (an != NULL) grbs_angle_update(grbs, an);
			if (ap != NULL) grbs_angle_update(grbs, ap);

			/* need to recompute lin's end coords because angles changed */
			grbs_line_unreg(grbs, lin);
			grbs_line_attach(grbs, lin, ap, 1);
			grbs_line_attach(grbs, lin, an, 2);
			grbs_line_bbox(lin);
			grbs_line_reg(grbs, lin);

			return 1;
		}
	}
	return 0; /* arc doesn't curve back so lines can't cross */
}

int grbs_mod_split_line(grbs_t *grbs, grbs_line_t *line, grbs_point_t *pt, int side)
{
	grbs_arc_dir_t dir;
	grbs_arc_t *new_arc, *arc1, *arc2;
	grbs_addr_t a1 = {0}, a2 = {0}, *ar, *ar2;
	grbs_2net_t *tn;
	grbs_line_t *new_line;

	dir = (side > 0) ? GRBS_ADIR_CONVEX_CW : GRBS_ADIR_CONVEX_CCW;
	tn = grbs_arc_parent_2net(line->a1);

	/* plan first route segment */
	arc2 = line->a2;
	reverse_arc(arc2);

	mkaddr(&a2, arc2, 1);
	mknew(arc2);

	/* since we are routing backward from arc2's startpoint (and not endpoint)
	   we need to reverse the arc temporarily */
	if ((a2.type & 0x0F) != ADDR_POINT) {
		a2.obj.arc->new_adir = arc2->new_adir;
		reverse_arc_new(a2.obj.arc);
		a2.obj.arc->new_adir = -a2.obj.arc->new_adir;
		a2.obj.arc->new_r = arc2->r;
		a2.obj.arc->new_sa = arc2->sa;
	}

	ar2 = grbs_path_next(grbs, tn, &a2, pt, dir);
	if (ar2 == NULL) {
		tprintf("split_line: failed to 'next' (#1) from arc2 to pt\n");
		arc2->new_in_use = 0;
		reverse_arc(arc2);
		arc2->in_use = 1;
		return -1;
	}

	/* plan second route segment */
	arc1 = line->a1;
	mkaddr(&a1, arc1, 1);
	mknew(arc1);
	if ((a1.type & 0x0F) != ADDR_POINT) {
		a1.obj.arc->new_adir = arc1->new_adir;
		a1.obj.arc->new_r = arc1->r;
		a1.obj.arc->new_sa = arc1->sa;
		a1.obj.arc->new_da = arc1->da;
	}

	ar = grbs_path_next_to_addr(grbs, tn, ar2, &a1, (arc1->da < 0) ? +1 : -1);
	if (ar == NULL) {
		tprintf("split_line: failed to 'next' (#2) from pt to arc1\n");
		grbs_addr_free_last(grbs); /* ar2 */
		arc2->new_in_use = arc1->new_in_use = 0;
		reverse_arc(arc2);
		arc2->in_use = arc1->in_use = 1;
		return -1;
	}


	/* realize the new arc */
	new_arc = grbs_path_realize_(grbs, tn, ar2, 1, 0, line->a1);
	arc2->in_use = arc1->in_use = 1; /* restore end arcs */
	arc2->new_in_use = arc1->new_in_use = 0;

	if ((a1.type & 0x0F) != ADDR_POINT) {
		double ea = arc1->sa, sa = a1.obj.arc->new_sa;

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

		/* compute delta so that it matches original direction */
		if (arc1->da < 0) {
			if (ea < sa)
				ea += 2 * GRBS_PI;
			arc1->da = sa - ea;
		}
		else {
			if (ea > sa)
				sa += 2 * GRBS_PI;
			arc1->da = sa - ea;
		}
	}
	if ((a2.type & 0x0F) != ADDR_POINT) {
		/* reverse arc2 back, see above the first reverse_arc_new() call */
		arc2->da = a2.obj.arc->new_da;
		reverse_arc(arc2);
	}

	new_line = new_arc->sline;
	arc1->eline = line;

	/* accurate angles with line ends connected */
	grbs_angle_update(grbs, line->a1);
	grbs_angle_update(grbs, new_line->a2);
	grbs_line_attach(grbs, line, arc1, 1);
	grbs_line_attach(grbs, line, new_arc, 2);
	grbs_line_attach(grbs, new_line, new_arc, 1);
	grbs_line_attach(grbs, new_line, arc2, 2);

	grbs_line_unreg(grbs, line);
	grbs_line_unreg(grbs, new_line);

	grbs_line_bbox(new_line);
	grbs_line_bbox(line);
	grbs_line_reg(grbs, new_line);
	grbs_line_reg(grbs, line);

	grbs_addr_free_last(grbs);
	grbs_addr_free_last(grbs);

	/* detect arcs becoming concave */
	if (arc_fix_became_concave(grbs, arc1)) {
		tprintf(" CONCAVE arc1 removed\n");
	}
	if (arc_fix_became_concave(grbs, arc2)) {
		tprintf(" CONCAVE arc2 removed\n");
	}

assert(new_arc->in_use);
	return 0;
}
