/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - sch-rnd (executable)
 *  Copyright (C) 2020,2022 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.*
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  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 "config.h"

#include <librnd/core/error.h>
#include <librnd/core/rnd_printf.h>
#include <librnd/core/compat_misc.h>

#include <genregex/regex_sei.h>

#include "concrete.h"
#include "cnc_grp.h"
#include "cnc_pen.h"
#include "cnc_text.h"
#include "cnc_any_obj.h"
#include "cnc_obj.h"
#include "cnc_conn.h"
#include "operation.h"
#include "op_common.h"
#include "util_wirenet.h"

#include "util_grp.h"

/* cschem convention for minimum terminal grid */
#define DEF_TERM_GRID 4000.0

/* Find and return the bottom floater text object */
static csch_text_t *bottom_floater(const csch_cgrp_t *grp)
{
	htip_entry_t *e;
	csch_text_t *best = NULL;
	csch_coord_t besty = CSCH_COORD_MAX;

	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
		csch_text_t *t = e->value;

		if ((t->hdr.type != CSCH_CTYPE_TEXT) || !t->hdr.floater) continue;

		if (t->spec1.y < besty) {
			besty = t->spec1.y;
			best = t;
		}
	}

	return best;
}

/* look at grp and fill in x1;y1 where the next floater dyntext should be placed */
static void floater_placement_below(const csch_cgrp_t *grp, const csch_cpen_t *pen, csch_coord_t *x1, csch_coord_t *y1)
{
	csch_text_t *bottom;

	bottom = bottom_floater(grp);
	if (bottom != NULL) {
		csch_coord_t step = pen->font_height;
		step = ceil(step / DEF_TERM_GRID) * DEF_TERM_GRID;
		*x1 = bottom->spec1.x;
		*y1 = bottom->spec1.y - step;
	}
	else {
		/* first floater; start placing at the center */
		*x1 = floor(((grp->hdr.bbox.x1 + grp->hdr.bbox.x2) / 2.0 - grp->x) / DEF_TERM_GRID) * DEF_TERM_GRID;
		*y1 = floor(((grp->hdr.bbox.y1 + grp->hdr.bbox.y2) / 2.0 - grp->y) / DEF_TERM_GRID) * DEF_TERM_GRID;
	}
}

static void floater_placement(const csch_cgrp_t *grp, const csch_cpen_t *pen, csch_coord_t *x1, csch_coord_t *y1, csch_coord_t *hint_xy)
{
	switch(grp->role) {
		case CSCH_ROLE_WIRE_NET:
			if (hint_xy != NULL) {
				*x1 = hint_xy[0];
				*y1 = hint_xy[1];
				return;
			}
			break;
		default: break;
	}

	/* fallback: place at grp center or below last */
	floater_placement_below(grp, pen, x1, y1);
}


/* Heuristics to decide default pen preferences */
static const char **def_pen_names(csch_cgrp_t *grp, const char *key)
{
	switch(grp->role) {
		case CSCH_ROLE_SYMBOL:
			{
				static const char *def_pen_sym[] = {"sym-decor", "sheet-decor", NULL};
				return def_pen_sym;
			}
			break;

		case CSCH_ROLE_WIRE_NET:
		case CSCH_ROLE_BUS_NET:
		case CSCH_ROLE_HUB_POINT:
			{
				static const char *def_pen_wire[] = {"wire", "sheet-decor", NULL};
				return def_pen_wire;
			}
			break;

		case CSCH_ROLE_JUNCTION:
			{
				static const char *def_pen_junc[] = {"junction", "wire", "sheet-decor", NULL};
				return def_pen_junc;
			}
			break;

		case CSCH_ROLE_BUS_TERMINAL:
		case CSCH_ROLE_TERMINAL:
			{
				static const char *def_pen_term[] = {"term-primary", "sym-decor", "sheet-decor", NULL};
				return def_pen_term;
			}
			break;


		case CSCH_ROLE_invalid:
		case CSCH_ROLE_empty:
			break;
	}

	/* final fallback */
	{
		static const char *def_pen_any[] = {"sheet-decor", NULL};
		return def_pen_any;
	}
}

csch_text_t *csch_auto_attr_place(csch_sheet_t *sheet, csch_cgrp_t *grp, const char *key, const char *stroke, const char *atempl, csch_coord_t *hint_xy)
{
	csch_text_t *text;
	csch_cpen_t *pen = NULL;
	const char **pn, **opn;
	csch_coord_t x1, y1;

	if (stroke != NULL)
		pen = csch_pen_resolve(grp, stroke);

	if (pen == NULL) {
		for(opn = pn = def_pen_names(grp, key); (pen == NULL) && (*pn != NULL); pn++) {
			pen = csch_pen_resolve(grp, *pn);
			if ((pen != NULL) && (pen->font_height <= 0))
				pen = NULL;
		}
	}

	if (pen == NULL) {
		rnd_message(RND_MSG_ERROR, "Can't place floater: no pen found\n; please define one of the following pens:");
		for(pn = opn; *pn != NULL; pn++)
			rnd_message(RND_MSG_ERROR, " %s", *pn);
		rnd_message(RND_MSG_ERROR, "\n");
		return 0;
	}

	if (hint_xy != NULL)
		csch_cgrp_inverse_xform(grp, &hint_xy[0], &hint_xy[1], 1);

	floater_placement(grp, pen, &x1, &y1, hint_xy);


	text = (csch_text_t *)csch_op_create(sheet, grp, CSCH_CTYPE_TEXT);
	text->spec1.x = x1;
	text->spec1.y = y1;
	if (atempl != NULL)
		text->text = rnd_strdup(atempl);
	else
		text->text = rnd_strdup_printf("%%../A.%s%%", key);
	text->hdr.stroke_name = csch_comm_str(sheet, pen->name.str, 1);
	text->hdr.floater = 1;
	text->dyntext = 1;
	csch_text_update(sheet, text, 1);

	csch_cobj_redraw(text);

	return text;
}

csch_text_t *csch_auto_attr_create(csch_sheet_t *sheet, csch_cgrp_t *grp, const char *key, const char *val, const char *stroke, const char *atempl)
{
	csch_source_arg_t *src;

	src = csch_attrib_src_c(NULL, 0, 0, NULL);

	csch_cobj_attrib_set(sheet, grp, CSCH_ATP_USER_DEFAULT, key, val, src);
	if (stroke != NULL)
		return csch_auto_attr_place(sheet, grp, key, stroke, atempl, NULL);
	return NULL;
}

/* Rename object pen references within the group using regex subst; this can
   be used to rename sheet-decor pens to sym-decor. */
static void grp_pen_rename(csch_sheet_t *sheet, csch_cgrp_t *grp, const char *regex, const char *subst)
{
	htip_entry_t *e;
	re_sei_t *rx = re_sei_comp(regex);

	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
		csch_chdr_t *obj = e->value;
		if ((obj->stroke_name.str != NULL) && re_sei_exec(rx, obj->stroke_name.str)) {
			char *dst;
			if (re_sei_subst(rx, &dst, obj->stroke_name.str, subst, 0)) {
				csch_comm_str_t stroke_name = csch_comm_str(sheet, dst, 1);
				csch_chdr_pen_name_modify(sheet, obj, &stroke_name, NULL, 1);
			}
		}
		if ((obj->fill_name.str != NULL) && re_sei_exec(rx, obj->fill_name.str)) {
			char *dst;
			if (re_sei_subst(rx, &dst, obj->fill_name.str, subst, 0)) {
				csch_comm_str_t fill_name = csch_comm_str(sheet, dst, 1);
				csch_chdr_pen_name_modify(sheet, obj, NULL, &fill_name, 1);
			}
		}
	}

	re_sei_free(rx);
}

void csch_cgrp_update_dsply_recurse(csch_sheet_t *sheet, csch_cgrp_t *grp)
{
	htip_entry_t *e;

	csch_cobj_update_dsply(sheet, &grp->hdr);

	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
		csch_chdr_t *obj = e->value;
		if (csch_obj_is_grp(obj))
			csch_cgrp_update_dsply_recurse(sheet, (csch_cgrp_t *)obj);
		else
			csch_cobj_update_dsply(sheet, obj);
	}
}


void csch_cgrp_role_side_effects(csch_sheet_t *sheet, csch_cgrp_t *grp, csch_role_t role)
{
	csch_text_t *t;
	static int zero = 0;

	/* side effects for advanced groups */
	switch(role) {
		case CSCH_ROLE_SYMBOL:
			csch_auto_attr_create(sheet, grp, "role", "symbol", NULL, NULL);
			csch_auto_attr_create(sheet, grp, "name", "REFDES", "sym-primary", NULL);
			grp_pen_rename(sheet, grp, "^sheet-", "sym-");
			break;
		case CSCH_ROLE_TERMINAL:
			csch_auto_attr_create(sheet, grp, "role", "terminal", NULL, NULL);
			t = csch_auto_attr_create(sheet, grp, "name", "TERMNAME", "term-primary", "%../a.display/name%");
			grp_pen_rename(sheet, grp, "^sheet-", "term-");
			
			csch_commprp_modify(sheet, &t->hdr, NULL, &zero, 1);
			break;

		default: break;
	}

	csch_cgrp_update_dsply_recurse(sheet, grp);
}


static void csch_cgrp_replace_attr(csch_sheet_t *sheet, csch_cgrp_t *new, csch_cgrp_t *dst, csch_cgrp_t *src, csch_cgrp_replace_how_t how)
{
	if (how & CSCH_REPLGRP_KEEP_DST_ATTR) {
		htsp_entry_t *e, *already;
		for(e = htsp_first(&dst->attr); e != NULL; e = htsp_next(&dst->attr, e)) {
			if (how & CSCH_REPLGRP_PREFER_DST_ATTR)
				already = NULL;
			else
				already = htsp_getentry(&new->attr, e->key);

			if (already == NULL) {
				csch_attrib_t *newa = csch_attrib_dup(e->value);
				htsp_set(&new->attr, newa->key, newa);
			}
		}
	}
}

int csch_cgrp_replace(csch_sheet_t *sheet, csch_cgrp_t *dst, csch_cgrp_t *src, csch_cgrp_replace_how_t how)
{
	csch_cgrp_t *newg;
	csch_chdr_t *newo;

	uundo_freeze_serial(&sheet->undo);
	csch_wirenet_recalc_freeze(sheet);

	newo = csch_cobj_dup(sheet, dst->hdr.parent, &src->hdr, 0, 0);

	if (newo == NULL) {
		csch_wirenet_recalc_unfreeze(sheet);
		uundo_unfreeze_serial(&sheet->undo);
		return -1;
	}

	newg = ((csch_cgrp_t *)newo);
	newg->sym_prefer_loclib = 0; /* lib buffer paste related special casing */

	/* copy transformation */
	newg->spec_rot = dst->spec_rot;
	newg->x = dst->x;
	newg->y = dst->y;
	newg->mirx = dst->mirx;
	newg->miry = dst->miry;

	csch_cgrp_replace_attr(sheet, newg, dst, src, how);

	csch_op_remove(sheet, &dst->hdr);

	csch_cgrp_update(sheet, newg, 1);
	csch_op_inserted(sheet, newo->parent, newo);


	csch_conn_auto_recalc(sheet, newo);

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

	return 0;
}



