/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - sch-rnd (executable)
 *  Copyright (C) 2019,2022,2023 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., 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 <libcschem/config.h>

#include <ctype.h>

#include <librnd/core/conf.h>
#include <librnd/core/conf_hid.h>
#include <librnd/core/list_conf.h>
#include <librnd/core/safe_fs.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/compat_lrealpath.h>
#include <librnd/hid/hid.h>
#include <liblihata/dom.h>
#include <liblihata/tree.h>

#include <libcschem/concrete.h>
#include <libcschem/project.h>
#include <libcschem/util_parse.h>
#include <libcschem/event.h>

#include "multi.h"

#include "conf_core.h"

static char prj_cookie[] = "sch-rnd/project.c";

static char *hash_get_strip(lht_node_t *parent, const char *name, gds_t *tmp)
{
	lht_node_t *n = lht_dom_hash_get(parent, name);
	const char *s;
	long i;

	if ((n == NULL) || (n->type != LHT_TEXT))
		return NULL;

	tmp->used = 0;
	s = n->data.text.value;
	while(isspace(*s)) s++;
	gds_append_str(tmp, s);
	for(i = tmp->used-1; i >= 0; i--) {
		if (!isspace(tmp->array[i]))
			break;
		tmp->array[i] = '\0';
		tmp->used--;
	}
	return tmp->array;
}

static void prj_append_view_cfg(csch_project_t *prj, rnd_conf_listitem_t *i)
{
	lht_node_t *nd = i->prop.src;
	int changed = 0;

	if ((nd != NULL) && (nd->type == LHT_HASH)) {
		gds_t tmp1 = {0}, tmp2 = {0};
		lht_node_t *e, *eng = lht_dom_hash_get(nd, "engines");
		if ((eng != NULL) && (eng->type == LHT_LIST)) {
			csch_view_t *view = csch_view_create(prj, nd->name);
			for(e = eng->data.list.first; e != NULL; e = e->next) {
				if (e->type == LHT_HASH) {
					char *plugin = hash_get_strip(e, "plugin", &tmp1);
					char *options = hash_get_strip(e, "options", &tmp2);
					if (plugin != NULL) {
						csch_view_eng_append(view, e->name, plugin, options);
						changed = 1;
					}
					else
						rnd_message(RND_MSG_ERROR, "View conf error: missing plugin name for engine %s/%s in %s:%ld\n", nd->name, e->name, e->file_name, e->line);
				}
				else
					rnd_message(RND_MSG_ERROR, "View conf error: plugin not a hash in engine %s/%s in %s:%ld\n", nd->name, e->name, e->file_name, e->line);
			}
		}
		else
			rnd_message(RND_MSG_ERROR, "View conf error: missing engines list in view %s in %s:%ld\n", nd->name, nd->file_name, nd->line);
		gds_uninit(&tmp1);
		gds_uninit(&tmp2);
	}

	if (changed)
		csch_views_changed(prj);
}

void sch_rnd_prj_conf2prj(csch_project_t *prj)
{
	rnd_conflist_t *lst = (rnd_conflist_t *)&conf_core.compile.views;
	rnd_conf_listitem_t *ci;

	csch_view_remove_all(prj);

	/* fill in view list from current conf */
	for(ci = rnd_conflist_first(lst); ci != NULL; ci = rnd_conflist_next(ci))
		prj_append_view_cfg(prj, ci);

	prj->num_root_sheets = rnd_conflist_length(&conf_core.prj.root_sheets);
	prj->num_aux_sheets = rnd_conflist_length(&conf_core.prj.aux_sheets);
}

void sch_rnd_prj_postproc(csch_project_t *prj)
{
	sch_rnd_prj_conf2prj(prj);
	prj->curr = -1;
	csch_view_activate(prj, 0);
}

void sch_rnd_prj_regen(csch_project_t *prj)
{
	csch_project_clean_views(prj);
	sch_rnd_prj_conf2prj(prj);
}

static int sch_rnd_project_create_file_(rnd_design_t *dsg, const char *pname)
{
	FILE *f;
	
	f = rnd_fopen(dsg, pname, "w");
	if (f == NULL) {
		rnd_message(RND_MSG_ERROR, "Failed to create project file:\n%s\n", pname);
		return -1;
	}
	fclose(f);
	return 0;
}

int sch_rnd_project_view_lib2lht(csch_view_t *view)
{
	lht_node_t *nr, *nv, *nviews = rnd_conf_lht_get_at(RND_CFR_PROJECT, "compile/views", 1);

	for(nv = nviews->data.list.first; nv != NULL; nv = nv->next) {
		if ((nv->type == LHT_HASH) && (strcmp(nv->name, view->fgw_ctx.name) == 0)) {
			/* replace existing */
			nr = csch_view2lht_in(nv, view);
			return (nr == NULL) ? -1 : 0;
		}
	}

	/* view not found, create new and append */
	nv = csch_view2lht(view);
	if (nv == NULL)
		return -1;

	lht_dom_list_append(nviews, nv);
	return 0;
}

static int project_create_postproc(csch_project_t *prj, const char *apname, int load, int copy_views)
{
	if (load)
		rnd_conf_load_as(RND_CFR_PROJECT, apname, 0);

	rnd_conf_lht_get_first(RND_CFR_PROJECT, 1);

	if (copy_views) { /* copy all existing views into the new project file */
		long n;
		lht_node_t *nnewv, *nviews = rnd_conf_lht_get_at(RND_CFR_PROJECT, "compile/views", 1);
		for(n = 0; n < prj->views.used; n++) {
			csch_view_t *view = prj->views.array[n];
			nnewv = csch_view2lht(view);
			if (nnewv != NULL)
				lht_dom_list_append(nviews, nnewv);
		}
	}
	return 0;
}

extern lht_doc_t *rnd_conf_main_root[RND_CFR_max_alloc];

int sch_rnd_project_create_file_for_sheet_gui(csch_sheet_t *sheet)
{
	gds_t tmp = {0};
	const char *pname;
	char *apname;
	int go_for_it;
	csch_project_t *prj = (csch_project_t *)sheet->hidlib.project;

	rnd_conf_get_project_conf_name(NULL, sheet->hidlib.loadname, &pname);
	if (*pname == '<') {
		rnd_message(RND_MSG_ERROR, "Failed to generate a project file name: %s;\nPlease save the sheet first!\n", pname);
		return -1;
	}

	/* do not create it if it's already there */
	if (rnd_file_readable(&sheet->hidlib, pname))
		return 0;

	gds_append_str(&tmp, "Do you want to create a project file ");
	gds_append_str(&tmp, pname);
	gds_append(&tmp, '?');

	go_for_it = rnd_hid_message_box(&sheet->hidlib, "question", "Creating project file", tmp.array, "yes", 1, "no/cancel", 0, NULL);
	if (!go_for_it)
		return -1;

	apname = rnd_strdup(pname);
	if (rnd_conf_lht_get_first(RND_CFR_PROJECT, 0) == NULL)
		lht_dom_loc_newfile(rnd_conf_main_root[RND_CFR_PROJECT], apname);

	/* create a new project file */
	if (sch_rnd_project_create_file_(&sheet->hidlib, pname) != 0) {
		free(apname);
		return -1;
	}

	if (project_create_postproc(prj, apname, 0, 1) != 0) {
		free(apname);
		return -1;
	}

	rnd_conf_makedirty(RND_CFR_PROJECT); /* to force save */
	if (rnd_conf_save_file(&sheet->hidlib, apname, sheet->hidlib.fullpath, RND_CFR_PROJECT, NULL) != 0) {
		rnd_message(RND_MSG_ERROR, "Failed to create project file (#2):\n%s\n", pname);
		return -1;
	}

	free(apname);
	prj->hdr.fullpath = rnd_strdup(pname);
	prj->hdr.loadname = rnd_strdup(pname);
	prj->dummy = 0;

	return 0;
}

int sch_rnd_project_create_file(rnd_design_t *dsg, const char *pname, csch_project_t **prj_out)
{
	csch_sheet_t *orig = NULL, *first = NULL;
	csch_project_t *prj;
	char *realp;
	long n;

	if (prj_out != NULL)
		*prj_out = NULL;

	if (sch_rnd_project_create_file_(dsg, pname) != 0)
		return -1;

	realp = rnd_lrealpath(pname);
	if (realp == NULL)
		return -1;

	prj = htsp_get(&rnd_projects, realp);
	if (prj == NULL) {
		prj = sch_rnd_multi_load_project(NULL, realp);
		free(prj->hdr.fullpath);
		prj->hdr.fullpath = realp; /* do not free realp */
		if (prj_out != NULL)
			*prj_out = prj;
		return (prj == NULL) ? -1 : 0;
	}


	if (prj->hdr.designs.used == 0)
		sch_rnd_multi_new_empty(realp);

	free(prj->hdr.fullpath);
	prj->hdr.fullpath = realp;

	for(n = 0; n < prj->hdr.designs.used; n++) {
		csch_sheet_t *sh = prj->hdr.designs.array[n];
		csch_sheet_t *old = sch_rnd_multi_switch_to(sh);
		project_create_postproc(prj, realp, 1, (orig == NULL));
		if (orig == NULL) {
			orig = old;
			first = sh;
		}
	}

	if (orig != NULL) {
		/* get sheetsel updated */
		rnd_event(&first->hidlib, RND_EVENT_DESIGN_FN_CHANGED, NULL);

		sch_rnd_multi_switch_to(orig);
	}

	if (prj_out != NULL)
		*prj_out = prj;

	/* do not free realp, it's owhed by prj now */
	return 0;
}



int sch_rnd_project_append_view(csch_sheet_t *sheet, const char *view_name, int silent)
{
	lht_node_t *nnewv, *nengs, *nviews = rnd_conf_lht_get_at(RND_CFR_PROJECT, "compile/views", 1);
	csch_project_t *prj = (csch_project_t *)sheet->hidlib.project;

	if (nviews == NULL) {
		if (!silent)
			rnd_message(RND_MSG_ERROR, "Failed to find or create the compile/views in the project config tree\n");
		return -1;
	}
	nnewv = lht_dom_node_alloc(LHT_HASH, view_name);
	lht_dom_list_append(nviews, nnewv);
	nengs = lht_dom_node_alloc(LHT_LIST, "engines");
	lht_dom_hash_put(nnewv, nengs);
	rnd_conf_makedirty(RND_CFR_PROJECT);
	rnd_conf_merge_all("compile/views");
	sch_rnd_prj_regen(prj);
	csch_views_changed(prj);
	return 0;
}

int sch_rnd_project_del_view(csch_sheet_t *sheet, long idx, int silent)
{
	lht_node_t *nviews = rnd_conf_lht_get_at(RND_CFR_PROJECT, "compile/views", 0);
	csch_project_t *prj = (csch_project_t *)sheet->hidlib.project;

	if (nviews == NULL) {
		if (!silent)
			rnd_message(RND_MSG_ERROR, "Failed to find or create the compile/views in the project config tree\n");
		return -1;
	}

	if (lht_tree_list_del_nth(nviews, idx) != 0)
		return -1;

	rnd_conf_makedirty(RND_CFR_PROJECT); /* to force save */
	rnd_conf_merge_all("compile/views");
	sch_rnd_prj_regen(prj);
	return 0;
}

void sch_rnd_project_views_save(csch_sheet_t *sheet)
{
	rnd_conf_merge_all("compile/views");
	rnd_conf_save_file(&sheet->hidlib, sheet->hidlib.project->fullpath, NULL, RND_CFR_PROJECT, NULL);
}

static rnd_conf_hid_callbacks_t cbs;
static rnd_conf_hid_id_t cfgid;
static rnd_conf_native_t *nat_views = NULL;

#define PATH_VIEWS "compile/views"

static void prj_conf_hlist(rnd_conf_native_t *cfg, rnd_conf_listitem_t *i, void *user_data)
{
	csch_sheet_t *sheet = (csch_sheet_t *)rnd_multi_get_current();
	csch_project_t *prj;

	if (nat_views == NULL) {
		if (strncmp(cfg->hash_path, PATH_VIEWS, strlen(PATH_VIEWS)) == 0) {
			nat_views = cfg;
			nat_views->shared->gui_edit_act = "EditConfCompileViews";
		}
	}

	if ((cfg != nat_views) || (sheet == NULL))
		return;

	prj = (csch_project_t *)sheet->hidlib.project;
	if (cfg->rnd_conf_rev > prj->view_conf_rev) {
		prj->view_conf_rev = cfg->rnd_conf_rev;
		csch_project_clean_views(prj);
	}

	prj_append_view_cfg(prj, i);
}

void sch_rnd_prj_post_remove_sheet(rnd_project_t *prj)
{
	const char *ldn;
	rnd_project_t *old;

	if (prj->designs.used != 0)
		return;

	ldn = (prj->loadname == NULL) ? "<none>" : prj->loadname;
	rnd_message(RND_MSG_INFO, "Unloading project '%s' as no sheet loaded for it anymore\n", ldn);

	ldn = (prj->fullpath == NULL) ? "<none>" : prj->fullpath;
	old = htsp_pop(&rnd_projects, ldn);
	if (old != NULL) {
		rnd_project_uninit(old);
		free(old);
	}
}

int sch_rnd_prj_remove_sheet(csch_sheet_t *sheet)
{
	rnd_project_t *prj = sheet->hidlib.project;
	int res = rnd_project_remove_design(prj, &sheet->hidlib);
	sch_rnd_prj_post_remove_sheet(prj);
	return res;
}

void sch_rnd_project_init(void)
{
	cbs.new_hlist_item_post = prj_conf_hlist;
	cfgid = rnd_conf_hid_reg(prj_cookie, &cbs);
}

void sch_rnd_project_uninit(void)
{
	rnd_conf_hid_unreg(prj_cookie);
}
