 /*
  *                            COPYRIGHT
  *
  *  pcb-rnd, interactive printed circuit board design - chart block graphic
  *  Copyright (C) 2024,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>

static void poly_new_line(csch_cpoly_t *poly, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2)
{
	csch_coutline_t *dst = csch_vtcoutline_alloc_append(&poly->outline, 1);
	csch_line_t *line = &dst->line;

	line->hdr.type = CSCH_CTYPE_LINE;
	line->spec.p1.x = x1; line->spec.p1.y = y1;
	line->spec.p2.x = x2; line->spec.p2.y = y2;
}

/* edit object: a single text object (find the first one) */
static csch_text_t *blk_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_text_t *txt = (csch_text_t *)e->value;
		if (txt->hdr.type == CSCH_CTYPE_TEXT)
			return txt;
	}

	return NULL;
}

static const char *shapes[] = {"rectangle",   "diamond",    "oval",    NULL};
typedef enum shape_e          {SHP_RECTANGLE, SHP_DIAMOND,  SHP_OVAL} shape_t;

#define GET_PENS(grp, stroke, fill) \
do { \
	stroke = csch_attrib_get_str(&grp->attr, "chart/stroke"); \
	if (stroke == NULL) stroke = "sheet-decor"; \
	fill = csch_attrib_get_str(&grp->attr, "chart/fill"); \
	if (fill == NULL) fill = "sheet-decor-fill"; \
} while(0)

/* The group's coordinate is the center of the block. Graphics are drawn in
   relative coordinate system and text is moved so text bbox center matches
   block center. */
static void chart_block_update(csch_cgrp_t *grp)
{
	csch_sheet_t *sheet = grp->hdr.sheet;
	csch_cgrp_t *gfx = csch_extobj_gfx_clear(sheet, grp);
	csch_text_t *txt = blk_get_edit(grp);
	const char *spen, *fpen, *smargin;
	csch_cpoly_t *poly;
	rnd_coord_t tcx, tcy, mx, my;
	rnd_coord_t w, h, rx, ry; /* rounded up to n*1000 grid */
	const char *sshape;
	shape_t shape = SHP_RECTANGLE;

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

	GET_PENS(grp, spen, fpen);

	csch_text_update(sheet, txt, 1);
	poly = csch_cpoly_alloc(sheet, gfx, csch_oid_new(sheet, gfx));
	poly->hdr.stroke_name = csch_comm_str(sheet, spen, 1);
	poly->hdr.fill_name = csch_comm_str(sheet, fpen, 1);
	poly->has_stroke = poly->has_fill = 1;

	w = ((txt->hdr.bbox.x2 - txt->hdr.bbox.x1) + 1500) / 1000 * 1000;
	h = ((txt->hdr.bbox.y2 - txt->hdr.bbox.y1) + 500) / 1000 * 1000;
	rx = w >> 1;
	ry = h >> 1;

	smargin = csch_attrib_get_str(&grp->attr, "chart/margin");
	if (smargin != NULL) {
		rnd_bool succ;
		csch_coord_t margin = rnd_get_value(smargin, NULL, NULL, &succ);
		if (succ) {
			if (margin != 0) {
				rx += P2C(margin*2);
				ry += P2C(margin*2);
			}
		}
		else
			rnd_message(RND_MSG_ERROR, "Ignoring broken chart/margin value '%s' (must be a coord value)\n", smargin);
	}

	/* figure shape */
	sshape = csch_attrib_get_str(&grp->attr, "chart/shape");
	if (sshape != NULL) {
		int n;
		for(n = 0; shapes[n] != NULL; n++) {
			if (strcmp(shapes[n], sshape) == 0) {
				shape = n;
				break;
			}
		}
	}

	/* draw shape */
	switch(shape) {
		case SHP_RECTANGLE:
			poly_new_line(poly, -rx, -ry, +rx, -ry);
			poly_new_line(poly, +rx, -ry, +rx, +ry);
			poly_new_line(poly, +rx, +ry, -rx, +ry);
			poly_new_line(poly, -rx, +ry, -rx, -ry);
			break;

		case SHP_DIAMOND:
			rx *= 2; ry *= 2;
			poly_new_line(poly, 0, +ry, +rx, 0);
			poly_new_line(poly, +rx, 0, 0, -ry);
			poly_new_line(poly, 0, -ry, -rx, 0);
			poly_new_line(poly, -rx, 0, 0, +ry);
			break;

		case SHP_OVAL:
			{
				double a;
				rnd_coord_t x, y, lx, ly, fx, fy;

				rx *= 1.5; ry *= 1.5;
				for(a = 0; a < 2*M_PI; a += 0.2) {
					x = cos(a) * rx;
					y = sin(a) * ry;
					if (a == 0) {
						fx = x;
						fy = y;
					}
					else
						poly_new_line(poly, lx, ly, x, y);
					lx = x;
					ly = y;
				}
				poly_new_line(poly, lx, ly, fx, fy);
			}
			break;
	}

	/* center text */
	tcx = (txt->hdr.bbox.x2 + txt->hdr.bbox.x1)/2;
	tcy = (txt->hdr.bbox.y2 + txt->hdr.bbox.y1)/2;
	mx = tcx - grp->x; my = tcy - grp->y;
	if ((mx != 0) || (my != 0))
		csch_move(sheet, &txt->hdr, -mx, -my, 1);

	csch_cgrp_update(sheet, grp, 1);
}

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

	if (key == NULL)
		return;

	switch(*key) {
		case 'f': changed = (strcmp(key, "fill") == 0); break;
		case 'm': changed = (strcmp(key, "margin") == 0); break;
		case 's': changed = (strcmp(key, "shape") == 0) || (strcmp(key, "stroke") == 0); break;
	}

	if (changed)
		chart_block_update(grp);
}

static void chart_block_conv_from(vtp0_t *objs)
{
	long n;
	csch_text_t *txt = NULL, *newt;
	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_TEXT) {
			txt = (csch_text_t *)cobj;
			vtp0_remove(objs, n, 1);
			break;
		}
	}

	if (txt == NULL)
		return;

	sheet = txt->hdr.sheet;
	uundo_freeze_serial(&sheet->undo);

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

	newt = (csch_text_t *)csch_cobj_dup(sheet, exto, &txt->hdr, 0, 0);
	newt->spec1.x = newt->spec1.y = 0;
	csch_text_dyntext_render(newt);
	newt->bbox_calced = 0; /* allow update() to recompute text bbox */

	cx = ((txt->hdr.bbox.x2 + txt->hdr.bbox.x1)/2000 * 1000);
	cy = ((txt->hdr.bbox.y2 + txt->hdr.bbox.y1)/2000 * 1000);

	exto->x = cx;
	exto->y = cy;

	src = csch_attrib_src_c(NULL, 0, 0, NULL);
	csch_attrib_set(&exto->attr, 0, "extobj", "chart-block", src, NULL);
	exto->extobj = csch_extobj_lookup("chart-block");

	csch_cgrp_update(sheet, exto, 1); /* get txt coords updated for the new group origin */

	chart_block_update(exto);
	csch_op_remove(sheet, &txt->hdr);

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

typedef struct chart_block_s {
	RND_DAD_DECL_NOINIT(dlg)
	rnd_timed_chg_t tc;
	csch_cgrp_t *grp;
	csch_text_t *editobj;
	int wtpen, wspen, wfpen;
	int wmargin, wstr, wshape;
	int oldshape;
	csch_coord_t oldmargin;
	const char *oss;
} chart_block_t;

static void chart_block_gui2sheet(void *vctx)
{
	chart_block_t *ctx = vctx;
	const char *newstr = ctx->dlg[ctx->wstr].val.str, *newshape = shapes[ctx->dlg[ctx->wshape].val.lng];
	csch_coord_t newmargin = P2C(ctx->dlg[ctx->wmargin].val.crd);
	csch_sheet_t *sheet = ctx->grp->hdr.sheet;
	int need_update = 0;

	/* make object/attrib modifications */
	uundo_freeze_serial(&sheet->undo);
	if (strcmp(newstr, ctx->editobj->text) != 0) {
		csch_text_modify(sheet, ctx->editobj, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &newstr, NULL, NULL, 1, 0);
		csch_text_dyntext_render(ctx->editobj);
		ctx->editobj->bbox_calced = 0; /* allow update() to recompute text bbox */
		need_update = 1;
	}
	if (newmargin != ctx->oldmargin) {
		csch_source_arg_t *src = csch_attrib_src_c(NULL, 0, 0, NULL);
		char tmp[128];

		rnd_snprintf(tmp, sizeof(tmp), "%$rc", newmargin);
		csch_attr_modify_str(sheet, ctx->grp, CSCH_ATP_USER_DEFAULT, "chart/margin", tmp, src, 1);
		ctx->oldmargin = newmargin;
		need_update = 1;
	}
	if ((ctx->oss == NULL) || (strcmp(ctx->oss, newshape) != 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, "chart/shape", newshape, src, 1);
		need_update = 1;
	}

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

static void block_update_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	chart_block_gui2sheet((chart_block_t *)caller_data);
}

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

static void block_change_text_pen_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	chart_block_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->hdr.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->hdr, &nstroke, NULL, 1);
			chart_block_update(ctx->grp);
			uundo_unfreeze_serial(&sheet->undo);
			uundo_inc_serial(&sheet->undo);

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

static void block_change_attr_pen_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr, const char *aname)
{
	chart_block_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->hdr.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;
			csch_source_arg_t *src = csch_attrib_src_c(NULL, 0, 0, NULL);

			/* set pen attr */
			uundo_freeze_serial(&sheet->undo);
			csch_attr_modify_str(sheet, ctx->grp, CSCH_ATP_USER_DEFAULT, aname, nstroke.str, src, 1);
			chart_block_update(ctx->grp);
			uundo_unfreeze_serial(&sheet->undo);
			uundo_inc_serial(&sheet->undo);

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

}

static void block_change_stroke_pen_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	block_change_attr_pen_cb(hid_ctx, caller_data, attr, "chart/stroke");
}

static void block_change_fill_pen_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	block_change_attr_pen_cb(hid_ctx, caller_data, attr, "chart/fill");
}


static void chart_block_gui_edit_dlg(csch_cgrp_t *grp)
{
	int n;
	const char **sn, *spen, *fpen, *smargin;
	rnd_hid_dad_buttons_t clbtn[] = {{"Close", 0}, {NULL, 0}};
	chart_block_t ctx = {0};

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

	GET_PENS(grp, spen, fpen);

	ctx.oldshape = -1;
	ctx.oss = csch_attrib_get_str(&grp->attr, "chart/shape");
	if (ctx.oss != NULL) {
		for(sn = shapes, n = 0; *sn != NULL; sn++,n++) {
			if (strcmp(*sn, ctx.oss) == 0) {
				ctx.oldshape = n;
				break;
			}
		}
	}
	if (ctx.oldshape < 0)
		ctx.oldshape = 0; /* default: rectangle */

	smargin = csch_attrib_get_str(&grp->attr, "chart/margin");
	if (smargin != NULL)
		ctx.oldmargin = rnd_get_value(smargin, NULL, NULL, NULL);

	rnd_timed_chg_init(&ctx.tc, chart_block_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, "Text string:");
			RND_DAD_STRING(ctx.dlg);
				ctx.wstr = RND_DAD_CURRENT(ctx.dlg);
				RND_DAD_DEFAULT_PTR(ctx.dlg, rnd_strdup(ctx.editobj->text));
				RND_DAD_CHANGE_CB(ctx.dlg, block_update_timed_cb);
		RND_DAD_END(ctx.dlg);

		RND_DAD_BEGIN_HBOX(ctx.dlg);
			RND_DAD_LABEL(ctx.dlg, "Text Pen:");
			RND_DAD_BUTTON(ctx.dlg, ctx.editobj->hdr.stroke_name.str);
				RND_DAD_CHANGE_CB(ctx.dlg, block_change_text_pen_cb);
				RND_DAD_HELP(ctx.dlg, "Change the stroke pen of the text");
				ctx.wtpen = RND_DAD_CURRENT(ctx.dlg);
		RND_DAD_END(ctx.dlg);

		RND_DAD_BEGIN_HBOX(ctx.dlg);
			RND_DAD_LABEL(ctx.dlg, "Stroke pen:");
			RND_DAD_BUTTON(ctx.dlg, spen);
				RND_DAD_CHANGE_CB(ctx.dlg, block_change_stroke_pen_cb);
				RND_DAD_HELP(ctx.dlg, "Change the stroke pen of block graphics");
				ctx.wtpen = RND_DAD_CURRENT(ctx.dlg);
		RND_DAD_END(ctx.dlg);

		RND_DAD_BEGIN_HBOX(ctx.dlg);
			RND_DAD_LABEL(ctx.dlg, "Fill pen:");
			RND_DAD_BUTTON(ctx.dlg, fpen);
				RND_DAD_CHANGE_CB(ctx.dlg, block_change_fill_pen_cb);
				RND_DAD_HELP(ctx.dlg, "Change the stroke pen of block graphics");
				ctx.wtpen = RND_DAD_CURRENT(ctx.dlg);
		RND_DAD_END(ctx.dlg);

		RND_DAD_BEGIN_HBOX(ctx.dlg);
			RND_DAD_LABEL(ctx.dlg, "Shape:");
			RND_DAD_ENUM(ctx.dlg, shapes);
				ctx.wshape = RND_DAD_CURRENT(ctx.dlg);
				RND_DAD_DEFAULT_NUM(ctx.dlg, ctx.oldshape);
				RND_DAD_CHANGE_CB(ctx.dlg, block_update_cb);
		RND_DAD_END(ctx.dlg);

		RND_DAD_BEGIN_HBOX(ctx.dlg);
			RND_DAD_LABEL(ctx.dlg, "Margin:");
			RND_DAD_CSCH_COORD(ctx.dlg);
				RND_DAD_DEFAULT_NUM(ctx.dlg, ctx.oldmargin);
				ctx.wmargin = RND_DAD_CURRENT(ctx.dlg);
				RND_DAD_CHANGE_CB(ctx.dlg, block_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_block", ctx.dlg, "Edit chart-block 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);
}

