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

#include <libcschem/config.h>

#include <librnd/core/conf.h>
#include <librnd/core/conf_multi.h>
#include <librnd/core/compat_fs.h>
#include <librnd/hid/hid.h>
#include <librnd/core/event.h>
#include <librnd/core/rnd_conf.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/compat_lrealpath.h>
#include <librnd/core/safe_fs.h>
#include <librnd/hid/tool.h>

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

#include <libcschem/plug_io.h>

#include "conf_core.h"
#include "sheet.h"
#include "project.h"

#include "multi.h"

csch_sheet_t *sch_rnd_multi_switch_to(csch_sheet_t *s)
{
	return (csch_sheet_t *)rnd_multi_switch_to(s == NULL ? NULL : &s->hidlib);
}

void sch_rnd_multi_switch_to_initial(csch_sheet_t *s)
{
	rnd_multi_switch_to_(&s->hidlib);
}

void sch_rnd_multi_switch_to_delta(csch_sheet_t *curr, int step)
{
	rnd_multi_switch_to_delta(curr == NULL ? NULL : &curr->hidlib, step);
}

static csch_project_t *sch_rnd_multi_load_project_(const char *project_fn)
{
	csch_project_t *prj = htsp_get(&rnd_projects, project_fn);
	if (prj == NULL) {
		char *pfn = rnd_strdup(project_fn);
		prj = csch_load_project_by_sheet_name(pfn, 0, rnd_conf.rc.quiet);
		if (prj->hdr.loadname != NULL) {
			htsp_set(&rnd_projects, prj->hdr.loadname, prj);
			free(pfn);
		}
		else {
			TODO("memleak: this leaks pfn; happens when loading a sheet without a project file");
			htsp_set(&rnd_projects, pfn, prj);
		}
	}
	return prj;
}


csch_project_t *sch_rnd_multi_load_project(const char *load_fn, const char *project_fn_in)
{
	char *project_fn, *project_dir;
	char *freeme1, *freeme2;
	csch_project_t *prj;

	if (project_fn_in != NULL) {
		char *realp = rnd_lrealpath(project_fn_in);
		if (realp == NULL)
			return NULL;
		prj = sch_rnd_multi_load_project_(realp);
		free(realp);
		return prj;
	}

	/* there may be no existing project file - assume project is the directory */
	project_dir = freeme1 = rnd_dirname_real(load_fn);
	project_fn = freeme2 = rnd_concat(project_dir, "/project.lht", NULL);

	prj = sch_rnd_multi_load_project_(project_fn);

	free(freeme1);
	free(freeme2);
	return prj;
}

static csch_sheet_t *sch_rnd_multi_load_(const char *fn, const char *fmt, const char *project_fn, int *is_project, rnd_conflist_t *root_sheets_out, rnd_conflist_t *aux_sheets_out);

/* Append all unique file names to fns (keeps order) */
static void map_all_sheets(const rnd_conflist_t *list, vts0_t *fns)
{
	rnd_conf_listitem_t *item;
	const char *shfn;
	int idx, n;

	rnd_conf_loop_list_str(list, item, shfn, idx) {
		int found = 0;

		for(n = 0; n < fns->used; n++) {
			if (strcmp(fns->array[n], shfn) == 0) {
				rnd_message(RND_MSG_ERROR, "Redundant file name in project: %s\n", shfn);
				found = 1;
				break;
			}
		}

		if (!found)
			vts0_append(fns, rnd_strdup(shfn));
	}
}

/* Load a project file from prj_fn and all sheets; return first sheet */
static csch_sheet_t *sch_rnd_multi_load_project_sheets(const char *prj_fn)
{
	csch_project_t *prj;
	csch_sheet_t *res = NULL;
	vts0_t fns = {0};
	long n, aux_start;
	char *prj_fn_real;

	/* need to load by real name for comparisons/lookups/hash */
	prj_fn_real = rnd_lrealpath(prj_fn);
	if (prj_fn_real == NULL)
		return NULL;

	prj = sch_rnd_multi_load_project_(prj_fn_real);
	free(prj_fn_real);

	if (prj == NULL)
		return NULL;

	rnd_conf_load_project(prj_fn, NULL);
	rnd_conf_merge_all("prj/root_sheets");
	rnd_conf_merge_all("prj/aux_sheets");


	map_all_sheets(&conf_core.prj.root_sheets, &fns);
	aux_start = fns.used;
	map_all_sheets(&conf_core.prj.aux_sheets, &fns);

	/* load usign raw "loadname" picked up from the project file because it
	   may be a symlink or relative to the project file */
	if (fns.used > 0) {
		for(n = 0; n < fns.used; n++) {
			csch_sheet_t *sheet = sch_rnd_multi_load_(fns.array[n], NULL, prj_fn, NULL, NULL, NULL);
			if (sheet != NULL) {
				if (res == NULL)
					res = sheet;
				sheet->stype = (n >= aux_start) ? CSCH_SHTY_AUX : CSCH_SHTY_ROOT;
			}
			free(fns.array[n]);
		}
	}
	else
		rnd_message(RND_MSG_ERROR, "Can not load project file '%s': does not name any sheet\n", prj_fn);

	vts0_uninit(&fns);

	return res;
}

int sch_rnd_is_fn_project_file(const char *fn)
{
	const char *end = fn + strlen(fn) - 11;
	return strcmp(end, "project.lht") == 0;
}

/* sch-rnd, app-specific tasks on any newly loaded sheet */
static void sch_rnd_multi_load_post(csch_sheet_t *sheet)
{
	rnd_tool_select_by_name(&sheet->hidlib, "arrow");

	/* this loads and initializes sheet engines; this must come before
	   events because events may rely on engines already initialized
	   (e.g. devmap) */
	sch_rnd_prj_postproc((csch_project_t *)sheet->hidlib.project);

	rnd_event(&sheet->hidlib, CSCH_EVENT_SHEET_POSTLOAD, NULL);
	rnd_event(&sheet->hidlib, RND_EVENT_LOAD_POST, NULL);
}

static csch_sheet_t *sch_rnd_multi_load_(const char *fn, const char *fmt, const char *project_fn, int *is_project, rnd_conflist_t *root_sheets_out, rnd_conflist_t *aux_sheets_out)
{
	csch_sheet_t *sheet;
	csch_project_t *prj;
	const char *real_fn = fn;
	char *freeme = NULL;
	int lres;
	rnd_conf_state_t *ncs;
	FILE *f = NULL;
	void *cookie, *io;

	if ((project_fn == NULL) && ((fmt == NULL) || (*fmt == '\0'))) {
		if (sch_rnd_is_fn_project_file(fn)) {
			if (is_project != NULL)
				*is_project = 1;
			return sch_rnd_multi_load_project_sheets(fn);
		}
	}

	/* try load as bundled */
	{
		prj = htsp_get(&rnd_projects, (char *)fn);
		if (prj != NULL) { /* already loaded */
			if (prj->hdr.designs.used > 0)
				return prj->hdr.designs.array[0];
			return NULL;
		}

		cookie = csch_test_parse_bundled(NULL, real_fn, CSCH_IOTYP_SHEET, &f, &io);
		if (cookie != NULL) {
			if (f != NULL) {
				sheet = csch_load_bundled_sheets(cookie, io, f, fn, sch_rnd_multi_load_post);
				fclose(f);
				if (root_sheets_out != NULL)
					memcpy(root_sheets_out, &conf_core.prj.root_sheets, sizeof(rnd_conflist_t));

				if (sheet != NULL) {
					prj = (csch_project_t *)sheet->hidlib.project;
					htsp_set(&rnd_projects, prj->hdr.loadname, prj);
				}
				return sheet;
			}
		}
	}

	/* not bundled; single sheet loader with all the extra rounds for project files */

	if (is_project != NULL)
		*is_project = 0;

	rnd_conf_multi_pre_new_design(&ncs);
	rnd_conf_reset(RND_CFR_DESIGN, fn);

	prj = sch_rnd_multi_load_project(fn, project_fn);

	rnd_conf_merge_all(NULL); /* get font settings right for the text update in the loader */

	/* If sheet name is not absolute and there is a project file being loaded,
	   sheet name needs to be translated relative to the project file's dir */
	if ((project_fn != NULL) && !rnd_is_path_abs(fn)) {
		const char *s, *sep = NULL;

		/* check if project file is not in ./; sep is the last path separator */
		for(s = project_fn; *s != '\0'; s++) {
			if ((*s == '/') || (*s == RND_DIR_SEPARATOR_C))
				sep = s;
		}

		/* project file is not in ./, change real_fn */
		if (sep != NULL) {
			gds_t tmp = {0};
			gds_append_len(&tmp, project_fn, sep - project_fn + 1); /* +1 to keep the sep in project file's path */
			gds_append_str(&tmp, real_fn);
			real_fn = freeme = tmp.array;
		}
	}

	lres = csch_project_load_sheet(prj, real_fn, fmt, &sheet, rnd_conf.rc.quiet, f);
	if (f != NULL)
		fclose(f);
	rnd_conf_multi_post_new_design(&ncs, &sheet->hidlib);
	if (lres != 0) {
		free(freeme);
		return NULL;
	}

	rnd_multi_load_prj_for_dsg(&sheet->hidlib);

	rnd_conf_state_new_design(&sheet->hidlib);
	rnd_conf_merge_all(NULL); /* to get the project file applied */

	if (root_sheets_out != NULL)
		memcpy(root_sheets_out, &conf_core.prj.root_sheets, sizeof(rnd_conflist_t));
	if (aux_sheets_out != NULL)
		memcpy(aux_sheets_out, &conf_core.prj.aux_sheets, sizeof(rnd_conflist_t));

	sch_rnd_multi_load_post(sheet);
	rnd_conf_state_save(sheet->hidlib.saved_rnd_conf);

	free(freeme);
	return sheet;
}

csch_sheet_t *sch_rnd_multi_load(const char *fn, const char *fmt, int *is_project)
{
	rnd_conflist_t roots = {0}, aux = {0};
	csch_sheet_t *sheet;
	
	sheet = sch_rnd_multi_load_(fn, fmt, NULL, is_project, &roots, &aux);
	if ((sheet != NULL) && (sheet->stype == CSCH_SHTY_unknown)) {
/*		rnd_conf_merge_all("prj/root_sheets");
		rnd_conf_merge_all("prj/aux_sheets");*/
		csch_sheet_type_detect(sheet, &roots, &aux);
	}

	/* Do not free roots or aux, they are owned by the conf system */

	return sheet;
}

csch_sheet_t *sch_rnd_app_load_sheet(csch_project_t *proj, const char *path)
{
	int is_project;
	csch_sheet_t *res;
	rnd_design_t *last;

	last = rnd_multi_switch_to(NULL); /* empty conf so we can overwrite */
	res = sch_rnd_multi_load_(path, NULL, proj->hdr.loadname, &is_project, NULL, NULL);
	res->stype = CSCH_SHTY_EXTERNAL;
	rnd_multi_switch_to_(last); /* activate the original */

	return res;
}


static csch_sheet_t *sch_rnd_multi_new_empty_(csch_project_t *prj, const char *fn)
{
	csch_sheet_t *sheet;
	static csch_project_t *prj_none = NULL;
	rnd_conf_state_t *ncs;

	rnd_conf_multi_pre_new_design(&ncs);
	rnd_conf_reset(RND_CFR_DESIGN, NULL);

	if ((prj == NULL) && (fn != NULL))
		prj = sch_rnd_multi_load_project(fn, NULL);

	if (prj == NULL) {
		if (prj_none == NULL) {
			prj_none = csch_project_alloc();
			htsp_set(&rnd_projects, rnd_strdup("<none>"), prj_none);
		}
		prj = prj_none;
	}

	sheet = sch_rnd_sheet_new(prj);

	/* set filename before postprocessing the sheet so that libs dependeing
	   on $(rc.path.design) already have it filled in */
	if (fn == NULL)
		fn = "<none>";

	if (!rnd_is_dir(NULL, fn)) {
		/* If we failed to load a file with a given file name and created an
		   empty sheet instead, remember the file name for the new sheet so
		   {f s} saves using that name specified originally */
		if (sheet != NULL) {
			sheet->hidlib.loadname = rnd_strdup(fn);
			sheet->hidlib.fullpath = rnd_strdup(fn);
		}
	}
	else
		rnd_message(RND_MSG_ERROR, "Supplied sheet name '%s' is a directory, can't save sheet by that name.\n", fn);

	rnd_conf_multi_post_new_design(&ncs,&sheet->hidlib);

	if (sheet != NULL) {
		rnd_multi_load_prj_for_dsg(&sheet->hidlib);
		rnd_conf_state_new_design(&sheet->hidlib);

		rnd_conf_merge_all(NULL); /* to get the project file applied */
		sch_rnd_prj_postproc(prj);
		rnd_tool_select_by_name(&sheet->hidlib, "arrow");
		rnd_event(&sheet->hidlib, CSCH_EVENT_SHEET_POSTLOAD, NULL);
		rnd_event(&sheet->hidlib, RND_EVENT_LOAD_POST, NULL);
	}
	else {
		TODO("multi: memleak");
/*		sch_rnd_prj_free(prj);*/
	}


	rnd_conf_state_save(sheet->hidlib.saved_rnd_conf);

	return sheet;
}

csch_sheet_t *sch_rnd_multi_new_empty(const char *fn)
{
	return sch_rnd_multi_new_empty_(NULL, fn);
}

csch_sheet_t *sch_rnd_multi_new_empty_in_prj(csch_project_t *prj, const char *path)
{
	return sch_rnd_multi_new_empty_(prj, path);
}


void sch_rnd_multi_unload(csch_sheet_t *sheet)
{
	csch_project_t *prj;

	if (sheet == NULL)
		sheet = (csch_sheet_t *)rnd_multi_get_current();

	if (sheet == NULL)
		return;

	prj = (csch_project_t *)sheet->hidlib.project;
	if (prj != NULL)
		sch_rnd_prj_remove_sheet(sheet);

	csch_sheet_uninit(sheet);
	rnd_conf_free(RND_CFR_DESIGN);
	rnd_conf_state_del_design(&sheet->hidlib);
}

const char sch_rnd_multi_cookie[] = "sch-rnd/multi.c";

void sch_rnd_multi_init(void)
{
	rnd_conf_state_plug_reg(&conf_core, sizeof(conf_core), sch_rnd_multi_cookie);
}

void sch_rnd_multi_uninit(void)
{
	rnd_conf_state_plug_unreg_all_cookie(sch_rnd_multi_cookie);
}

void sch_rnd_sheet_free_all(void)
{
	rnd_design_t *curr, *next;
	htsp_entry_t *e;

	/* free all projects (will free all sheets too) */
	while((e = htsp_first(&rnd_projects)) != NULL) {
		csch_project_t *prj = e->value;

		htsp_delentry(&rnd_projects, e);
		csch_project_free(prj);
	}

	/* just in case anything left outside of known projects */
	for(curr = gdl_first(&rnd_designs); curr != NULL; curr = next) {
		next = gdl_next(&rnd_designs, curr);

		csch_sheet_uninit((csch_sheet_t *)curr);
		rnd_conf_state_del_design(curr); /* also removes curr from rnd_designs */
		free(curr);
	}
}
