/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2018,2023 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 <genvector/gds_char.h>
#include "project.h"
#include "libcschem.h"
#include <librnd/core/compat_lrealpath.h>
#include <librnd/core/event.h>
#include <librnd/core/compat_fs.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/conf.h>
#include "plug_io.h"
#include "event.h"
#include "project_p4.h"

static const char project_cookie[] = "libcschem/project";

csch_project_t *csch_project_alloc(void)
{
	csch_project_t *prj;

	prj = calloc(sizeof(csch_project_t), 1);
	/* no need to init fields because of calloc() */

	rnd_conf_load_as(RND_CFR_PROJECT, NULL, 0);

	prj->dummy = 1; /* until it's loaded or saved */
	prj->view_conf_rev = -1;
	csch_p4_call_project_init(prj);
	return prj;
}

void csch_project_free(csch_project_t *prj)
{
	int n;

	if (prj->abst != NULL) {
		csch_abstract_uninit(prj->abst);
		free(prj->abst);
	}

	for(n = 0; n < vtp0_len(&prj->hdr.designs); n++)
		csch_sheet_free(prj->hdr.designs.array[n]);
	prj->hdr.designs.used = 0; /* rnd_project_uninit() will iterate */

	csch_p4_call_project_uninit(prj);

	for(n = 0; n < vtp0_len(&prj->views); n++)
		csch_view_free(prj->views.array[n]);
	vtp0_uninit(&prj->views);

	rnd_project_uninit(&prj->hdr);
	free(prj);
}

csch_view_t *csch_view_create(csch_project_t *prj, const char *name)
{
	csch_view_t *view = calloc(sizeof(csch_view_t), 1);

	fgw_init(&view->fgw_ctx, name);
	view->parent = prj;

	vtp0_append(&prj->views, view);
	return view;
}

void csch_view_free(csch_view_t *view)
{
	long n;

	for(n = 0; n < vtp0_len(&view->engines); n++)
		csch_eng_free(view, view->engines.array[n]);
	vtp0_uninit(&view->engines);

	fgw_uninit(&view->fgw_ctx);
	free(view);
}

void csch_view_remove_all(csch_project_t *prj)
{
	long n;
	for(n = 0; n < prj->views.used; n++)
		csch_view_free(prj->views.array[n]);
	prj->views.used = 0;
}

int csch_view_get_id(csch_project_t *prj, const char *name)
{
	int n;
	for(n = 0; n < prj->views.used; n++) {
		csch_view_t *v = prj->views.array[n];
		if ((v != NULL) && (strcmp(v->fgw_ctx.name, name) == 0))
			return n;
	}
	return -1;
}

csch_view_t *csch_view_get(csch_project_t *prj, const char *name)
{
	int view_id = csch_view_get_id(prj, name);
	if (view_id < 0)
		return NULL;
	return prj->views.array[view_id];
}


void csch_view_renum(csch_view_t *view)
{
	int n, prio;
	for(n = 0, prio = (view->engines.used - 1) * 20; n < view->engines.used; n++,prio-=20) {
		csch_view_eng_t *eng = view->engines.array[n];
		eng->eprio = prio;
	}
}

int csch_view_eng_append(csch_view_t *view, const char *user_name, const char *eng_name, const char *file_name)
{
	csch_view_eng_t *eng;

	if (view->engines.used >= CSCH_PRIMAX_PLUGIN/2) return -1;

	eng = csch_eng_alloc(view, user_name, eng_name, file_name);
	if (eng == NULL) return -1;

	vtp0_append(&view->engines, eng);
	csch_view_renum(view);
	return 0;
}


int csch_view_eng_insert_before(csch_view_t *view, const char *user_name, const char *eng_name, const char *file_name, int idx)
{
	csch_view_eng_t *eng;

	if (view->engines.used >= CSCH_PRIMAX_PLUGIN) return -1;
	if ((idx < 0) || (idx > view->engines.used)) return -1;

	eng = csch_eng_alloc(view, user_name, eng_name, file_name);
	if (eng == NULL) return -1;

	vtp0_insert_len(&view->engines, idx, (void *)eng, 1);
	csch_view_renum(view);
	return 0;
}


int csch_proj_sheet_update_filename(csch_project_t *prj, csch_sheet_t *sheet)
{
	gds_t tmp;
	char *realfn;

	if (rnd_is_path_abs(sheet->hidlib.loadname)) {
		free(sheet->hidlib.fullpath);
		sheet->hidlib.fullpath = rnd_strdup(sheet->hidlib.loadname);
		rnd_event(&sheet->hidlib, RND_EVENT_DESIGN_FN_CHANGED, NULL);
		return 0;
	}

	if (prj->hdr.fullpath == NULL)
		return -1;
	
	gds_init(&tmp);
	gds_append_str(&tmp, prj->hdr.prjdir);
	gds_append(&tmp, '/');
	gds_append_str(&tmp, sheet->hidlib.loadname);
	realfn = rnd_lrealpath(tmp.array);
	gds_uninit(&tmp);
	if (realfn == NULL)
		return -1;

	free(sheet->hidlib.fullpath);
	sheet->hidlib.fullpath = realfn;
	rnd_event(&sheet->hidlib, RND_EVENT_DESIGN_FN_CHANGED, NULL);
	return 0;
}

int csch_proj_update_filename(csch_project_t *prj)
{
	return rnd_project_update_filename(&prj->hdr);
}

int csch_project_load_sheet(csch_project_t *prj, const char *fn, const char *fmt, csch_sheet_t **sheet_out, rnd_bool quiet, FILE *optf)
{
	int ain;
	csch_sheet_t *sheet = csch_load_sheet(prj, fn, fmt, &ain, optf);

	if (sheet_out != NULL)
		*sheet_out = sheet;

	if (sheet == NULL) {
		if (!quiet) {
			if (fmt != NULL)
				rnd_message(RND_MSG_ERROR, "Failed to load %s (with requested format %s)\n", fn, fmt);
			else
				rnd_message(RND_MSG_ERROR, "Failed to load %s\n", fn);
		}
		return -1;
	}

	if (!ain)
		return rnd_project_append_design(&prj->hdr, &sheet->hidlib);

	return 0;
}

int csch_project_remove_sheet(csch_project_t *prj, csch_sheet_t *sheet)
{
	return rnd_project_remove_design(&prj->hdr, &sheet->hidlib);
}

void csch_project_flush(void)
{
	csch_sheet_t *sheet = (csch_sheet_t *)rnd_multi_get_current();

	rnd_conf_makedirty(RND_CFR_PROJECT);
	rnd_conf_save_file(&sheet->hidlib, NULL,  sheet->hidlib.loadname, RND_CFR_PROJECT, NULL);
}

csch_project_t *csch_load_project_by_sheet_name(const char *sheet_fn, int with_sheets, rnd_bool quiet)
{
	const char *try;
	const char *project_fn = rnd_conf_get_project_conf_name(NULL, sheet_fn, &try);
	csch_project_t *prj = csch_load_project(project_fn, "lht", with_sheets);

	if (prj == NULL) {
		if (!quiet) {
			if (project_fn == NULL)
				rnd_message(RND_MSG_INFO, "Failed to load project file for %s; assuming implicit project\n", sheet_fn);
			else
				rnd_message(RND_MSG_WARNING, "Failed to load project file %s; creating a dummy project in memory and assuming implicit project\n", project_fn);
		}
		prj = csch_project_alloc();
	}
	else
		prj->dummy = 0;

	return prj;
}

void csch_project_clean_views(csch_project_t *prj)
{
	long n;
	for(n = 0; n < prj->views.used; n++)
		csch_view_free(prj->views.array[n]);
	prj->views.used = 0;
}

void csch_views_changed(csch_project_t *prj)
{
	long n;

	for(n = 0; n < prj->hdr.designs.used; n++) {
		if (prj->hdr.designs.array[n] != NULL) {
			csch_sheet_t *sheet = prj->hdr.designs.array[n];
			rnd_event(&sheet->hidlib, CSCH_EVENT_PRJ_VIEWS_CHANGED, NULL);
			return;
		}
	}
}

int csch_view_activate(csch_project_t *prj, int view_id)
{
	long n;

	if ((view_id < 0) || (view_id >= prj->views.used))
		return -1;

	if (prj->curr == view_id)
		return 0;

	prj->curr = view_id;

	/* call with the first sheet */
	for(n = 0; n < prj->hdr.designs.used; n++) {
		if (prj->hdr.designs.array[n] != NULL) {
			csch_sheet_t *sheet = prj->hdr.designs.array[n];
			rnd_event(&sheet->hidlib, CSCH_EVENT_PRJ_VIEW_ACTIVATED, NULL);
			return 0;
		}
	}

	return 0;
}


static void csch_project_conf_saved(rnd_design_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[])
{
	int role = argv[2].d.i;

	if (role == RND_CFR_PROJECT) {
		csch_sheet_t *sheet = (csch_sheet_t *)hidlib;
		csch_project_t *prj = (csch_project_t *)sheet->hidlib.project;

		prj->dummy = 0;
	}
}

const char *csch_view_get_prop(csch_project_t *prj, int viewid, const char *name)
{
	csch_view_t *view;

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

	if ((viewid < 0) || (viewid >= prj->views.used))
		return NULL;

	view = prj->views.array[viewid];
	if (view == NULL)
		return NULL;

	if (strcmp(name, "name") == 0) return view->fgw_ctx.name;

	return NULL;

}

static gds_t stance_path = {0};
const char *csch_stance_get(const char *name)
{
	int save;
	rnd_conf_native_t *nat;

	if (stance_path.used == 0) gds_append_str(&stance_path, "stance/");

	save = stance_path.used;
	gds_append_str(&stance_path, name);
	nat = rnd_conf_get_field(stance_path.array);
	stance_path.used = save;

	if ((nat == NULL) || (nat->type != RND_CFN_STRING))
		return NULL;

	return (nat->val.string[0] == NULL) ? "" : nat->val.string[0];
}

extern lht_doc_t *rnd_conf_main_root[RND_CFR_max_alloc];
static rnd_conf_role_t get_role(lht_node_t *nd)
{
	rnd_conf_role_t n;

	if (nd == NULL)
		return RND_CFR_invalid;

	for(n = 0; n < RND_CFR_max_alloc; n++)
		if (nd->doc == rnd_conf_main_root[n])
			return n;
	return RND_CFR_invalid;
}

static void stance_flush(rnd_conf_role_t role)
{
	if (role == RND_CFR_PROJECT)
		csch_project_flush();
}

int csch_stance_add_to_values(const char *name, const char *val)
{
	rnd_conf_native_t *nat, *nat_scalar;
	rnd_conf_role_t role;
	int save, idx;
	rnd_conf_listitem_t *item_li;
	const char *item_str;
	lht_node_t *nd, *src;

	if (stance_path.used == 0) gds_append_str(&stance_path, "stance/");
	save = stance_path.used;
	gds_append_str(&stance_path, name);
	nat_scalar = rnd_conf_get_field(stance_path.array);
	gds_append_str(&stance_path, "_values");
	nat = rnd_conf_get_field(stance_path.array);
	stance_path.used = save;

	if ((nat == NULL) || (nat_scalar == NULL) || (nat->type != RND_CFN_LIST))
		return -1;

	role = get_role(nat_scalar->prop[0].src);
	if (role == RND_CFR_invalid)
		role = RND_CFR_PROJECT;


	/* check if value already exists */
	rnd_conf_loop_list_str(nat->val.list, item_li, item_str, idx) {
		if (strcmp(val, item_str) == 0)
			return 0;
	}

	src = rnd_conf_lht_get_at(role, nat->hash_path, 1);
	nd = lht_dom_node_alloc(LHT_TEXT, NULL);
	lht_dom_list_append(src, nd);
	nd->data.text.value = rnd_strdup(val);
	rnd_conf_update(nat->hash_path, -1);

	stance_flush(role);

	return -1;
}


int csch_stance_set(const char *name, const char *val)
{
	int save, res;
	rnd_conf_native_t *nat;
	rnd_conf_role_t role;

	if (stance_path.used == 0) gds_append_str(&stance_path, "stance/");
	save = stance_path.used;
	gds_append_str(&stance_path, name);
	nat = rnd_conf_get_field(stance_path.array);
	stance_path.used = save;

	if ((nat == NULL) || (nat->type != RND_CFN_STRING))
		return -1;

	role = get_role(nat->prop[0].src);
	if (role == RND_CFR_invalid)
		role = RND_CFR_PROJECT;

	res = rnd_conf_set(role, nat->hash_path, -1, val, RND_POL_OVERWRITE);

	stance_flush(role);

	return res;
}


void csch_project_init(void)
{
	rnd_event_bind(RND_EVENT_CONF_FILE_SAVE_POST, csch_project_conf_saved, NULL, project_cookie);
}

void csch_project_uninit(void)
{
	rnd_event_unbind_allcookie(project_cookie);
	gds_uninit(&stance_path);
}

