/*
 *                            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)
 *
 *  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 <libgrbs/route.h>
#include <libgrbs/geo.h>
#include <libgrbs/force_attach.h>
#include "conf_core.h"
#include "search.h"
#include "remove.h"
#include "install.h"
#include "route_helper.h"


static int coll_ingore_tn_line(grbs_t *grbs, grbs_2net_t *tn, grbs_line_t *l)
{
	rnd_trace("ign coll line\n");
	return 1;
}

static int coll_ingore_tn_arc(grbs_t *grbs, grbs_2net_t *tn, grbs_arc_t *a)
{
	rnd_trace("ign coll arc\n");
	return 1;
}

static int coll_ingore_tn_point(grbs_t *grbs, grbs_2net_t *tn, grbs_point_t *p)
{
	rnd_trace("ign coll pt\n");
	return 1;
}

RND_INLINE void stretch_get_addr(rbsr_stretch_t *rbss, grbs_addr_t *dst, grbs_arc_t *arc, grbs_arc_t **rem, int is_from)
{
	if (arc->r == 0) {
		/* incident */
		dst->type = ADDR_POINT;
		dst->obj.pt = arc->parent_pt;
	}
	else {
		dst->type = (is_from ? ADDR_ARC_END : ADDR_ARC_START) | ADDR_ARC_CONVEX;
		dst->obj.arc = arc;
		*rem = arc;
	}
	dst->last_real = NULL;
}

static int rbsr_stretch_gline_begin(rbsr_stretch_t *rbss, pcb_board_t *pcb, grbs_line_t *gl)
{
	rbsr_map_debug_draw(&rbss->map, "rbss1.svg");
	rbsr_map_debug_dump(&rbss->map, "rbss1.dump");

	rbss->map.grbs.user_data = rbss;
	rbss->map.grbs.ignore_arc_incident_cross = 1;
	rbss->map.grbs.coll_ingore_tn_line = coll_ingore_tn_line;
	rbss->map.grbs.coll_ingore_tn_arc = coll_ingore_tn_arc;
	rbss->map.grbs.coll_ingore_tn_point = coll_ingore_tn_point;

	/* for jump-over preliminary drawing when there's no grbs realization */
	rbss->fromx = RBSR_G2R(gl->x1); rbss->fromy = RBSR_G2R(gl->y1);
	rbss->tox = RBSR_G2R(gl->x2); rbss->toy = RBSR_G2R(gl->y2);
	rbss->gline = gl;
	gl->RBSR_WIREFRAME_FLAG = 1;

	rbss->snap_reset = grbs_snapshot_save(&rbss->map.grbs);
	rbss->map.grbs.force_new_alloc = 1; /* don't reuse old (deleted from grbs model) objects with ->user_data loaded; important for snapshot comparison based install() */

	rbsr_map_debug_draw(&rbss->map, "rbss2.svg");
	rbsr_map_debug_dump(&rbss->map, "rbss2.dump");

	return 0;
}

int rbsr_stretch_line_begin(rbsr_stretch_t *rbss, pcb_board_t *pcb, pcb_line_t *line)
{
	grbs_line_t *gl;
	rnd_layer_id_t lid = pcb_layer_id(pcb->Data, line->parent.layer);

	if ((pcb_layer_flags(pcb, lid) & PCB_LYT_COPPER) == 0) {
		rnd_message(RND_MSG_ERROR, "Works only on copper lines\n");
		return -1;
	}

	if (rbsr_map_pcb(&rbss->map, pcb, lid) != 0)
		return -1;

	gl = htpp_get(&rbss->map.robj2grbs, line);
	if (gl == NULL) {
		rnd_message(RND_MSG_ERROR, "rbsr_stretch_line_begin(): can't stretch this line (not in the grbs map)\n");
		return -1;
	}

	rbss->snap_cmp = NULL; /* ->use snap_reset for install */
	rbss->permit_straight = 0;
	return rbsr_stretch_gline_begin(rbss, pcb, gl);
}


int rbsr_stretch_arc_begin(rbsr_stretch_t *rbss, pcb_board_t *pcb, pcb_arc_t *arc)
{
	rnd_layer_id_t lid = pcb_layer_id(pcb->Data, arc->parent.layer);
	grbs_arc_t *ga, *preva;
	grbs_line_t *gl;

	if ((pcb_layer_flags(pcb, lid) & PCB_LYT_COPPER) == 0) {
		rnd_message(RND_MSG_ERROR, "Works only on copper arcs\n");
		return -1;
	}

	if (rbsr_map_pcb(&rbss->map, pcb, lid) != 0)
		return -1;

	ga = htpp_get(&rbss->map.robj2grbs, arc);
	if (ga == NULL) {
		rnd_message(RND_MSG_ERROR, "rbsr_stretch_arc_begin(): can't stretch this arc (not in the grbs map)\n");
		return -1;
	}

	/* compare to the initial state (before detach) when installing at the end */
	rbss->snap_cmp = grbs_snapshot_save(&rbss->map.grbs);

	/* detach the arc and stretch the resulting short-cut line */
	preva = ga->link_2net.prev;
	grbs_force_detach(&rbss->map.grbs, ga, 1);
	gl = preva->eline;

	rbss->permit_straight = 1;
	return rbsr_stretch_gline_begin(rbss, pcb, gl);
}


int rbsr_stretch_rat_begin(rbsr_stretch_t *rbss, pcb_board_t *pcb, pcb_rat_t *rat)
{
	pcb_layer_t *ly;
	rnd_layer_id_t lid;
	pcb_any_obj_t *o1, *o2;
	grbs_2net_t *tn;
	grbs_line_t *gl;
	grbs_point_t *pt1, *pt2;
	grbs_arc_t *a1, *a2;
	double sa;

	ly = PCB_CURRLAYER(pcb);
	lid = pcb_layer_id(pcb->Data, ly);

	o1 = pcb_idpath2obj_in(pcb->Data, rat->anchor[0]);
	o2 = pcb_idpath2obj_in(pcb->Data, rat->anchor[1]);
	if ((o1 == NULL) || (o2 == NULL)) {
		rnd_message(RND_MSG_ERROR, "rbsr_stretch_rat_begin(): internal error: can't find pcb-rnd rat-end objects - broken anchor?\n");
		return -1;
	}

	if (rbsr_map_pcb(&rbss->map, pcb, lid) != 0)
		return -1;


	pt1 = htpp_get(&rbss->map.term4incident, o1);
	pt2 = htpp_get(&rbss->map.term4incident, o2);
	if ((pt1 == NULL) || (pt2 == NULL)) {
		rnd_message(RND_MSG_ERROR, "rbsr_stretch_rat_begin(): can't find rat's grbs endpoints\nOnly rat lines between terminals are supported at the moment");
		return -1;
	}


	/* create twonet and start/end incident arcs on it */
	tn = grbs_2net_new(&rbss->map.grbs, RBSR_R2G(conf_core.design.line_thickness/2.0), RBSR_R2G(conf_core.design.clearance/2.0));

	sa = atan2(pt2->y - pt1->y, pt2->x - pt1->x);
	a1 = grbs_arc_new(&rbss->map.grbs, pt1, 0, 0, sa, 0);
	gdl_append(&tn->arcs, a1, link_2net);
	a1->in_use = 1;

	sa += GRBS_PI;
	if (sa > 2*GRBS_PI)
		sa -= 2*GRBS_PI;
	a2 = grbs_arc_new(&rbss->map.grbs, pt2, 0, 0, sa, 0);
	gdl_append(&tn->arcs, a2, link_2net);
	a2->in_use = 1;

	/* create a grbs line in between the two arcs */
	gl = grbs_line_new(&rbss->map.grbs);
	grbs_line_attach(&rbss->map.grbs, gl, a1, 1);
	grbs_line_attach(&rbss->map.grbs, gl, a2, 2);
	grbs_line_bbox(gl);
	grbs_line_reg(&rbss->map.grbs, gl);

	rbss->snap_cmp = NULL; /* ->use snap_reset for install */
	rbss->permit_straight = 0;
	return rbsr_stretch_gline_begin(rbss, pcb, gl);
}


static void rbsr_stretch_end(rbsr_stretch_t *rbss, rnd_bool apply)
{
	if (apply)
		rbsr_install_by_snapshot(&rbss->map, pcb_get_layer(rbss->map.pcb->Data, rbss->map.lid), (rbss->snap_cmp == NULL ? rbss->snap_reset : rbss->snap_cmp));

	grbs_snapshot_free(rbss->snap_reset);
	if (rbss->snap_cmp != NULL)
		grbs_snapshot_free(rbss->snap_cmp);
	rbss->snap_cmp = rbss->snap_reset = NULL;

	rbsr_map_uninit(&rbss->map);
}

int rbsr_stretch_any_begin(rbsr_stretch_t *rbss, pcb_board_t *pcb, rnd_coord_t x, rnd_coord_t y)
{
	void *ptr1, *ptr2, *ptr3;
	int type, res;

	rbss->jump_over_pt = NULL;
	rbss->remove_obj = NULL;

	type = pcb_search_obj_by_location(PCB_OBJ_LINE, &ptr1, &ptr2, &ptr3, x, y, 0);
	if (type == 0)
		type = pcb_search_obj_by_location(PCB_OBJ_LINE, &ptr1, &ptr2, &ptr3, x, y, rnd_pixel_slop);
	if (type == 0)
		type = pcb_search_obj_by_location(PCB_OBJ_LINE, &ptr1, &ptr2, &ptr3, x, y, rnd_pixel_slop*5);

	if (type == 0)
		type = pcb_search_obj_by_location(PCB_OBJ_ARC, &ptr1, &ptr2, &ptr3, x, y, 0);
	if (type == 0)
		type = pcb_search_obj_by_location(PCB_OBJ_ARC, &ptr1, &ptr2, &ptr3, x, y, rnd_pixel_slop);
	if (type == 0)
		type = pcb_search_obj_by_location(PCB_OBJ_ARC, &ptr1, &ptr2, &ptr3, x, y, rnd_pixel_slop*5);


	if (type == 0)
		type = pcb_search_obj_by_location(PCB_OBJ_RAT, &ptr1, &ptr2, &ptr3, x, y, rnd_pixel_slop);
	if (type == 0)
		type = pcb_search_obj_by_location(PCB_OBJ_RAT, &ptr1, &ptr2, &ptr3, x, y, rnd_pixel_slop*5);

	if (type == PCB_OBJ_LINE) {
		pcb_line_t *l = ptr2;
		res = rbsr_stretch_line_begin(rbss, pcb, l);
		if (res == 0)
			rbsr_ui_save(&rbss->map);
		return res;
	}

	if (type == PCB_OBJ_RAT) {
		pcb_rat_t *rat = ptr2;
		res = rbsr_stretch_rat_begin(rbss, pcb, rat);
		if (res == 0) {
			rbsr_ui_save(&rbss->map);
			rbss->remove_obj = (pcb_any_obj_t *)rat;
		}
		return res;
	}


	if (type == PCB_OBJ_ARC) {
		pcb_arc_t *a = ptr2;

		res = rbsr_stretch_arc_begin(rbss, pcb, a);

		if (res == 0)
			rbsr_ui_save(&rbss->map);
		return res;
	}

	rnd_message(RND_MSG_ERROR, "Failed to find a line or ratline or arc at that location\n");
	return -1;
}


int rbsr_stretch_to_coords(rbsr_stretch_t *rbss)
{
	grbs_point_t *pt;
	int changed = 0;

	rbss->acceptable = 0;

	/* jump over */
	pt = rbsr_crosshair_get_pt(&rbss->map, pcb_crosshair.X, pcb_crosshair.Y, 1, NULL);
	changed = (pt != rbss->jump_over_pt);


	rbss->jump_over_pt = pt;
	rbss->jump_over_consider = NULL;

	grbs_snapshot_restore(rbss->snap_reset);


	if (pt != NULL) {
		grbs_arc_dir_t dir;
		grbs_arc_t *ea1 = rbss->gline->a1, *ea2 = rbss->gline->a2;

		if ((pt == ea1->parent_pt) || (pt == ea2->parent_pt)) {
			rnd_trace("not stretching to neighbor (#1)\n");
			return 1;
		}

		ea1 = ea1->link_2net.prev;
		ea2 = ea2->link_2net.next;
		if (((ea1 != NULL) && (pt == ea1->parent_pt)) || ((ea2 != NULL) && (pt == ea2->parent_pt))) {
			rnd_trace("not stretching to neighbor (#2)\n");
			return 1;
		}

		dir = rbsr_crosshair_get_pt_dir(&rbss->map, rbss->fromx, rbss->fromy, pcb_crosshair.X, pcb_crosshair.Y, pt);
		if (dir == GRBS_ADIR_INC)
			return 1;
		rnd_trace("jump-over: %p %d from: %$mm;%$mm\n", pt, dir, rbss->fromx, rbss->fromy);

		if (grbs_mod_split_line(&rbss->map.grbs, rbss->gline, pt, (dir & GRBS_ADIR_CONVEX_CW) ? -1 : +1) == 0)
			rbss->acceptable = 1;
	}
	else {
		if (rbss->permit_straight) {
			double dist2, slop;
			pcb_line_t line = {0};

			line.Point1.X = rbss->fromx; line.Point1.Y = rbss->fromy;
			line.Point2.X = rbss->tox; line.Point2.Y = rbss->toy;

			dist2 = pcb_point_line_dist2(pcb_crosshair.X, pcb_crosshair.Y, &line);
			slop = rnd_pixel_slop * 25;
			if (slop < RND_MM_TO_COORD(0.5))
				slop = RND_MM_TO_COORD(0.5);
			rnd_trace("jump-over: straight-line solution: accept %$mm slop: %$mm\n", (rnd_coord_t)(sqrt(dist2)), (rnd_coord_t)slop);
			if (dist2 < slop*slop) {
				rnd_trace("jump-over: NULL, straight line\n");
				rbss->acceptable = 1;
			}
			else
				rnd_trace("jump-over: NULL (no point found, too far from straight line, not accepted)\n");
		}
		else
			rnd_trace("jump-over: NULL (no point found, not accepted)\n");
	}

	return 1;
}

int rbsr_stretch_accept(rbsr_stretch_t *rbss)
{
	if (!rbss->acceptable)
		return 0;

	rnd_trace("IMPLEMENT!\n");
	rbsr_ui_restore(&rbss->map);
	if (rbss->remove_obj != NULL) {
		pcb_remove_object(rbss->remove_obj->type, rbss->remove_obj, rbss->remove_obj, NULL);
	}

	rbsr_stretch_end(rbss, 1);
	return 1;
}


int rbsr_stretch_cancel(rbsr_stretch_t *rbss)
{
	rbsr_ui_restore(&rbss->map);
	rbsr_stretch_end(rbss, 0);
	return 0;
}
