 /*
  *                            COPYRIGHT
  *
  *  pcb-rnd, interactive printed circuit board design - chart block graphic
  *  Copyright (C) 2025 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/pcb-rnd
  *    lead developer: http://repo.hu/projects/pcb-rnd/contact.html
  *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
  */

#include <gengeo2d/xform.h>
#include <libcschem/operation.h>
#include <libcschem/cnc_text.h>
#include <libcschem/cnc_any_obj.h>
#include <librnd/hid/hid_dad.h>

/* edit object: a single line object (find the first one) */
static csch_chdr_t *arr_get_edit(csch_cgrp_t *grp)
{
	htip_entry_t *e;

	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
		csch_chdr_t *obj = (csch_chdr_t *)e->value;
		if ((obj->type == CSCH_CTYPE_LINE) || (obj->type == CSCH_CTYPE_ARC))
			return obj;
	}

	return NULL;
}

static const char *heads[] = {"none",   "arrow",   "back-arrow",   "thin-arrow",  "thin-back-arrow",   "slash",    "back-slash",   "orthogonal",  NULL};
typedef enum head_e          {HD_NONE,  HD_ARROW,  HD_BACK_ARROW,  HD_THIN_ARROW,  HD_THIN_BACK_ARROW,  HD_SLASH,  HD_BACK_SLASH,  HD_ORTHOGONAL  } head_t;
static const char *anames[] = {"chart/head/shape", "chart/mid/shape", "chart/tail/shape", NULL};
static const char *snames[] = {"chart/head/size",  "chart/mid/size",  "chart/tail/size",  NULL};
static const char *dnames[] = {"head",             "middle",          "tail", NULL};

RND_INLINE void draw_arrow_line(csch_sheet_t *sheet, csch_cgrp_t *gfx, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2, const char *pen)
{
	csch_line_t *line = csch_line_alloc(sheet, gfx, csch_oid_new(sheet, gfx));
	line->spec.p1.x = x1; line->spec.p1.y = y1;
	line->spec.p2.x = x2; line->spec.p2.y = y2;
	line->hdr.stroke_name = csch_comm_str(sheet, pen, 1);
}

static void draw_arrow_head(csch_cgrp_t *gfx, rnd_coord_t x, rnd_coord_t y, double vx, double vy, head_t he, int adjust, double size, const char *pen)
{
	csch_sheet_t *sheet = gfx->hdr.sheet;
	csch_cpoly_t *poly;
	double nx = -vy, ny = vx, xa, ya, xb, yb, xsize = size*1.5, ysize = size/2.0;

	switch(he) {
		case HD_NONE:
			break;

		case HD_BACK_ARROW:
		case HD_THIN_BACK_ARROW:
			if (!adjust) {
				x -= vx*xsize;
				y -= vy*xsize;
			}
			break;

		case HD_ARROW:
		case HD_THIN_ARROW:
			if (adjust) {
				x += vx*xsize;
				y += vy*xsize;
			}
			break;

		case HD_SLASH:
		case HD_BACK_SLASH:
		case HD_ORTHOGONAL:
			break;
	}

	switch(he) {
		case HD_NONE:
			/* need to draw a non-floater dot at unused ends so that the group bbox is properly computed */
			draw_arrow_line(sheet, gfx, x, y, x, y, pen);
			break;

		case HD_BACK_ARROW:
			vx = -vx;
			vy = -vy;
		case HD_ARROW:
			poly = csch_cpoly_alloc(sheet, gfx, csch_oid_new(sheet, gfx));
			poly->hdr.stroke_name = csch_comm_str(sheet, pen, 1);
			poly->hdr.fill_name = csch_comm_str(sheet, pen, 1);
			poly->has_stroke = poly->has_fill = 1;

			xa = x - vx * xsize + nx * ysize; ya = y - vy * xsize + ny * ysize;
			xb = x - vx * xsize - nx * ysize; yb = y - vy * xsize - ny * ysize;
			poly_new_line(poly, x, y, xa, ya);
			poly_new_line(poly, xa, ya, xb, yb);
			poly_new_line(poly, xb, yb, x, y);
			break;

		case HD_THIN_BACK_ARROW:
			vx = -vx;
			vy = -vy;
		case HD_THIN_ARROW:
			draw_arrow_line(sheet, gfx, x, y, x - vx * xsize + nx * ysize, y - vy * xsize + ny * ysize, pen);
			draw_arrow_line(sheet, gfx, x, y, x - vx * xsize - nx * ysize, y - vy * xsize - ny * ysize, pen);
			break;

		case HD_SLASH:
			draw_arrow_line(sheet, gfx, x - vx * size - nx * size, y - vy * size - ny * size, x + vx * size + nx * size, y + vy * size + ny * size, pen);
			break;
		case HD_BACK_SLASH:
			draw_arrow_line(sheet, gfx, x - vx * size + nx * size, y - vy * size + ny * size, x + vx * size - nx * size, y + vy * size - ny * size, pen);
			break;


		case HD_ORTHOGONAL:
			draw_arrow_line(sheet, gfx, x - nx * size, y - ny * size, x + nx * size, y + ny * size, pen);
			break;
	}
}

/* compute direction vector from xy1 to xy2 */
RND_INLINE void getvxy(double *vxo, double *vyo, double x1, double y1, double x2, double y2)
{
	double len, vx = x2 - x1, vy = y2 - y1;

	if ((vx != 0) || (vy != 0))
		len = sqrt(vx*vx + vy*vy);

	if (len > 0) {
		vx /= len;
		vy /= len;
	}

	*vxo = vx;
	*vyo = vy;
}

/* The group's coordinate is the center of the line. */
static void chart_arrow_update(csch_cgrp_t *grp)
{
	csch_sheet_t *sheet = grp->hdr.sheet;
	csch_cgrp_t *gfx = csch_extobj_gfx_clear(sheet, grp);
	csch_chdr_t *edit_obj =arr_get_edit(grp);
	double vx[3], vy[3], hx[3], hy[3];
	int mandatory[3], adjust[3];
	const char **s, **i, **sz, *pen;
	int n;

	if (edit_obj == NULL) {
		rnd_message(RND_MSG_ERROR, "Can't update chart-arrow extended object without a line child\n");
		return;
	}

	mandatory[0] = 1; adjust[0] = 0;
	mandatory[1] = 0; adjust[1] = 0;
	mandatory[2] = 1; adjust[2] = 1;

	if (edit_obj->type == CSCH_CTYPE_LINE) {
		csch_line_t *line = (csch_line_t *)edit_obj;

		hx[2] = line->spec.p1.x; hy[2] = line->spec.p1.y;
		hx[0] = line->spec.p2.x; hy[0] = line->spec.p2.y;
		hx[1] = (line->spec.p1.x + line->spec.p2.x)/2;
		hy[1] = (line->spec.p1.y + line->spec.p2.y)/2;
		getvxy(&vx[0], &vy[0], line->spec.p1.x, line->spec.p1.y, line->spec.p2.x, line->spec.p2.y);

		vx[1] = vx[2] = vx[0];
		vy[1] = vy[2] = vy[0];
	}
	else if (edit_obj->type == CSCH_CTYPE_ARC) {
		csch_arc_t *arc = (csch_arc_t *)edit_obj;
		csch_coord_t x = 0, y = 0;

		csch_arc_get_spec_endxy(arc, 0, &x, &y);
		hx[0] = x; hy[0] = y;
		getvxy(&vy[0], &vx[0], hx[0], hy[0], arc->spec.c.x, arc->spec.c.y);
		vx[0] = -vx[0];

		csch_arc_get_spec_midxy(arc, &x, &y);
		hx[1] = x; hy[1] = y;
		getvxy(&vy[1], &vx[1], hx[1], hy[1], arc->spec.c.x, arc->spec.c.y);
		vx[1] = -vx[1];

		csch_arc_get_spec_endxy(arc, 1, &x, &y);
		hx[2] = x; hy[2] = y;
		getvxy(&vy[2], &vx[2], hx[2], hy[2], arc->spec.c.x, arc->spec.c.y);
		vx[2] = -vx[2];

		mandatory[1] = 1; /* better bbox with middle point included */
	}
	else {
		rnd_message(RND_MSG_ERROR, "Internal error: invalid edit object in chart_arrow_update()\n");
		return;
	}

	pen = edit_obj->stroke_name.str;

	for(s = anames, sz = snames, n = 0; *s != NULL; s++,sz++,n++) {
		double size = 2000;
		const char *ha = csch_attrib_get_str(&grp->attr, *s);
		const char *hs = csch_attrib_get_str(&grp->attr, *sz);
		head_t he;
		int found;

rnd_trace("size '%s' -> '%s'\n", *sz, hs);

		if ((hs != NULL) && (*hs != '\0')) {
			char *end;
			long l = strtol(hs, &end, 10);
			if ((end[0] == 'k') && (end[1] == '\0'))
				size = l*1000;
			else if (*end != '\0')
				rnd_message(RND_MSG_ERROR, "Invalid arrow size (%s): '%s' - must be an integer\n", *sz, hs);
			else
				size = l;
		}

		found = 0;
		if (ha != NULL) {
			for(he = 0, i = heads; *i != NULL; he++,i++) {
				if (strcmp(ha, *i) == 0) {
					draw_arrow_head(gfx, hx[n], hy[n], vx[n], vy[n], he, adjust[n], size, pen);
					found = 1;
					break;
				}
			}
		}

		if (!found && mandatory[n])
			draw_arrow_head(gfx, hx[n], hy[n], vx[n], vy[n], HD_NONE, adjust[n], size, pen);
	}

	csch_cgrp_update(sheet, grp, 1);
}

static void chart_arrow_floater_edit_post(csch_cgrp_t *grp, csch_chdr_t *flt)
{
	chart_arrow_update(grp);
}

static void chart_arrow_attr_edit_post(csch_cgrp_t *grp, const char *key)
{
	int changed = 0;

	if (key == NULL)
		return;

	if ((key[0] == 'c') && (strncmp(key, "chart/", 6) == 0)) {
		key += 6;
		switch(*key) {
			case 'h': changed = (strncmp(key, "head/", 5) == 0); break;
			case 'm': changed = (strncmp(key, "mid/", 4) == 0); break;
			case 't': changed = (strncmp(key, "tail/", 5) == 0); break;
		}
	}

	if (changed)
		chart_arrow_update(grp);
}

static void chart_arrow_conv_from(vtp0_t *objs)
{
	long n;
	csch_line_t *lin = NULL, *newl;
	csch_arc_t *arc = NULL, *newa;
	csch_coord_t cx, cy;
	csch_cgrp_t *exto;
	csch_sheet_t *sheet;
	csch_source_arg_t *src;

	for(n = objs->used-1; n >= 0; n--) {
		csch_chdr_t *cobj = objs->array[n];
		if (cobj->type == CSCH_CTYPE_LINE) {
			lin = (csch_line_t *)cobj;
			vtp0_remove(objs, n, 1);
			break;
		}
		if (cobj->type == CSCH_CTYPE_ARC) {
			arc = (csch_arc_t *)cobj;
			vtp0_remove(objs, n, 1);
			break;
		}
	}

	if ((lin == NULL) && (arc == NULL))
		return;

	sheet = (arc == NULL) ? lin->hdr.sheet : arc->hdr.sheet;
	uundo_freeze_serial(&sheet->undo);

	exto = (csch_cgrp_t *)csch_op_create(sheet, &sheet->direct, CSCH_CTYPE_GRP);

	if (lin != NULL) {
		cx = (lin->inst.c.p1.x + lin->inst.c.p2.x)/2;
		cy = (lin->inst.c.p1.y + lin->inst.c.p2.y)/2;
		exto->x = cx;
		exto->y = cy;

		newl = (csch_line_t *)csch_cobj_dup(sheet, exto, &lin->hdr, 0, 0);
		newl->hdr.floater = 1; /* so line endpoints can be moved */

		/* normalize line coords within the group (group center is original line
		   center), doing a non-undoable relative move of the ends */
		cx = -cx; cy = -cy;
		csch_line_modify(sheet, newl, &cx, &cy, &cx, &cy, 0, 1, 0);
	}
	else {
		cx = arc->inst.c.c.x; cy = arc->inst.c.c.y;
		exto->x = cx;
		exto->y = cy;

		newa = (csch_arc_t *)csch_cobj_dup(sheet, exto, &arc->hdr, 0, 0);
		newa->hdr.floater = 1; /* so line endpoints can be moved */

		/* normalize arc center within the group (group center is original arc
		   center), doing a non-undoable absolute move of center */
		cx = cy = 0;
		csch_arc_modify(sheet, newa, &cx, &cy, NULL, NULL, NULL, 0, 0);
	}

	src = csch_attrib_src_c(NULL, 0, 0, NULL);
	csch_attrib_set(&exto->attr, 0, "extobj", "chart-arrow", src, NULL);
	src = csch_attrib_src_c(NULL, 0, 0, NULL);
	csch_attrib_set(&exto->attr, 0, anames[0], heads[1], src, NULL);
	exto->extobj = csch_extobj_lookup("chart-arrow");

	chart_arrow_update(exto);
	if (lin != NULL)
		csch_op_remove(sheet, &lin->hdr);
	else
		csch_op_remove(sheet, &arc->hdr);

	uundo_unfreeze_serial(&sheet->undo);
	uundo_inc_serial(&sheet->undo);
}

typedef struct chart_arrow_s {
	RND_DAD_DECL_NOINIT(dlg)
	rnd_timed_chg_t tc;
	csch_cgrp_t *grp;
	csch_chdr_t *editobj;

	int wpen, ashape[3], asize[3];
	head_t orig[3];
} chart_arrow_t;

static void chart_arrow_gui2sheet(void *vctx)
{
	chart_arrow_t *ctx = vctx;
	csch_sheet_t *sheet = ctx->grp->hdr.sheet;
	const char **s, **sz;
	int n, need_update = 0;

	/* make object/attrib modifications */
	uundo_freeze_serial(&sheet->undo);
	for(s = anames, sz = snames, n = 0; *s != NULL; s++,sz++,n++) {
		head_t newshape = ctx->dlg[ctx->ashape[n]].val.lng;
		const char *hs = csch_attrib_get_str(&ctx->grp->attr, *sz);
		const char *newsize = ctx->dlg[ctx->asize[n]].val.str;

		if (newshape != ctx->orig[n]) {
			csch_source_arg_t *src = csch_attrib_src_c(NULL, 0, 0, NULL);
			csch_attr_modify_str(sheet, ctx->grp, CSCH_ATP_USER_DEFAULT, *s, heads[newshape], src, 1);
			need_update = 1;
		}

		if ((newsize != NULL) && ((hs == NULL) || (strcmp(newsize, hs) != 0))) {
			csch_source_arg_t *src = csch_attrib_src_c(NULL, 0, 0, NULL);
			csch_attr_modify_str(sheet, ctx->grp, CSCH_ATP_USER_DEFAULT, *sz, newsize, src, 1);
			need_update = 1;
		}
	}

	/* update the extended object */
	if (need_update) {
		chart_arrow_update(ctx->grp);
		uundo_unfreeze_serial(&sheet->undo);
		uundo_inc_serial(&sheet->undo);
	}
	else
		uundo_unfreeze_serial(&sheet->undo);
}

static void arrow_update_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	chart_arrow_gui2sheet((chart_arrow_t *)caller_data);
}

static void arrow_update_timed_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	chart_arrow_t *ctx = caller_data;
	rnd_timed_chg_schedule(&ctx->tc);
}

static void arrow_change_pen_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	chart_arrow_t *ctx = caller_data;
	csch_sheet_t *sheet = ctx->grp->hdr.sheet;
	const char *newpen;

	newpen = sch_rnd_pen_dlg(sheet, ctx->grp, ctx->editobj->stroke_name.str, 1, 1);
	if (newpen != NULL) {
		csch_comm_str_t nstroke = csch_comm_str(sheet, newpen, 0);
		if (nstroke.str != NULL) {
			rnd_hid_attr_val_t hv;

			/* set pen in obj and update the extobj */
			uundo_freeze_serial(&sheet->undo);
			csch_chdr_pen_name_modify(sheet, ctx->editobj, &nstroke, NULL, 1);
			chart_arrow_update(ctx->grp);
			uundo_unfreeze_serial(&sheet->undo);
			uundo_inc_serial(&sheet->undo);

			/* update the button */
			hv.str = ctx->editobj->stroke_name.str;
			rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wpen, &hv);
		}
		else
			rnd_message(RND_MSG_ERROR, "pen lookup failure\n");
	}
}

static void chart_arrow_gui_edit_dlg(csch_cgrp_t *grp)
{
	int n;
	const char **s, **sz, **d;
	rnd_hid_dad_buttons_t clbtn[] = {{"Close", 0}, {NULL, 0}};
	chart_arrow_t ctx = {0};

	ctx.grp = grp;
	ctx.editobj = arr_get_edit(grp);
	if (ctx.editobj == NULL) {
		rnd_message(RND_MSG_ERROR, "Can't edi chart arrow extended object: there is no text object in this arrow\n");
		return;
	}

	rnd_timed_chg_init(&ctx.tc, chart_arrow_gui2sheet, &ctx);

	RND_DAD_BEGIN_VBOX(ctx.dlg);
		RND_DAD_COMPFLAG(ctx.dlg, RND_HATF_EXPFILL);

		RND_DAD_BEGIN_HBOX(ctx.dlg);
			RND_DAD_LABEL(ctx.dlg, "Pen:");
			RND_DAD_BUTTON(ctx.dlg, ctx.editobj->stroke_name.str);
				RND_DAD_CHANGE_CB(ctx.dlg, arrow_change_pen_cb);
				RND_DAD_HELP(ctx.dlg, "Change the stroke and fill pen of the arrow");
				ctx.wpen = RND_DAD_CURRENT(ctx.dlg);
		RND_DAD_END(ctx.dlg);

		for(d = dnames, s = anames, sz = snames, n = 0; *s != NULL; d++,s++,sz++,n++) {
			const char **i, *ha = csch_attrib_get_str(&grp->attr, *s);
			const char *hs = csch_attrib_get_str(&grp->attr, *sz);
			head_t he = 0;

			/* figure current setting */
			if (ha != NULL)
				for(he = 0, i = heads; *i != NULL; he++,i++)
					if (strcmp(ha, *i) == 0)
						goto found;
			he = 0; /* default when it's not found */

			found:;
			ctx.orig[n] = he;
			RND_DAD_BEGIN_HBOX(ctx.dlg);
				RND_DAD_LABEL(ctx.dlg, *d);
				RND_DAD_LABEL(ctx.dlg, " arrowhead shape:");
				RND_DAD_ENUM(ctx.dlg, heads);
					ctx.ashape[n] = RND_DAD_CURRENT(ctx.dlg);
					RND_DAD_DEFAULT_NUM(ctx.dlg, he);
					RND_DAD_CHANGE_CB(ctx.dlg, arrow_update_cb);
				RND_DAD_LABEL(ctx.dlg, " size:");
				RND_DAD_STRING(ctx.dlg);
					ctx.asize[n] = RND_DAD_CURRENT(ctx.dlg);
					if (hs != NULL)
						RND_DAD_DEFAULT_PTR(ctx.dlg, rnd_strdup(hs));
					RND_DAD_CHANGE_CB(ctx.dlg, arrow_update_timed_cb);
			RND_DAD_END(ctx.dlg);
		}

		RND_DAD_BEGIN_HBOX(ctx.dlg);
			RND_DAD_TIMING(ctx.dlg, &ctx.tc, "pending update...");
			RND_DAD_BEGIN_VBOX(ctx.dlg); /* spring */
				RND_DAD_COMPFLAG(ctx.dlg, RND_HATF_EXPFILL);
			RND_DAD_END(ctx.dlg);
			RND_DAD_BUTTON_CLOSES(ctx.dlg, clbtn);
		RND_DAD_END(ctx.dlg);
	RND_DAD_END(ctx.dlg);

	RND_DAD_NEW("extobj_chart_arrow", ctx.dlg, "Edit chart-arrow extended object", &ctx, rnd_true, NULL);
	rnd_timed_chg_timing_init(&ctx.tc, ctx.dlg_hid_ctx);
	RND_DAD_RUN(ctx.dlg);
	rnd_timed_chg_finalize(&ctx.tc);
	RND_DAD_FREE(ctx.dlg);
}

