/*
 *                            COPYRIGHT
 *
 *  pcb-rnd, interactive printed circuit board design - Rubber Band Stretch Router
 *  Copyright (C) 2024 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., 31 Milk Street, # 960789 Boston, MA 02196 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")
 */

/* Code to compare current grbs model to snapshot of starting grbs model then
   copy back changes to the pcb-rnd model */

#include "undo.h"
#include "remove.h"

#include "install.h"

/* define to 1 to enable trace messages for installation */
#if 0
#	define inst_trace rnd_trace
#else
	RND_INLINE void inst_trace(char *fmt, ...) {}
#endif


/*** snapshot based install ***/

/* rnd side angle compare with tolerance (degrees) */
RND_INLINE int rangeq(double a1, double a2)
{
	double d = a1 - a2;
	return (d >= -0.01) && (d < +0.01);
}

/* Convert grbs arc into pcb-rnd cx:cy:r:sa:da */
#define ARC_G2R(garc) \
	do { \
		sa = 180.0 - (garc->sa * RND_RAD_TO_DEG); \
		da = - (garc->da * RND_RAD_TO_DEG); \
		cx = RBSR_G2R(garc->parent_pt->x); \
		cy = RBSR_G2R(garc->parent_pt->y); \
		r = RBSR_G2R(garc->r); \
	} while(0)

typedef struct {
	rbsr_map_t *map;
	pcb_layer_t *ly;
} copy_back_t;

/* decide if two grbs radian angles are equal (with a tiny amount of tolerance) */
RND_INLINE int gangeq(double a1, double a2)
{
	double d = a1 - a2;
	return (d >= -0.0001) && (d < +0.0001);
}

/* return 1 if two grbs arcs are not the same */
RND_INLINE int garc_diff(grbs_arc_t *orig, grbs_arc_t *new)
{
	if (orig->parent_pt != new->parent_pt) return 1;
	if (!gcrdeq(orig->r, new->r)) return 1;
	if (!gangeq(orig->sa, new->sa)) return 1;
	if (!gangeq(orig->da, new->da)) return 1;
	return 0;
}

/* return 1 if two grbs lines are not the same */
RND_INLINE int gline_diff(grbs_line_t *orig, grbs_line_t *new)
{
	if (!gangeq(orig->x1, new->x1)) return 1;
	if (!gangeq(orig->y1, new->y1)) return 1;
	if (!gangeq(orig->x2, new->x2)) return 1;
	if (!gangeq(orig->y2, new->y2)) return 1;
	return 0;
}

/* callback to compare original grbs model objct (from the snapshot) to current
   model object and copy back to pcb-rnd model if needed */
static void cmp_cb(void *uctx, grbs_obj_type_t type, void *orig_obj, void *new_obj)
{
	copy_back_t *cctx = uctx;

	switch(type) {
		case GRBS_ARC:
			{
				grbs_arc_t *orig = orig_obj, *new = new_obj;

				if ((new != NULL) && (new->RBSR_WIREFRAME_COPIED_BACK)) {
					if (orig == NULL)
						break;
					new = NULL;
					new_obj = NULL;
				}

				if (orig_obj == NULL) {
					/* new arc in grbs */
					if (new->r > 0) { /* r==0 is incident, pcb-rnd won't need an arc there */
						pcb_arc_t *pa;
						double sa, da;
						rnd_coord_t cx, cy, r;
						grbs_2net_t *tn = grbs_arc_parent_2net(new);
						ARC_G2R(new);

						inst_trace(" arc new %ld:\n", new->uid);

						pa = pcb_arc_new(cctx->ly, cx, cy, r, r, sa, da,
							RBSR_G2R(tn->copper*2), RBSR_G2R(tn->clearance*2),
							pcb_flag_make(PCB_FLAG_CLEARLINE), 1);

						if (pa == NULL) {
							rnd_message(RND_MSG_ERROR, "rbsr_install: failed to create arc (center %$mm %$mm r %$mm)\n", cx, cy, r);
							return;
						}

						pcb_undo_add_obj_to_create(PCB_OBJ_ARC, cctx->ly, pa, pa);
					}
				}
				else if (new_obj == NULL) {
					/* arc removed from grbs */
					if ((orig->r > 0) && (orig->user_data != NULL)) {
						inst_trace(" arc del %ld:\n", orig->uid);
						pcb_remove_object(PCB_OBJ_ARC, cctx->ly, orig->user_data, orig->user_data);
					}
				}
				else if (((orig->r > 0) || (new->r > 0)) && (garc_diff(orig, new))) {
					/* arc modified in grbs */
					pcb_arc_t *pa;
					double sa, da;
					rnd_coord_t cx, cy, r;
					double sa2, da2, *new_sa = NULL, *new_da = NULL;
					int match_cent, match_radi, match_ang1, match_ang2;

					rnd_coord_t *new_cx = NULL, *new_cy = NULL, *new_r = NULL;
					ARC_G2R(new);

					inst_trace(" arc changed %ld %ld:\n", orig->uid, new->uid);

					sa2 = sa+da; da2 = -da;
					pa = (pcb_arc_t *)orig->user_data;
					assert(pa != NULL);
					assert(pa->type == PCB_OBJ_ARC);

					/* figure which parameters match */
					match_cent = (rcrdeq(cx, pa->X) && rcrdeq(cy, pa->Y));
					match_radi = rcrdeq(r, pa->Width);
					match_ang1 = (rangeq(sa, pa->StartAngle) && rangeq(da, pa->Delta));
					match_ang2 = (rangeq(sa2, pa->StartAngle) && rangeq(da2, pa->Delta));

					/* no change */
					if (match_cent && match_radi && (match_ang1 || match_ang2)) return;

					/* figure what needs change */
					if (!match_cent) {
						new_cx = &cx;
						new_cy = &cy;
					}
					if (!match_radi)
						new_r = &r;
					if (!match_ang1 && !match_ang2) {
						new_sa = &sa;
						new_da = &da;
					}

					pcb_arc_modify(pa, new_cx, new_cy, new_r, new_r, new_sa, new_da, NULL, NULL, 1);
				}
			}
			break;
		case GRBS_LINE:
			{
				grbs_line_t *orig = orig_obj, *new = new_obj;

				if ((new != NULL) && (new->RBSR_WIREFRAME_COPIED_BACK)) {
					if (orig == NULL)
						break;
					new = NULL;
					new_obj = NULL;
				}

				if ((orig_obj == NULL) || ((new != NULL) && (new->user_data == NULL))) {
					/* new line in grbs; ->user_data == NULL when the new line was
					   created before user edit but after mapping (it surely doesn't
					   exist on board) */
					pcb_line_t *pl;
					grbs_2net_t *tn = grbs_arc_parent_2net(new->a1);
					rnd_coord_t l1x = RBSR_G2R(new->x1), l1y = RBSR_G2R(new->y1);
					rnd_coord_t l2x = RBSR_G2R(new->x2), l2y = RBSR_G2R(new->y2);

					inst_trace(" line new %ld:\n", new->uid);

					if ((new->x1 == new->x2) && (new->y1 == new->y2))
						return; /* don't create zero length lines */

					pl = pcb_line_new(cctx->ly, l1x, l1y, l2x, l2y,
						RBSR_G2R(tn->copper*2), RBSR_G2R(tn->clearance*2),
						pcb_flag_make(PCB_FLAG_CLEARLINE));

					if (pl == NULL) {
						rnd_message(RND_MSG_ERROR, "rbsr_install: failed to create line\n");
						return;
					}

					pcb_undo_add_obj_to_create(PCB_OBJ_LINE, cctx->ly, pl, pl);
				}
				else if (new_obj == NULL) {
					/* line removed in grbs */
					if (orig->user_data != NULL) {
						inst_trace(" line del %ld:\n", orig->uid);
						pcb_remove_object(PCB_OBJ_LINE, cctx->ly, orig->user_data, orig->user_data);
					}
				}
				else if (gline_diff(orig, new)) {
					/* line changed in grbs */
					pcb_line_t *pl;
					rnd_coord_t l1x = RBSR_G2R(new->x1), l1y = RBSR_G2R(new->y1);
					rnd_coord_t l2x = RBSR_G2R(new->x2), l2y = RBSR_G2R(new->y2);

					pl = (pcb_line_t *)new->user_data;
					assert(pl != NULL);
					assert(pl->type == PCB_OBJ_LINE);

					/* don't touch lines that have no change */
					if (rcrdeq(l1x, pl->Point1.X) && rcrdeq(l1y, pl->Point1.Y) && rcrdeq(l2x, pl->Point2.X) && rcrdeq(l2y, pl->Point2.Y)) return;
					if (rcrdeq(l2x, pl->Point1.X) && rcrdeq(l2y, pl->Point1.Y) && rcrdeq(l1x, pl->Point2.X) && rcrdeq(l1y, pl->Point2.Y)) return;

					/* modify trying to keep original direction */
					if ((rcrdeq(l1x, pl->Point1.X) && rcrdeq(l1y, pl->Point1.Y)) || (rcrdeq(l2x, pl->Point2.X) && rcrdeq(l2y, pl->Point2.Y)))
						pcb_line_modify(pl, &l1x, &l1y, &l2x, &l2y, NULL, NULL, 1);
					else
						pcb_line_modify(pl, &l2x, &l2y, &l1x, &l1y, NULL, NULL, 1);

					inst_trace(" line changed %ld %ld:\n", orig->uid, new->uid);
				}
			}
			break;
		case GRBS_POINT:
			/* no need to deal with points, we don't change them at the moment */
			break;
		default:
			inst_trace(" ???\n");
	}
}

int rbsr_install_by_snapshot(rbsr_map_t *map, pcb_layer_t *ly, grbs_snapshot_t *snap)
{
	copy_back_t cctx;

	pcb_undo_freeze_serial();

	inst_trace(" === snapshot cmp based ===\n");
	cctx.map = map;
	cctx.ly = ly;
	grbs_snapshot_cmp(snap, cmp_cb, &cctx);

	pcb_undo_unfreeze_serial();
	pcb_undo_inc_serial();

	return 0;
}
