/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2019 Tibor 'Igor2' Palinkas
 *
 *  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 <stdlib.h>
#include <stdarg.h>
#include <libfungw/fungw.h>

#include "actions_csch.h"
#include <librnd/core/compat_misc.h>
#include "project.h"

#include "engine.h"

#define MAX_ARGS 16

const char *csch_enghk_names[CSCH_ENGHK_max] = {
	"terminal_name_to_port_name",
	"wirenet_name_to_net_name",
	"attrib_net_name_to_net_name",
	"symbol_name_to_component_name",
	"symbol_joined_component",
	"compile_component0",
	"compile_component1",
	"compile_component2",
	"compile_port",
	"compile_net",
	"compile_conns_before",
	"compile_conns_after",
	"compile_project_before",
	"compile_project_after"
};

TODO("fungw: remove this in favor of fgws_c_call_script() in fungwbind (make sure it is installed)")
fgw_error_t csch_c_call_script(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	fgw_error_t rv;
	fgw_func_t *fnc = argv[0].val.func;
	rv = fnc->func(res, argc, argv);
	fgw_argv_free(fnc->obj->parent, argc, argv);
	return rv;
}

csch_view_eng_t *csch_eng_alloc(csch_view_t *view, const char *user_name, const char *eng_name, const char *options)
{
	csch_view_eng_t *eng;
	fgw_obj_t *obj;
	csch_eng_hook_t i;

	eng = calloc(sizeof(csch_view_eng_t), 1);
	obj = fgw_obj_new2(&view->fgw_ctx, user_name, eng_name, options, NULL, eng);
	if (obj == NULL) {
		free(eng);
		return NULL;
	}

	eng->obj = obj;
	if (options != NULL)
		eng->options = rnd_strdup(options);

	for(i = 0; i < CSCH_ENGHK_max; i++)
		eng->hook[i] = fgw_func_lookup_in(obj, csch_enghk_names[i]);

	return eng;
}

void csch_eng_free(csch_view_t *view, csch_view_eng_t *eng)
{
	fgw_obj_unreg(&view->fgw_ctx, eng->obj);
	free(eng->options);
	free(eng);
}
/*** calls ***/

/* returns argc or -1 on error; arguments are packed starting from skip_first+1,
   making room for caller supplied arguments before argv[1] if skip_first > 0 */
static int eng_load_args(csch_hook_call_ctx_t *cctx, fgw_arg_t *argv, int max_argc, va_list ap, int skip_first)
{
	int argc;

	argv[0].type = FGW_FUNC;
	argv[0].val.argv0.user_call_ctx = cctx;

	for(argc = 1 + skip_first;;argc++) {
		fgw_type_t type = va_arg(ap, fgw_type_t);
		if (argc >= max_argc)
			return -1;
		if (type == FGW_INVALID)
			break;

		argv[argc].type = type;
		type = type & 0x3FF;
		if ((argv[argc].type) & FGW_PTR)
			argv[argc].val.ptr_void = va_arg(ap, void *);
		else switch((int)type) {
			case FGW_CHAR:    argv[argc].val.nat_char = va_arg(ap, int); break;
			case FGW_UCHAR:   argv[argc].val.nat_uchar = va_arg(ap, int); break;
			case FGW_SCHAR:   argv[argc].val.nat_schar = va_arg(ap, int); break;
			case FGW_SHORT:   argv[argc].val.nat_short = va_arg(ap, int); break;
			case FGW_USHORT:  argv[argc].val.nat_ushort = va_arg(ap, int); break;
			case FGW_INT:     argv[argc].val.nat_int = va_arg(ap, int); break;
			case FGW_UINT:    argv[argc].val.nat_uint = va_arg(ap, unsigned int); break;
			case FGW_LONG:    argv[argc].val.nat_long = va_arg(ap, long); break;
			case FGW_ULONG:   argv[argc].val.nat_ulong = va_arg(ap, unsigned long); break;
			case FGW_SIZE_T:  argv[argc].val.nat_size_t = va_arg(ap, size_t); break;
			case FGW_FLOAT:   argv[argc].val.nat_float = va_arg(ap, double); break;
			case FGW_DOUBLE:  argv[argc].val.nat_double = va_arg(ap, double); break;
			case FGW_AOBJ:
			case FGW_COBJ:
			case FGW_HPATH:
				argv[argc].val.ptr_void = va_arg(ap, void *);
				break;
			default:
				assert(!"invalid fungw type");
		}
	}
	return argc;
}

void csch_eng_call_namemod(csch_project_t *proj, int viewid, csch_hier_path_t *hpath, csch_eng_hook_t hkid, char **res_glob, char **res_loc, const char *defval, ...)
{
	va_list ap;
	csch_view_t *view;
	void **v;
	int argc;
	long n;
	char *sres_glob = NULL, *sres_loc = NULL;
	fgw_arg_t argv[MAX_ARGS], ares;
	csch_hook_call_ctx_t cctx;

	if (viewid < 0)
		viewid = proj->curr;

	v = vtp0_get(&proj->views, viewid, 0);
	if (v == NULL)
		goto error;
	view = *v;

	va_start(ap, defval);
	argc = eng_load_args(&cctx, argv, (sizeof(argv)/sizeof(argv[0])), ap, 1);
	va_end(ap);
	if (argc < 0)
		goto error;

	argv[1].type = 0; /* avoid invalid memory handling in corner case */

	sres_glob = rnd_strdup(defval);
	sres_loc = rnd_strdup(defval);
	cctx.project = proj;
	cctx.view_id = viewid;
	cctx.hpath = hpath;

	for(n = 0; n < view->engines.used; n++) {
		csch_view_eng_t *eng = view->engines.array[n];
		fgw_func_t *hk = eng->hook[hkid];
		if (hk == NULL)
			continue;

		cctx.view_eng = eng;
		ares.type = FGW_INT;
		ares.val.nat_int = 0;
		argv[0].type = FGW_FUNC;
		argv[0].val.argv0.func = hk;
		argv[1].type = FGW_STR;
		argv[1].val.cstr = defval;
		if (hk->func(&ares, argc, argv) == 0) {
			if ((ares.type & FGW_STR) == FGW_STR) {
				char *names, *new_glob, *new_loc;

				/* sort out allocation of returned string */
				if (ares.type & FGW_DYN) { /* steal allocated string */
					names = ares.val.str;
					ares.val.str = NULL;
				}
				else /* need to dup static string */
					names = rnd_strdup(ares.val.str);

				new_glob = names;
				new_loc = strpbrk(new_glob, "\n\r");
				if (new_loc != NULL) {
					*new_loc = '\0';
					new_loc++;
					while((*new_loc == '\n') || (*new_loc == '\r')) new_loc++;
				}

				/* override sres_glob with new value */
				if (sres_glob != defval)
					free(sres_glob);
				sres_glob = new_glob;

				if (new_loc != NULL) {
					if (sres_loc != defval)
						free(sres_loc);
					sres_loc = rnd_strdup(new_loc); /* it's part of the new_glob allocation, need to reallocate */
				}

			}
			fgw_arg_free(&view->fgw_ctx, &ares);
		}
	}

	fgw_argv_free(&view->fgw_ctx, argc, argv);

	*res_glob = sres_glob;
	*res_loc  = sres_loc;
	return;
	error:;
	free(sres_glob);
	free(sres_loc);
	*res_glob = *res_loc = NULL;
}

int csch_eng_call(csch_project_t *proj, int viewid, csch_hier_path_t *hpath, csch_eng_hook_t hkid, ...)
{
	va_list ap;
	csch_view_t *view;
	void **v;
	int argc;
	long n;
	fgw_arg_t argv[MAX_ARGS], ares;
	csch_hook_call_ctx_t cctx;

	if (viewid < 0)
		viewid = proj->curr;

	v = vtp0_get(&proj->views, viewid, 0);
	if (v == NULL)
		return -1;
	view = *v;

	va_start(ap, hkid);
	argc = eng_load_args(&cctx, argv, (sizeof(argv)/sizeof(argv[0])), ap, 0);
	va_end(ap);
	if (argc < 0)
		return -1;

	cctx.project = proj;
	cctx.view_id = viewid;
	cctx.hpath = hpath;

	for(n = 0; n < view->engines.used; n++) {
		csch_view_eng_t *eng = view->engines.array[n];
		fgw_func_t *hk = eng->hook[hkid];
		if (hk == NULL)
			continue;

		cctx.view_eng = eng;
		ares.type = FGW_INT;
		ares.val.nat_int = 0;
		argv[0].val.argv0.func = hk;
		hk->func(&ares, argc, argv);
		fgw_arg_free(&view->fgw_ctx, &ares);
	}

	fgw_argv_free(&view->fgw_ctx, argc, argv);
	return 0;
}
