/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - standard cschem forge
 *  Copyright (C) 2022, 2023 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., 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <genht/htsp.h>
#include <genht/hash.h>
#include <genht/ht_utils.h>
#include <genvector/vtl0.h>
#include <libfungw/fungw.h>
#include <librnd/core/conf.h>
#include <libcschem/config.h>
#include <libcschem/abstract.h>
#include <libcschem/concrete.h>
#include <libcschem/engine.h>
#include <libcschem/actions_csch.h>
#include <libcschem/attrib.h>
#include <libcschem/libcschem.h>
#include <libcschem/project.h>
#include <libcschem/util_compile.h>
#include <librnd/core/actions.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/rnd_printf.h>
#include <librnd/core/plugins.h>
#include <librnd/core/misc_util.h>
#include <librnd/src_3rd/genregex/regex_se.h>

#include "cond.h"
#include "cond_gram.h"
#include "dlg_test_bench.h"
#include "std_forge.h"

#if 0
typedef struct {
} std_forge_ctx_t; /* per view data */
#endif

static const char std_forge_cookie[] = "std_forge";
static gds_t stmp = {0};
static htsp_t cond_cache; /* key: condition expression source; val: precompiled forgecond_ctx_t */
static vtl0_t cond_stack;

/* skip matches protected with \, also interpret \\ */
static char *strchr_prot(char *s, char chr)
{
	for(; *s != '\0'; s++) {
		if (*s == '\\')
			s++;
		else if (*s == chr)
			return s;
	}
	return NULL;
}

static char *shift(char *last, csch_ahdr_t *aobj, const char *args)
{
	char *next;

	if (last == NULL)
		return NULL;

	next = strchr_prot(last, ',');
	if (next == NULL) {
		rnd_message(RND_MSG_ERROR, "Syntax error in #%ld's forge: too few arguments in %s\n", aobj->aid, args);
		return NULL;
	}
	*next = '\0';
	return next+1;
}

/* execute regex subst to payload in dst; realloc dst as needed */
static void forge_sub_exec(re_se_t *re, char **dst, char *payload, int global)
{
	char *res;
	re_se_subst(re, &res, *dst, payload, !global);
	free(*dst);
	*dst = rnd_strdup(res);
}

static int forge_sub(csch_ahdr_t *aobj, const char *args, int prio, int global, int isattr, const char *srctxt)
{
	csch_attrib_t *a;
	char *pat, *target, *key;
	re_se_t *re;

	stmp.used = 0;
	gds_append_str(&stmp, args);
	pat = stmp.array;

	target = shift(pat, aobj, args);
	key = shift(target, aobj, args);
	if (key == NULL)
		return -1;

	a = csch_attrib_get(&aobj->attr, key);
	if (a == NULL) {
		rnd_message(RND_MSG_ERROR, "Error in regex sub in #%ld's forge: attribute %s does not exist\n", aobj->aid, key);
		return -1;
	}

	if (isattr) {
		csch_attrib_t *ra;

		ra = csch_attrib_get(&aobj->attr, target);
		if (ra != NULL) {
			if (ra->val == NULL) {
				rnd_message(RND_MSG_ERROR, "Error in regex [g]suba in #%ld's forge: referee %s is not scalar\n", aobj->aid, target);
				return -1;
			}
			else
				target = ra->val;
		}
		else
			target = "";
	}

	if (prio > a->prio) {
		csch_source_arg_t *src = csch_attrib_src_pa(aobj, srctxt, "forge", "inst: *sub*");
		csch_attrib_append_src(a, prio, src, 1);
		csch_attr_src_free(src);
		return 0;
	}

	re = re_se_comp(pat);
	if (re == NULL) {
		rnd_message(RND_MSG_ERROR, "Error in regex sub in #%ld's forge: invalid regex '%s'\n", aobj->aid, pat);
		return -1;
	}

	if (a->val == NULL) {
		long n;
		for(n = 0; n < a->arr.used; n++)
			forge_sub_exec(re, &a->arr.array[n], target, global);
	}
	else
		forge_sub_exec(re, &a->val, target, global);

	re_se_free(re);

	{
		csch_source_arg_t *src = csch_attrib_src_pa(aobj, srctxt, "forge", "inst: *sub*");
		csch_attrib_append_src(a, prio, src, 0);
		csch_attr_src_free(src);
	}
	return 0;
}

static int forge_delete(csch_ahdr_t *aobj, const char *args, int prio, const char *srctxt)
{
	csch_source_arg_t *src = csch_attrib_src_pa(aobj, srctxt, "forge", "inst: delete");
	csch_attrib_del(&aobj->attr, prio, args, src);
	return 0;
}

static int forge_create(csch_ahdr_t *aobj, const char *args, int prio, int isarr, const char *srctxt)
{
	csch_attrib_t *a = csch_attrib_get(&aobj->attr, args);

	if (a != NULL) {
		if ((a->val != NULL) && isarr) {
			rnd_message(RND_MSG_ERROR, "Error in 'array' in #%ld's forge: attribute %s is a scalar\n", aobj->aid, args);
			return -1;
		}
		if ((a->arr.used > 0) && !isarr) {
			rnd_message(RND_MSG_ERROR, "Error in 'scalar' in #%ld's forge: attribute %s is an array\n", aobj->aid, args);
			return -1;
		}
		return 0;
	}
	else {
		csch_source_arg_t *src = csch_attrib_src_pa(aobj, srctxt, "forge", isarr ? "inst: array" : "inst: scalar");
		csch_attrib_set(&aobj->attr, prio, args, isarr ? NULL : "", src, NULL);
	}

	return 0;
}

static void prep_app_arr(csch_attrib_t *dst, const char *str, int where)
{
	char *s = rnd_strdup(str);
	if (where < 0)
		vts0_insert_len(&dst->arr, 0, &s, 1);
	else
		vts0_append(&dst->arr, s);
}

/* where: 0 is overwrite, -1 is prepend, +1 is append */
static int forge_copy(csch_ahdr_t *aobj, const char *args, int prio, int where, const char *srctxt)
{
	csch_attrib_t *dst, *src;
	char *dstn, *srcn;
	long n;

	stmp.used = 0;
	gds_append_str(&stmp, args);

	dstn = stmp.array;
	srcn = shift(dstn, aobj, args);
	if (srcn == NULL)
		return -1;

	dst = csch_attrib_get(&aobj->attr, dstn);
	if (dst == NULL) {
		rnd_message(RND_MSG_ERROR, "Error in copy/append/prepend in #%ld's forge: destination attribute %s does not exist\n", aobj->aid, dstn);
		return -1;
	}
	src = csch_attrib_get(&aobj->attr, srcn);
	if (src == NULL) {
		rnd_message(RND_MSG_ERROR, "Error in copy/append/prepend in #%ld's forge: source attribute %s does not exist\n", aobj->aid, srcn);
		return -1;
	}


	if (prio > dst->prio) {
		csch_source_arg_t *src = csch_attrib_src_pa(aobj, srctxt, "forge", "inst: prepend/append/copy");
		csch_attrib_append_src(dst, prio, src, 1);
		csch_attr_src_free(src);
		return 0;
	}

	switch(where) {
		case 0: /* copy */
			if ((src->val == NULL) != (dst->val == NULL)) {
				rnd_message(RND_MSG_ERROR, "Error in copy/append/prepend in #%ld's forge: type of src and dst (%s and %s) does not match\n", aobj->aid, srcn, dstn);
				return -1;
			}
			if (src->val == NULL) {
				/* free dst array */
				for(n = 0; n < dst->arr.used; n++) {
					free(dst->arr.array[n]);
					dst->arr.array[n] = NULL;
				}
				dst->arr.used = 0;

				/* copy array */
				for(n = 0; n < src->arr.used; n++)
					vts0_append(&dst->arr, src->arr.array[n]);
			}
			else {
				free(dst->val);
				dst->val = rnd_strdup(src->val);
			}
			break;

		case +1:
		case -1:
			if (dst->val == NULL) {
				/* dst is an array */
				if (src->val == NULL) {
					for(n = 0; n < src->arr.used; n++)
						prep_app_arr(dst, src->arr.array[n], where);
				}
				else
					prep_app_arr(dst, src->val, where);
			}
			else {
				char *combined;

				/* plain string prepend/append */
				if (src->val == NULL) {
					rnd_message(RND_MSG_ERROR, "Error in copy/append/prepend in #%ld's forge: can't combine array into scalar (%s into %s)\n", aobj->aid, srcn, dstn);
					return -1;
				}
				if (where == -1)
					combined = rnd_concat(src->val, dst->val, NULL);
				else
					combined = rnd_concat(dst->val, src->val, NULL);
				free(dst->val);
				dst->val = combined;
			}
	}

	{
		csch_source_arg_t *src = csch_attrib_src_pa(aobj, srctxt, "forge", "inst: prepend/append/copy");
		csch_attrib_append_src(dst, prio, src, 0);
		csch_attr_src_free(src);
	}

	return 0;
}

static int forge_exec(csch_ahdr_t *aobj, const char *inst, int prio, const char *srctxt)
{
	while(isspace(*inst)) inst++;

/*	rnd_trace(" %s\n", inst);*/

	switch(*inst) {
		case 'a':
			if (strncmp(inst+1, "rray,", 5) == 0)   return forge_create(aobj, inst+6, prio, 1, srctxt);
			if (strncmp(inst+1, "ppend,", 6) == 0)  return forge_copy(aobj, inst+7, prio, +1, srctxt);
			break;
		case 'c':
			if (strncmp(inst+1, "opy,", 4) == 0)  return forge_copy(aobj, inst+5, prio, 0, srctxt);
			break;
		case 'd':
			if (strncmp(inst+1, "elete,", 6) == 0)  return forge_delete(aobj, inst+7, prio, srctxt);
			break;
		case 'g':
			if (strncmp(inst+1, "sub,", 4) == 0)    return forge_sub(aobj, inst+5, prio, 1, 0, srctxt);
			if (strncmp(inst+1, "suba,", 5) == 0)   return forge_sub(aobj, inst+6, prio, 1, 1, srctxt);
			break;
		case 'p':
			if (strncmp(inst+1, "repend,", 7) == 0) return forge_copy(aobj, inst+8, prio, -1, srctxt);
			break;
		case 's':
			if (strncmp(inst+1, "ub,", 3) == 0)     return forge_sub(aobj, inst+4, prio, 0, 0, srctxt);
			if (strncmp(inst+1, "uba,", 4) == 0)    return forge_sub(aobj, inst+5, prio, 0, 1, srctxt);
			if (strncmp(inst+1, "calar,", 6) == 0)  return forge_create(aobj, inst+7, prio, 0, srctxt);
			break;
	}

	rnd_message(RND_MSG_ERROR, "Syntax error in #%ld's forge: unknown instruction %s\n", aobj->aid, inst);
	return -1;
}

RND_INLINE const char *resolve_view(forgecond_ctx_t *ctx, const char *name)
{
	if ((ctx->abst == NULL) || (ctx->abst->prj == NULL))
		return NULL;

	return csch_view_get_prop(ctx->abst->prj, ctx->abst->view_id, name);
}

const char *forgecond_var_resolve_cb(forgecond_ctx_t *ctx, const char *name1, const char *name2)
{
	if (name1 != NULL) {
		switch(*name1) {
			case 's':
				if (strcmp(name1, "stance") == 0) return csch_stance_get(name2);
				break;
			case 'v':
				if (strcmp(name1, "view") == 0) return resolve_view(ctx, name2);
				break;
		}
	}
	return NULL;
}

void forgecond_error_cb(forgecond_ctx_t *ctx, const char *s)
{
	rnd_message(RND_MSG_ERROR, "std_forge condition: %s\n", s);
}



static forgecond_ctx_t *forge_condition_get(const char *objtype, const char *objname, const char *condname, const char *condstr)
{
	forgecond_ctx_t *fc = htsp_get(&cond_cache, condstr);

	if (fc == NULL) {
		fc = calloc(sizeof(forgecond_ctx_t), 1);
		htsp_set(&cond_cache, rnd_strdup(condstr), fc);
		if (forgecond_parse_str(fc, condstr) != 0)
			fc->error = 1;
	}
	if (fc->error) {
		rnd_message(RND_MSG_ERROR, "Failed to compile std_forge attribute condition: %s of %s %s:\n%s\n", condname, objtype, objname, condstr);
		return NULL;
	}

	return fc;
}

static int forge_condition(const char *objtype, const char *objname, csch_ahdr_t *aobj, const char *condname, const char *condstr)
{
	forgecond_ctx_t *fc = forge_condition_get(objtype, objname, condname, condstr);

	if (fc == NULL)
		return -1;

	fc->abst = aobj->abst;
	return forgecond_exec(fc, &cond_stack);
}

static int fc_test_bench_rev(vts0_t *dst, forgecond_ctx_t *fc, long *start)
{
	long istr, ivar1, ivar2;

	if (*start < 0)
		return 0;
	switch(fc->prg.array[*start]) {
		case FGC_LOG_AND:
/*rnd_trace("[%ld] AND\n", *start);*/
			(*start)--;
			if (fc_test_bench_rev(dst, fc, start) != 0)
				return -1;
			if (fc_test_bench_rev(dst, fc, start) != 0)
				return -1;
			return 0;
		case FGC_STR_NEQ:
/*rnd_trace("[%ld] SNQ\n", *start);*/
			(*start)--;
			istr = fc->prg.array[*start];
			(*start)--;
			if (fc->prg.array[*start] != FGC_STR)
				return -1;
			(*start)--;
			ivar2 = fc->prg.array[*start];
			(*start)--;
			ivar1 = fc->prg.array[*start];
			(*start)--;
			if (fc->prg.array[*start] != FGC_VAR2)
				return -1;
			(*start)--;
/*rnd_trace("  istr='%s' ivar1='%s' ivar2='%s'\n", fc->strs.array+istr, fc->strs.array+ivar1, fc->strs.array+ivar2);*/
			vts0_append(dst, fc->strs.array+ivar1);
			vts0_append(dst, fc->strs.array+ivar2);
			vts0_append(dst, fc->strs.array+istr);
			return 0;

	}

	return -1;
}


int forge_condition_get_test_bench(vts0_t *dst, const char *objtype, const char *objname, const char *condname, const char *condstr)
{
	forgecond_ctx_t *fc = forge_condition_get(objtype, objname, condname, condstr);
	long start = fc->prg.used-1;

	if (fc == NULL)
		return -1;

	dst->used = 0;
	return fc_test_bench_rev(dst, fc, &start);
}


static int forge_apply(const char *objtype, const char *objname, csch_ahdr_t *aobj, int prio)
{
	const csch_attrib_t *forge;
	long n;
	int r = 0;
	htsp_entry_t *e;

	/* unconditional */
	forge = csch_attrib_get(&aobj->attr, "forge");
	if (forge != NULL) {
	/*	rnd_trace("--- FORGE in comp %s\n", comp->name);*/
		for(n = 0; (r == 0) && (n < forge->arr.used); n++)
			r |= forge_exec(aobj, forge->arr.array[n], prio, "forge");
	}

	/* conditionals */
	for(e = htsp_first(&aobj->attr); (e != NULL) && (r == 0); e = htsp_next(&aobj->attr, e)) {
		csch_attrib_t *a = e->value;

		if (a->deleted || (a->arr.used < 2) || (e->key[0] != 'f') || (strncmp(e->key, "forge-if/", 9) != 0))
			continue;

		/* execute */
		if (forge_condition(objtype, objname, aobj, e->key, a->arr.array[0])) {
			for(n = 1; (r == 0) && (n < a->arr.used); n++)
				r |= forge_exec(aobj, a->arr.array[n], prio, e->key);
		}
	}

	return r;
}


/*** hooks ***/

fgw_error_t std_forge_compile_comp0(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
/*	fgw_obj_t *obj = argv[0].val.argv0.func->obj;*/
/*	std_forge_ctx_t *ctx = obj->script_data; */
	csch_hook_call_ctx_t *cctx = argv[0].val.argv0.user_call_ctx;
	csch_acomp_t *comp;
	int prio = cctx->view_eng->eprio + CSCH_PRI_PLUGIN_NORMAL;

	CSCH_HOOK_CONVARG(1, FGW_AOBJ, std_forge_comp_update, comp = fgw_aobj(&argv[1]));
	assert(comp->hdr.type == CSCH_ATYPE_COMP);

	res->type = FGW_INT;
	res->val.nat_int = forge_apply("component", comp->name, &comp->hdr, prio);
	return 0;
}

fgw_error_t std_forge_compile_net(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
/*	fgw_obj_t *obj = argv[0].val.argv0.func->obj;*/
/*	std_forge_ctx_t *ctx = obj->script_data; */
	csch_hook_call_ctx_t *cctx = argv[0].val.argv0.user_call_ctx;
	csch_anet_t *net;
	int prio = cctx->view_eng->eprio + CSCH_PRI_PLUGIN_NORMAL;

	CSCH_HOOK_CONVARG(1, FGW_AOBJ, std_forge_comp_update, net = fgw_aobj(&argv[1]));
	assert(net->hdr.type == CSCH_ATYPE_NET);

	res->type = FGW_INT;
	res->val.nat_int = forge_apply("net", net->name, &net->hdr, prio);
	return 0;
}

static int on_load(fgw_obj_t *obj, const char *filename, const char *opts)
{
	fgw_func_reg(obj, "compile_component0", std_forge_compile_comp0);
	fgw_func_reg(obj, "compile_net", std_forge_compile_net);

	/* initialize view-local cache */
	obj->script_data = /*calloc(sizeof(std_forge_ctx_t), 1)*/ NULL;

	return 0;
}

static int on_unload(fgw_obj_t *obj)
{
/*	std_forge_ctx_t *ctx = obj->script_data;
	free(ctx);*/
	return 0;
}


static const fgw_eng_t fgw_std_forge_eng = {
	"std_forge",
	csch_c_call_script,
	NULL,
	on_load,
	on_unload
};

static rnd_action_t std_forge_action_list[] = {
	{"TestBenchDialog", csch_act_TestBenchDialog, csch_acth_TestBenchDialog, csch_acts_TestBenchDialog},
	{"TestBenchModify", csch_act_TestBenchModify, csch_acth_TestBenchModify, csch_acts_TestBenchModify},
	{"quick_attr_forge__if__test_bench", csch_act_quick_attr_forge__if__test_bench, csch_acth_quick_attr_forge__if__test_bench, csch_acts_quick_attr_forge__if__test_bench}
};


int pplg_check_ver_std_forge(int ver_needed) { return 0; }

void pplg_uninit_std_forge(void)
{
	gds_uninit(&stmp);
	csch_dlg_test_bench_uninit();
	vtl0_uninit(&cond_stack);
	genht_uninit_deep(htsp, &cond_cache, {
		forgecond_ctx_t *fc = htent->value;
		free(htent->key);
		forgecond_uninit(fc);
		free(fc);
	});
	rnd_remove_actions_by_cookie(std_forge_cookie);
}

int pplg_init_std_forge(void)
{
	RND_API_CHK_VER;
	fgw_eng_reg(&fgw_std_forge_eng);
	htsp_init(&cond_cache, strhash, strkeyeq);
	csch_dlg_test_bench_init();

	RND_REGISTER_ACTIONS(std_forge_action_list, std_forge_cookie);

	return 0;
}

