/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - alien file format helpers
 *  Copyright (C) 2023 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust in 2023)
 *
 *  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
 */

/* #included from read_helper.c */

/* Execute the generic, per format, user provideed postprocessor action, e.g.
   io_geda_postporc_sheet_load, if it is defined. Returns 0 on success. */
static int alien_postproc_sheet_act(csch_alien_read_ctx_t *ctx)
{
	fgw_func_t *af;
	char *aname = rnd_concat(ctx->fmt_prefix, "_postproc_sheet_load", NULL);
	int res = 0;

	af = rnd_act_lookup(aname);
	if (af != NULL)
		res = rnd_action(&ctx->sheet->hidlib, aname);

	free(aname);
	return res;
}

typedef struct {
	int res;
	const char *act;
	csch_sheet_t *sheet;
	long changed;
} alien_postproc_sheet_conf_t;

static void alien_postproc_sheet_conf_cb(void *user_ctx, pcb_qry_val_t *res, csch_chdr_t *current)
{
	alien_postproc_sheet_conf_t *ppctx = user_ctx;
	int bv;

	if (res->type == PCBQ_VT_COORD)
		bv = res->data.crd != 0;
	else if (res->type == PCBQ_VT_LONG)
		bv = res->data.lng != 0;
	else if (res->type == PCBQ_VT_LST)
		bv = res->data.lst.used > 0;
	else if (res->type == PCBQ_VT_OBJ)
		bv = res->data.obj != NULL;
	else
		return;

	if (!bv)
		return;

	ppctx->sheet->currobj = current;
	current->selected = 1;
	ppctx->res |= rnd_parse_command(&ppctx->sheet->hidlib, ppctx->act, 0) < 0;
	current->selected = 0;
	ppctx->sheet->currobj = NULL;

	ppctx->changed++;
}

static int alien_postproc_sheet_conf_exec(csch_alien_read_ctx_t *ctx, const char *pat, const char *act, int *needs_indir_rend)
{
	pcb_qry_exec_t qctx = {0};
	int qres;
	alien_postproc_sheet_conf_t ppctx;

	ppctx.res = 0;
	ppctx.act = act;
	ppctx.sheet = ctx->sheet;
	ppctx.changed = 0;

	pcb_qry_init(&qctx, ctx->sheet, NULL, -2);
	qres = pcb_qry_run_script(&qctx, ctx->sheet, pat, "sheet-indirect", alien_postproc_sheet_conf_cb, &ppctx);
	pcb_qry_uninit(&qctx);

	if (ppctx.changed)
		*needs_indir_rend = 1;

	pcb_qry_init(&qctx, ctx->sheet, NULL, -1);
	qres |= pcb_qry_run_script(&qctx, ctx->sheet, pat, "sheet", alien_postproc_sheet_conf_cb, &ppctx);
	pcb_qry_uninit(&qctx);

	return (qres < 0) | ppctx.res;
}

static int alien_postproc_sheet_conf(csch_alien_read_ctx_t *ctx)
{
	char *path;
	rnd_conf_native_t *nat;
	rnd_conflist_t *list;
	rnd_conf_listitem_t *i;
	const char *pat, *act;
	int idx, needs_indir_rend = 0;

	path = rnd_concat("plugins/", ctx->fmt_prefix, "/postproc_sheet_load", NULL);
	nat = rnd_conf_get_field(path);
	if (nat == NULL) {
		free(path);
		return 0;
	}

	if (nat->type != RND_CFN_LIST) {
		rnd_message(RND_MSG_ERROR, "Invalid config node type %s: should be a list\n", path);
		free(path);
		return -1;
	}

	list = nat->val.list;
	for(i = rnd_conf_list_first_str(list, &pat, &idx); i != NULL; i = rnd_conf_list_next_str(i, &pat, &idx)) {
		if (i == NULL) {
			rnd_message(RND_MSG_ERROR, "Invalid config node %s: missing action (odd number of list items)\n", path);
			free(path);
			return -1;
		}
		i = rnd_conf_list_next_str(i, &act, &idx);
		if (alien_postproc_sheet_conf_exec(ctx, pat, act, &needs_indir_rend) != 0) {
			rnd_message(RND_MSG_ERROR, "Failed to execute %s\n", path);
			free(path);
			return -1;
		}
	}

	/* if indirect objects have been modified, need to re-render all instances
	   to get the modifications visible */
	if (needs_indir_rend)
		csch_cgrp_render_all(ctx->sheet, &ctx->sheet->direct);

	free(path);
	return 0;
}

int csch_alien_postproc_sheet(csch_alien_read_ctx_t *ctx)
{

	if (ctx->fmt_prefix == NULL) {
		rnd_message(RND_MSG_ERROR, "csch_alien_postproc_sheet(): fmt_prefix not available\n");
		return -1;
	}

	if (alien_postproc_sheet_conf(ctx) != 0)
		return -1;

	return alien_postproc_sheet_act(ctx);
}

void csch_alien_postproc_normalize(csch_alien_read_ctx_t *ctx)
{
	htip_entry_t *e;
	csch_coord_t dx = ctx->sheet->bbox.x1, dy = ctx->sheet->bbox.y1;

	dx = dx / 4000 * 4000;
	dy = dy / 4000 * 4000;

	for(e = htip_first(&ctx->sheet->direct.id2obj); e != NULL; e = htip_next(&ctx->sheet->direct.id2obj, e)) {
		csch_chdr_t *o = e->value;
		csch_move(ctx->sheet, o, -dx, -dy, 0);
	}
}

static int is_text_rot_180(csch_text_t *t)
{
	int res = (fabs((fabs(t->inst_raw_rot) - 180.0)) < 0.01); /* +- 180 */
	/*rnd_trace("T180: '%s' '%s' rot=%f %f mir=%d %d -> %d %f\n", t->text, t->rtext, t->inst_rot, t->inst_raw_rot, t->inst_mirx, t->inst_miry, res, fabs((fabs(t->inst_rot) - 180.0)));*/
	return res;
}

static int is_text_rot_270(csch_text_t *t)
{
	if ((fabs(t->inst_raw_rot - 270.0)) < 0.01) return 1; /* 270 */
	if ((fabs(t->inst_raw_rot + 90.0)) < 0.01) return 1; /* -90 */
	return 0;
}

static void csch_alien_postproc_text_autorot_(csch_alien_read_ctx_t *ctx, csch_cgrp_t *grp, int inref, int fix180, int fix270)
{
	htip_entry_t *e;

	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
		csch_chdr_t *o = e->value;
		if (o->type == CSCH_CTYPE_TEXT) {
			csch_text_t *t = (csch_text_t *)o;
			if (fix180 && is_text_rot_180(t))
				csch_rotate90(ctx->sheet, o, (o->bbox.x1+o->bbox.x2)/2, (o->bbox.y1+o->bbox.y2)/2, 2, 0);
			if (fix270 && is_text_rot_270(t))
				csch_rotate90(ctx->sheet, o, (o->bbox.x1+o->bbox.x2)/2, (o->bbox.y1+o->bbox.y2)/2, 2, 0);
		}
		else if (o->type == CSCH_CTYPE_GRP)
			csch_alien_postproc_text_autorot_(ctx, (csch_cgrp_t *)o, inref, fix180, fix270);
		else if (o->type == CSCH_CTYPE_GRP_REF)
			csch_alien_postproc_text_autorot_(ctx, (csch_cgrp_t *)o, 1, fix180, fix270);
	}
}

void csch_alien_postproc_text_autorot(csch_alien_read_ctx_t *ctx, csch_cgrp_t *grp, int fix180, int fix270)
{
	csch_alien_postproc_text_autorot_(ctx, grp, 0, fix180, fix270);
}

RND_INLINE void rename_sym_redundant_terms(csch_alien_read_ctx_t *ctx, csch_cgrp_t *sym, vtp0_t *tmp, gds_t *stmp)
{
	htip_entry_t *e, *e2;
	long n;

	tmp->used = 0;

	/* Find redundant names with O(n^2) - expect only a few terminals */
	for(e = htip_first(&sym->id2obj); e != NULL; e = htip_next(&sym->id2obj, e)) {
		csch_cgrp_t *term = e->value;

		if (csch_obj_is_grp(&term->hdr) && (term->role == CSCH_ROLE_TERMINAL)) {
			const char *name = csch_attrib_get_str(&term->attr, "name");

			if ((name == NULL) || (*name == '\0'))
				continue;

			for(e2 = htip_first(&sym->id2obj); e2 != NULL; e2 = htip_next(&sym->id2obj, e2)) {
				csch_cgrp_t *term2 = e2->value;

				if (csch_obj_is_grp(&term2->hdr) && (term2->role == CSCH_ROLE_TERMINAL)) {
					const char *name2 = csch_attrib_get_str(&term2->attr, "name");

					if ((name2 == NULL) || (*name2 == '\0'))
						continue;

					if ((e != e2) && (strcmp(name, name2) == 0)) {
						vtp0_append(tmp, term);
						break;
					}
				}
			}
		}
	}

	for(n = 0; n < tmp->used; n++) {
		csch_cgrp_t *term =tmp->array[n];
		csch_attrib_t *a = csch_attrib_get(&term->attr, "name");
		char suffix[64];
		csch_source_arg_t *src;

		stmp->used = 0;
		gds_append_str(stmp, a->val);
		gds_append_str(stmp, "__");
		sprintf(suffix, "%d", term->hdr.oid);
		gds_append_str(stmp, suffix);

		free(a->val);
		a->val = stmp->array;
		stmp->array = NULL;
		stmp->used = stmp->alloced = 0;

		src = csch_attrib_src_c(NULL, 0, 0, "Alien import: redundant terminal names changed");
		csch_attrib_append_src(a, a->prio, src, 0);
	}
}

void csch_alien_postproc_rename_redundant_terms(csch_alien_read_ctx_t *ctx)
{
	htip_entry_t *e;
	vtp0_t tmp = {0};
	gds_t stmp = {0};

	for(e = htip_first(&ctx->sheet->direct.id2obj); e != NULL; e = htip_next(&ctx->sheet->direct.id2obj, e)) {
		csch_cgrp_t *g = e->value;
		if ((g->hdr.type == CSCH_CTYPE_GRP) && (g->role == CSCH_ROLE_SYMBOL))
			rename_sym_redundant_terms(ctx, g, &tmp, &stmp);
	}

	vtp0_uninit(&tmp);
	gds_uninit(&stmp);
}
