/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - move or copy object
 *  Copyright (C) 2022 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022)
 *
 *  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 <libcschem/util_endpoint.h>

#include "tool_movecopy.h"

typedef struct {
	csch_coord_t press_x, press_y, press_chx, press_chy;
	int click;
	int copy;
	int ret_arrow;
	int endpoint; /* 1 or 2 for grabbing endpoint in move, 0 for non-endpoint */
	csch_chdr_t *click_obj;
	vtp0_t click_list;

	htepu_t rubber;
	unsigned rubber_inited:1;
} tool_movecopy_t;

static tool_movecopy_t mvcp;

static void rbm_reset_endpoints(void)
{
	if (!mvcp.rubber_inited) {
		htepu_init(&mvcp.rubber, htepu_keyhash_xy, htepu_keyeq_xy);
		mvcp.rubber_inited = 1;
	}
	else
		htepu_clear(&mvcp.rubber);
}

static int rbm_include_obj(void *ctx, csch_chdr_t *obj)
{
	/* ignore junctions */
	if (obj->type == CSCH_CTYPE_LINE) {
		csch_line_t *line = (csch_line_t *)obj;
		if ((line->inst.c.p1.x == line->inst.c.p2.x) && (line->inst.c.p1.y == line->inst.c.p2.y))
			return 0;
	}

	return 1;
}


static int rbm_include_sel(void *ctx, csch_chdr_t *obj)
{
	if (!rbm_include_obj(ctx, obj))
		return 0;

	/* ignore selected objects - they are being moved, they shouldn't leave
	   endpoints behind to connect to */
	if (csch_chdr_is_selected(obj))
		return 0;

	return 1;
}

static void rbm_grab_endpoints(csch_sheet_t *sheet, csch_chdr_t *obj, int omit_selected)
{
	if (obj->type != CSCH_CTYPE_LINE)
		return;

	if (omit_selected)
		csch_endpoint_list_connected(sheet, &mvcp.rubber, obj, rbm_include_sel, NULL);
	else
		csch_endpoint_list_connected(sheet, &mvcp.rubber, obj, rbm_include_obj, NULL);
}

static void rbm_xor_draw(csch_sheet_t *sheet, csch_coord_t dx, csch_coord_t dy)
{
	htepu_entry_t *e;
	if (!conf_core.editor.rubber_band_mode)
		return;

	for(e = htepu_first(&mvcp.rubber); e != NULL; e = htepu_next(&mvcp.rubber, e)) {
		if (conf_core.editor.rubber_band_ortho) {
			rnd_render->draw_line(sch_rnd_crosshair_gc, C2P(e->key.x), C2P(e->key.y), C2P(e->key.x+dx), C2P(e->key.y));
			rnd_render->draw_line(sch_rnd_crosshair_gc, C2P(e->key.x+dx), C2P(e->key.y), C2P(e->key.x+dx), C2P(e->key.y+dy));
		}
		else
			rnd_render->draw_line(sch_rnd_crosshair_gc, C2P(e->key.x), C2P(e->key.y), C2P(e->key.x+dx), C2P(e->key.y+dy));
	}
}

static void rbm_execute(csch_sheet_t *sheet, csch_coord_t dx, csch_coord_t dy)
{
	htepu_entry_t *e;
	csch_cpen_t *pen;

	if (!conf_core.editor.rubber_band_mode)
		return;

	pen = SCH_RND_DIRECT_PEN(sheet, tool_wire_stroke, NULL);
	if (pen == NULL)
		return;

	for(e = htepu_first(&mvcp.rubber); e != NULL; e = htepu_next(&mvcp.rubber, e)) {
		csch_line_t *line;

		if (csch_obj_is_deleted(e->key.obj)) continue;

		if (conf_core.editor.rubber_band_ortho) {
			line = csch_wirenet_draw_line_in(sheet, e->key.obj->parent, pen->name, e->key.x, e->key.y, e->key.x + dx, e->key.y);
			csch_wirenet_recalc_obj_conn(sheet, &line->hdr);
			csch_wirenet_recalc_merge(sheet, line, 1); /* must be the last; with overlap removal enabled this removes the stub in the |_ case converted into a |- case */

			line = csch_wirenet_draw_line_in(sheet, e->key.obj->parent, pen->name, e->key.x + dx, e->key.y, e->key.x + dx, e->key.y + dy);
			csch_wirenet_recalc_obj_conn(sheet, &line->hdr);
			csch_wirenet_recalc_merge(sheet, line, 1); /* must be the last; with overlap removal enabled this removes the stub in the |_ case converted into a |- case */
		}
		else {
			line = csch_wirenet_draw_line_in(sheet, e->key.obj->parent, pen->name, e->key.x, e->key.y, e->key.x + dx, e->key.y + dy);
			csch_wirenet_recalc_obj_conn(sheet, &line->hdr);
			csch_wirenet_recalc_merge(sheet, line, 1); /* must be the last; with overlap removal enabled this removes the stub in the |_ case converted into a |- case */
		}
	}
}

static void tool_move_init(void)
{
	mvcp.copy = 0;
}

static void tool_copy_init(void)
{
	mvcp.copy = 1;
}

static void tool_movecopy_uninit(void)
{
}

/* for the passon from the arrow tool */
static void tool_movecopy_press_at(rnd_design_t *hl, csch_coord_t x, csch_coord_t y, csch_chdr_t *obj, int ret_arrow)
{
	csch_sheet_t *sheet = (csch_sheet_t *)hl;
	long v;

	mvcp.ret_arrow = ret_arrow;
	mvcp.press_x = P2C(x);
	mvcp.press_y = P2C(y);
	mvcp.press_chx = x;
	mvcp.press_chy = y;
	rbm_reset_endpoints();

	mvcp.click_list.used = 0;
	mvcp.endpoint = 0;
	v = csch_search_all_selected(sheet, &sheet->direct, &mvcp.click_list, 1);

	if (v == 0) {
		mvcp.click_obj = obj;
		if (mvcp.click_obj == NULL)
			mvcp.click_obj = sch_rnd_search_obj_at(sheet, mvcp.press_x, mvcp.press_y, sch_rnd_slop);
		if (mvcp.click_obj == NULL) {
			mvcp.click = 0;
			return;
		}

		/* figure endpoint for move */
		if (!mvcp.copy) {
			switch(mvcp.click_obj->type) {
				case CSCH_CTYPE_LINE:
					{
						csch_line_t *l = (csch_line_t *)mvcp.click_obj;
						if ((l->inst.c.p1.x == mvcp.press_x) && (l->inst.c.p1.y == mvcp.press_y)) mvcp.endpoint = 1;
						else if ((l->inst.c.p2.x == mvcp.press_x) && (l->inst.c.p2.y == mvcp.press_y)) mvcp.endpoint = 2;
					}
					break;
				default:
					break;
			}
		}

		if (!mvcp.copy && (mvcp.endpoint == 0) && conf_core.editor.rubber_band_mode)
			rbm_grab_endpoints(sheet, mvcp.click_obj, 0);
	}
	else {
		mvcp.click_obj = NULL;
		if (!mvcp.copy && conf_core.editor.rubber_band_mode) {
			long n;
			for(n = 0; n < mvcp.click_list.used; n++)
				rbm_grab_endpoints(sheet, mvcp.click_list.array[n], 1);
		}
	}

	mvcp.click = 1;
}

static void tool_movecopy_press(rnd_design_t *hl)
{
	tool_movecopy_press_at(hl, sch_rnd_crosshair_x, sch_rnd_crosshair_y, NULL, 0);
}

static void move_endpoint(csch_sheet_t *sheet, csch_coord_t dx, csch_coord_t dy)
{
	switch(mvcp.click_obj->type) {
		case CSCH_CTYPE_LINE:
			{
				csch_line_t *l = (csch_line_t *)mvcp.click_obj;
				uundo_freeze_serial(&sheet->undo);
				if (mvcp.endpoint == 1)
					csch_line_modify(sheet, l, &dx, &dy, NULL, NULL, 1, 1, 1);
				else if (mvcp.endpoint == 2)
					csch_line_modify(sheet, l, NULL, NULL, &dx, &dy, 1, 1, 1);
				uundo_unfreeze_serial(&sheet->undo);
				uundo_inc_serial(&sheet->undo);
			}
			break;
		default: break;
	}

}

static void tool_movecopy_release(rnd_design_t *hl)
{
	csch_sheet_t *sheet = (csch_sheet_t *)hl;
	long n;

	if (mvcp.click) {
		csch_coord_t x = P2C(sch_rnd_crosshair_x), y = P2C(sch_rnd_crosshair_y);
		csch_coord_t dx = x - mvcp.press_x, dy = y - mvcp.press_y;

		if (!mvcp.copy) {
			if (!mvcp.endpoint) {
				csch_cobj_redraw_freeze(sheet);
				uundo_freeze_serial(&sheet->undo);
				csch_wirenet_recalc_freeze(sheet);

				for(n = 0; n < mvcp.click_list.used; n++) {
					csch_chdr_t *obj = mvcp.click_list.array[n];
					if (!csch_chdr_any_parent_selected(obj)) /* do not move anything with parent group selected because the parent grp is moved already */
						csch_move(sheet, obj, dx, dy, 1);
				}
				if (mvcp.click_obj != NULL) {
					csch_move(sheet, mvcp.click_obj, dx, dy, 1);
					if (mvcp.click_obj->type == CSCH_CTYPE_LINE)
						csch_wirenet_recalc_merge(sheet, (csch_line_t *)mvcp.click_obj, 0);
				}
				for(n = 0; n < mvcp.click_list.used; n++) { /* revisit for merging line pieces */
					csch_chdr_t *obj = mvcp.click_list.array[n];
					if (!csch_chdr_any_parent_selected(obj)) /* do not move anything with parent group selected because the parent grp is moved already */
						if (obj->type == CSCH_CTYPE_LINE)
							csch_wirenet_recalc_merge(sheet, (csch_line_t *)obj, 0);
				}

				rbm_execute(sheet, dx, dy);

				csch_wirenet_recalc_unfreeze(sheet);
				uundo_unfreeze_serial(&sheet->undo);
				uundo_inc_serial(&sheet->undo);
				csch_cobj_redraw_unfreeze(sheet);
			}
			else
				move_endpoint(sheet, dx, dy);
		}
		else {
			csch_cobj_redraw_freeze(sheet);
			uundo_freeze_serial(&sheet->undo);
			csch_wirenet_recalc_freeze(sheet);
			for(n = 0; n < mvcp.click_list.used; n++) {
				csch_chdr_t *obj = mvcp.click_list.array[n];
				if (!csch_chdr_any_parent_selected(obj)) /* do not copy anything with parent group selected because the parent grp is copied already */
					csch_copy(sheet, obj, dx, dy, 1);
			}
			if (mvcp.click_obj != NULL)
				csch_copy(sheet, mvcp.click_obj, dx, dy, 1);
			csch_wirenet_recalc_unfreeze(sheet);
			uundo_unfreeze_serial(&sheet->undo);
			uundo_inc_serial(&sheet->undo);
			csch_cobj_redraw_unfreeze(sheet);
		}


		mvcp.click = 0;
	}
	if (mvcp.ret_arrow)
		rnd_tool_select_by_name(hl, "arrow");
}

static void xor_draw_endpoint(csch_sheet_t *sheet, csch_coord_t dx, csch_coord_t dy)
{
	switch(mvcp.click_obj->type) {
		case CSCH_CTYPE_LINE:
			{
				csch_line_t *l = (csch_line_t *)mvcp.click_obj;
				if (mvcp.endpoint == 1)
					rnd_render->draw_line(sch_rnd_crosshair_gc, C2P(l->inst.c.p1.x + dx), C2P(l->inst.c.p1.y + dy), C2P(l->inst.c.p2.x), C2P(l->inst.c.p2.y));
				else if (mvcp.endpoint == 2)
					rnd_render->draw_line(sch_rnd_crosshair_gc, C2P(l->inst.c.p1.x), C2P(l->inst.c.p1.y), C2P(l->inst.c.p2.x + dx), C2P(l->inst.c.p2.y + dy));
			}
			break;
		default: break;
	}
}

static void tool_movecopy_draw_attached(rnd_design_t *hl)
{
	if (mvcp.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 - mvcp.press_x, dy = y - mvcp.press_y;

		if (!mvcp.copy && mvcp.endpoint) {
			xor_draw_endpoint(sheet, dx, dy);
		}
		else {
			long n;
			rbm_xor_draw(sheet, dx, dy);
			for(n = 0; n < mvcp.click_list.used; n++)
				sch_rnd_xor_draw_obj(sheet, mvcp.click_list.array[n], dx, dy, sch_rnd_crosshair_gc, 1, 0);
			if (mvcp.click_obj != NULL)
				sch_rnd_xor_draw_obj(sheet, mvcp.click_obj, dx, dy, sch_rnd_crosshair_gc, 1, 0);
		}
	}
}

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

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


static rnd_tool_t sch_rnd_tool_move = {
	"move", NULL, std_tools_cookie, 50, move_icon, RND_TOOL_CURSOR_NAMED("left_ptr"), 0,
	tool_move_init,
	tool_movecopy_uninit,
	tool_movecopy_press,
	tool_movecopy_release,
	NULL, /* adjust_attached_objects */
	tool_movecopy_draw_attached,
	NULL, /* undo */
	NULL, /* redo */
	NULL, /* escape */
};

static rnd_tool_t sch_rnd_tool_copy = {
	"copy", NULL, std_tools_cookie, 50, copy_icon, RND_TOOL_CURSOR_NAMED("left_ptr"), 0,
	tool_copy_init,
	tool_movecopy_uninit,
	tool_movecopy_press,
	tool_movecopy_release,
	NULL, /* adjust_attached_objects */
	tool_movecopy_draw_attached,
	NULL, /* undo */
	NULL, /* redo */
	NULL, /* escape */
};
