/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - edit polygon points
 *  Copyright (C) 2023 Tibor 'Igor2' Palinkas
 *
 *  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/sch-rnd
 *    contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html
 *    mailing list: http://www.repo.hu/projects/sch-rnd/contact.html
 */

#include <libcschem/config.h>
#include <libcschem/cnc_obj.h>
#include <gengeo2d/cline.h>

#include "tool_polyedit.h"

typedef struct {
	csch_coord_t press_x, press_y, press_chx, press_chy;
	int click, ret_arrow, ortho_valid;
	csch_cpoly_t *poly;
	long obj_idx[2];
	csch_chdr_t *obj[2];
} tool_polyedit_t;

static tool_polyedit_t pedit;

/* extend the line between px;py and (already moved) x;y knowing the previous
   point beyond px;py (which is ppx;ppy) */
static void perp_extend(csch_coord_t ppx, csch_coord_t ppy, csch_coord_t *px, csch_coord_t *py, csch_coord_t x, csch_coord_t y, csch_coord_t dx, csch_coord_t dy)
{
	g2d_cline_t l1, l2;
	g2d_vect_t ip;
	g2d_offs_t offs[2];

	l1.p1.x = ppx; l1.p1.y = ppy;
	l1.p2.x = *px; l1.p2.y = *py;

	l2.p1.x = (*px) + dx; l2.p1.y = (*py) + dy;
	l2.p2.x = x; l2.p2.y = y;

	/* calculate the intersection point, which is most probably not on any of the lines */
	g2d__iscp_cline_cline_o(&l1, &l2, NULL, offs, 1);
	ip = g2d_cline_offs(&l1, offs[0]);

	*px = ip.x;
	*py = ip.y;
}


RND_INLINE csch_line_t *get_mod_obj(csch_vtcoutline_t *cnt, long idx)
{
	if (idx < 0) idx += cnt->used;
	else if (idx >= cnt->used) idx -= cnt->used;
	return &cnt->array[idx].line;
}

RND_INLINE int ortho_pt_move(csch_vtcoutline_t *cnt, csch_coord_t dx, csch_coord_t dy, int draw)
{
	csch_coord_t x, y;                /* point being moved */
	csch_coord_t px, py, nx, ny;      /* previous and next point */
	csch_coord_t ppx, ppy, nnx, nny;  /* 2nd previous and next point */
	csch_line_t *obj = get_mod_obj(cnt, pedit.obj_idx[0]);    /* object before x;y (2nd endpoint is x;y) */
	csch_line_t *pobj = get_mod_obj(cnt, pedit.obj_idx[0]-1); /* object before px;py (2nd endpoint is px;py) */
	csch_line_t *nobj = get_mod_obj(cnt, pedit.obj_idx[0]+1);  /* object after x;y (1st endpoint is x;y) */
	csch_line_t *ppobj = get_mod_obj(cnt, pedit.obj_idx[0]-2); /* object before ppx;ppy (2nd endpoint is ppx;ppy) */
	csch_line_t *nnobj = get_mod_obj(cnt, pedit.obj_idx[0]+2); /* object after nx;ny (1st endpoint is nx;ny) */

	if (obj->hdr.type != CSCH_CTYPE_LINE) return -1;
	if (nobj->hdr.type != CSCH_CTYPE_LINE) return -1;
	if (pobj->hdr.type != CSCH_CTYPE_LINE) return -1;
	if (nnobj->hdr.type != CSCH_CTYPE_LINE) return -1;
	if (ppobj->hdr.type != CSCH_CTYPE_LINE) return -1;

	x = obj->inst.c.p2.x + dx; y = obj->inst.c.p2.y + dy;
	nx = nobj->inst.c.p2.x; ny = nobj->inst.c.p2.y;
	nnx = nnobj->inst.c.p2.x; nny = nnobj->inst.c.p2.y;
	px = pobj->inst.c.p2.x; py = pobj->inst.c.p2.y;
	ppx = ppobj->inst.c.p2.x; ppy = ppobj->inst.c.p2.y;


	perp_extend(ppx, ppy, &px, &py, x, y, dx, dy);
	perp_extend(nnx, nny, &nx, &ny, x, y, dx, dy);

	if (draw) {
		rnd_render->draw_line(sch_rnd_crosshair_gc, C2P(ppx), C2P(ppy), C2P(px), C2P(py));
		rnd_render->draw_line(sch_rnd_crosshair_gc, C2P(nnx), C2P(nny), C2P(nx), C2P(ny));

		rnd_render->draw_line(sch_rnd_crosshair_gc, C2P(px), C2P(py), C2P(x), C2P(y));
		rnd_render->draw_line(sch_rnd_crosshair_gc, C2P(nx), C2P(ny), C2P(x), C2P(y));
	}
	else {
		nobj->spec.p2.x = nx; nobj->spec.p2.y = ny;
		nnobj->spec.p1.x = nx; nnobj->spec.p1.y = ny;

		pobj->spec.p2.x = px; pobj->spec.p2.y = py;
		obj->spec.p1.x = px; obj->spec.p1.y = py;

		obj->spec.p2.x = x; obj->spec.p2.y = y;
		nobj->spec.p1.x = x; nobj->spec.p1.y = y;
	}

	return 0;
}

RND_INLINE void polyedit_draw_pt_anydir(csch_coord_t dx, csch_coord_t dy)
{
	csch_line_t *l1 = (csch_line_t *)pedit.obj[0];
	csch_line_t *l2 = (csch_line_t *)pedit.obj[1];

	/* TODO: this assumes two lines */
	rnd_render->draw_line(sch_rnd_crosshair_gc, C2P(l1->inst.c.p1.x), C2P(l1->inst.c.p1.y), C2P(l1->inst.c.p2.x + dx), C2P(l1->inst.c.p2.y + dy));
	rnd_render->draw_line(sch_rnd_crosshair_gc, C2P(l2->inst.c.p2.x), C2P(l2->inst.c.p2.y), C2P(l2->inst.c.p1.x + dx), C2P(l2->inst.c.p1.y + dy));
}

RND_INLINE void polyedit_draw_pt_ortho(csch_coord_t dx, csch_coord_t dy)
{
	if (ortho_pt_move(&pedit.poly->outline, dx, dy, 1) != 0) {
		polyedit_draw_pt_anydir(dx, dy); /* fall back */
		pedit.ortho_valid = 0;
	}
	else
		pedit.ortho_valid = 1;
}

RND_INLINE void polyedit_exec_pt_anydir(csch_sheet_t *sheet, csch_coord_t dx, csch_coord_t dy)
{
	csch_line_t *l1, *l2;
	csch_vtcoutline_t *cnt;
		void *cookie;

	/* TODO: this assumes two lines */
	cnt = csch_cpoly_modify_geo_begin(sheet, pedit.poly, &cookie, 1);
	l1 = &cnt->array[pedit.obj_idx[0]].line;
	l2 = &cnt->array[pedit.obj_idx[1]].line;
	l1->spec.p2.x += dx; l1->spec.p2.y += dy;
	l2->spec.p1.x += dx; l2->spec.p1.y += dy;
	csch_cpoly_modify_geo_end(sheet, pedit.poly, &cookie);
}

RND_INLINE void polyedit_exec_pt_ortho(csch_sheet_t *sheet, csch_coord_t dx, csch_coord_t dy)
{
	if (pedit.ortho_valid) {
		void *cookie;
		csch_vtcoutline_t *cnt = csch_cpoly_modify_geo_begin(sheet, pedit.poly, &cookie, 1);
		ortho_pt_move(cnt, dx, dy, 0);
		csch_cpoly_modify_geo_end(sheet, pedit.poly, &cookie);
	}
	else
		polyedit_exec_pt_anydir(sheet, dx, dy);
}


static void tool_polyedit_init(void)
{
}

static void tool_polyedit_uninit(void)
{
}

/* for the passon from the arrow tool; returns 0 if accepted */
static int tool_polyedit_press_at(rnd_design_t *hl, csch_coord_t x, csch_coord_t y, csch_cpoly_t *poly, long obj_idx[2], int ret_arrow)
{
	csch_sheet_t *sheet = (csch_sheet_t *)hl;
	long v;
	int n;
	vtp0_t tmp = {0};

	if (poly == NULL) return -1;
	if (obj_idx[0] < 0) return -1;
	if (obj_idx[1] < 0) return -1; /* for now accept only corner moves */

	for(n = 0; n < 2; n++) {
		if (obj_idx[n] >= 0) {
			if (obj_idx[n] >= poly->outline.used)
				return -1; /* invalid object */
			pedit.obj[n] = &poly->outline.array[obj_idx[n]].hdr;

			if (pedit.obj[n]->type != CSCH_CTYPE_LINE) return -1; /* accept line-line only for now */
		}
		else
			pedit.obj[n] = NULL;
	}

	/* refuse if there's selection (move selected) */
	v = csch_search_all_selected(sheet, &sheet->direct, &tmp, 1);
	if (v != 0) {
		vtp0_uninit(&tmp);
		return -1;
	}

	pedit.ret_arrow = ret_arrow;
	pedit.press_x = P2C(x);
	pedit.press_y = P2C(y);
	pedit.press_chx = x;
	pedit.press_chy = y;
	pedit.poly = poly;
	pedit.click = 1;
	pedit.obj_idx[0] = obj_idx[0];
	pedit.obj_idx[1] = obj_idx[1];

rnd_trace("polyedit!\n");
	return 0; /* accept */
}

static void tool_polyedit_press(rnd_design_t *hl)
{
	csch_sheet_t *sheet = (csch_sheet_t *)hl;
	csch_coord_t x, y, r = sch_rnd_slop/2+1;
	long obj_idx[2], len;
	csch_chdr_t *obj;
	csch_rtree_box_t query;

	x = P2C(sch_rnd_crosshair_x); y = P2C(sch_rnd_crosshair_y);
	query.x1 = x - r; query.y1 = y - r;
	query.x2 = x + r; query.y2 = y + r;
	obj = csch_search_first_mask(sheet, &query, CSCH_CMASK_POLY);
	if (obj == NULL)
		return;

	len = csch_poly_contour_objs_at((csch_cpoly_t *)obj, x, y, r, obj_idx);
	if (len == 2)
		tool_polyedit_press_at(hl, sch_rnd_crosshair_x, sch_rnd_crosshair_y, (csch_cpoly_t *)obj, obj_idx, 0);
}

static void tool_polyedit_release(rnd_design_t *hl)
{
	csch_sheet_t *sheet = (csch_sheet_t *)hl;

	if (pedit.click) {
		csch_coord_t r = sch_rnd_slop/2+1;
		csch_coord_t x = P2C(sch_rnd_crosshair_x), y = P2C(sch_rnd_crosshair_y);
		csch_coord_t dx = x - pedit.press_x, dy = y - pedit.press_y;
		long obj_idx[2];
		int len;

		if ((dx == 0) && (dy == 0))
			goto skip; /* do not pollute the undo list */

		len = csch_poly_contour_objs_at(pedit.poly, x, y, r, obj_idx);
		if (len != 0) {
			if ((len > 1) || !csch_poly_contour_obj_ends_at(pedit.poly, obj_idx[0], pedit.press_x, pedit.press_y)) {
				rnd_message(RND_MSG_ERROR, "Polygon corner shall not overlap with the same polygon's edge or corner\n");
				goto skip;
			}
			/* else the new point is on a contour object that starts or ends on the corner we are moving so the resulting poly won't be invalid */
		}

		if (rnd_gui->control_is_pressed(rnd_gui))
			polyedit_exec_pt_ortho(sheet, dx, dy);
		else
			polyedit_exec_pt_anydir(sheet, dx, dy);

		skip:;
		pedit.click = 0;
	}
	if (pedit.ret_arrow)
		rnd_tool_select_by_name(hl, "arrow");
}

static void tool_polyedit_draw_attached(rnd_design_t *hl)
{
	if (pedit.click) {
		/*csch_sheet_t *sheet = (csch_sheet_t *)hl;*/
		csch_coord_t x = P2C(sch_rnd_crosshair_x), y = P2C(sch_rnd_crosshair_y);
		csch_coord_t dx = x - pedit.press_x, dy = y - pedit.press_y;

		if (rnd_gui->control_is_pressed(rnd_gui))
			polyedit_draw_pt_ortho(dx, dy);
		else
			polyedit_draw_pt_anydir(dx, dy);

	}
}

/* XPM */
static const char *polyedit_icon[] = {
/* columns rows colors chars-per-pixel */
"21 21 3 1",
"  c #000000",
". c #6EA5D7",
"o c None",
/* pixels */
"ooooooooooooooooooooo",
"ooooooooooooooooooooo",
"ooooooo...........ooo",
"ooooooo.         .ooo",
"oooooo. ...... ...ooo",
"ooooo. .ooo. .ooooooo",
"oooo. .oo. .ooooooooo",
"ooo. .o. .ooooooooooo",
"oo. . .oooooooooooooo",
"o. .ooooooooooooooooo",
"o...ooooooooooooooooo",
"ooooooooooooooooooooo",
"ooooooooooooooooooooo",
"o    oo      o     oo",
"o ooo o oooooo oooo o",
"o ooo o oooooo oooo o",
"o    oo    ooo oooo o",
"o ooooo oooooo oooo o",
"o ooooo oooooo oooo o",
"o ooooo      o     oo",
"ooooooooooooooooooooo"
};


static rnd_tool_t sch_rnd_tool_polyedit = {
	"polyedit", NULL, std_tools_cookie, 50, polyedit_icon, RND_TOOL_CURSOR_NAMED("left_ptr"), 0,
	tool_polyedit_init,
	tool_polyedit_uninit,
	tool_polyedit_press,
	tool_polyedit_release,
	NULL, /* adjust_attached_objects */
	tool_polyedit_draw_attached,
	NULL, /* undo */
	NULL, /* redo */
	NULL, /* escape */
};

