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

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h> /* Seed for srand() */

/* hidlib headers */
#include <librnd/core/unit.h>
#include <librnd/hid/hid_init.h>
#include <librnd/hid/hid.h>
#include <librnd/hid/tool.h>
#include <librnd/core/conf.h>
#include <librnd/core/hidlib.h>
#include <librnd/hid/buildin.hidlib.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/plugins.h>

#include <librnd/core/actions.h>
#include <librnd/core/plugins.h>
#include <librnd/core/safe_fs.h>
#include <librnd/core/funchash.h>
#include <librnd/core/compat_lrealpath.h>

#include <libcschem/libcschem.h>
#include <libcschem/project.h>
#include <libcschem/abstract.h>
#include <libcschem/compile.h>
#include <libcschem/util_export.h>
#include <libcschem/event.h>
#include <libcschem/csch_printf.h>
#include <libcschem/hierarchy.h>

#include "conf_core.h"
#include "crosshair.h"
#include "build_run.h"
#include "sheet.h"
#include "draw.h"
#include "font.h"
#include "emergency.h"
#include "project.h"
#include "multi.h"
#include "plug_io_act.h"
#include "extobj_act.h"
#include "export.h"

csch_chdr_t *csch_obj_clicked = NULL;

#define pup_buildins sch_rnd_buildins
#include "buildin.c"
#undef pup_buildins

static const char *EXPERIMENTAL = NULL;

static const char *menu_file_paths[8];
static const char *menu_name_fmt = "menu-%s.lht";

#define CONF_USER_DIR "~/" DOT_SCH_RND

static rnd_conf_ignore_t sch_rnd_conf_ignores[] = {
	{"stance/opt", 10, 1, NULL}, /* do not warn: dynamically created by the user */
	{NULL, 0, 0, NULL}
};

static int sch_rnd_human_coord(rnd_coord_t coord, double *out_val, const char **out_suffix)
{
	*out_val = P2C(coord);
	if (*out_val > 100) {
		*out_val = *out_val / 1000.0;
		*out_suffix = "k";
	}
	else
		*out_suffix = "";

	return 1;
}

#ifdef RND_WANT_FLOATING_FHS
	char *sch_floating_confdir;
#endif

static char *main_path_init(char *exec_prefix)
{
	char *tmp;
	int se = 0;

#ifdef RND_WANT_FLOATING_FHS
	TODO("This ignores ./configure --confdir; the code is in librnd trunk/src/librnd/scconfig/hooks_common.h, search for ''set up confdir'' -> should save a variant usable here, without prefix");
	sch_floating_confdir = rnd_concat(rnd_w32_root, "/etc/sch-rnd", NULL);
#endif

	/* export the most important paths and data for child processes (e.g. parametric footprints) */
	tmp = rnd_concat(SCHSHAREDIR, "/footprint", NULL);
	se |= rnd_setenv("SCH_RND_VERSION",     SCH_VERSION,           1);
	se |= rnd_setenv("SCH_RND_REVISION",    SCH_REVISION,          1);
	se |= rnd_setenv("SCH_RND_PCBLIB",      tmp,                   1); /* for backward compatibility - do not use */
	se |= rnd_setenv("SCH_RND_FOOTPRINT",   tmp,                   1);
	se |= rnd_setenv("SCH_RND_SHARE",       SCHSHAREDIR,           1);
	se |= rnd_setenv("SCH_RND_LIB",         SCHLIBDIR,             1);
	se |= rnd_setenv("SCH_RND_EXEC_PREFIX", exec_prefix,           1);
	free(tmp);

	if (se != 0)
		fprintf(stderr, "WARNING: setenv() failed - external commands such as parametric footprints may not have a proper environment\n");

	menu_file_paths[0] = "./";
	menu_file_paths[1] = "~/" DOT_SCH_RND "/";
	menu_file_paths[2] = rnd_concat(SCHCONFDIR, "/", NULL);
	menu_file_paths[3] = NULL;
	assert((sizeof(menu_file_paths)/sizeof(menu_file_paths[0])) > 3);

	rnd_conf_postproc(); /* get ~ (home dir) set */


#ifdef RND_WANT_FLOATING_FHS
	rnd_app.conf_sys_path = rnd_concat(SCHCONFDIR, "/sch-rnd-conf.lht", NULL);
#else
	rnd_app.conf_sys_path = SCHCONFDIR "/sch-rnd-conf.lht";
#endif

	rnd_app.conf_sysdir_path = SCHSHAREDIR;
	rnd_app.lib_dir = SCHLIBDIR;
	rnd_app.conf_userdir_path = CONF_USER_DIR;
	rnd_app.conf_user_path = rnd_concat(CONF_USER_DIR, "/conf_core.lht", NULL);
	rnd_app.conf_ignores = sch_rnd_conf_ignores;
	rnd_app.human_coord = sch_rnd_human_coord;

	return exec_prefix;
}

static void main_path_uninit(void)
{
	/* const for all other parts of the code, but we had to concat (alloc) it above */
	free((char *)menu_file_paths[2]);
	free((char *)rnd_app.conf_user_path);
}


/* initialize signal and error handlers */
static void main_sighand_init(void)
{
#ifdef RND_HAVE_SIGHUP
	signal(SIGHUP, sch_rnd_catch_signal);
#endif
#ifdef RND_HAVE_SIGQUIT
	signal(SIGQUIT, sch_rnd_catch_signal);
#endif
#ifdef RND_HAVE_SIGTERM
	signal(SIGTERM, sch_rnd_catch_signal);
#endif
#ifdef RND_HAVE_SIGINT
	signal(SIGINT, sch_rnd_catch_signal);
#endif

#ifdef NDEBUG
/* so that we get a core dump on segfault in debug mode */
#	ifdef RND_HAVE_SIGABRT
		signal(SIGABRT, sch_rnd_catch_signal);
#	endif
#	ifdef RND_HAVE_SIGSEGV
		signal(SIGSEGV, sch_rnd_catch_signal);
#	endif
#endif

/* calling external program by popen() may cause a PIPE signal, so we ignore it */
#ifdef RND_HAVE_SIGPIPE
	signal(SIGPIPE, SIG_IGN);
#endif
}


static void gui_support_plugins(int load)
{
	static int loaded = 0;
	static pup_plugin_t *puphand;

	if (load && !loaded) {
		static const char *plugin_name = "sch_dialogs";
		int state = 0;
		loaded = 1;
		rnd_message(RND_MSG_DEBUG, "Loading GUI support plugin: '%s'\n", plugin_name);
		puphand = pup_load(&rnd_pup, (const char **)rnd_pup_paths, plugin_name, 0, &state);
		if (puphand == NULL)
			rnd_message(RND_MSG_ERROR, "Error: failed to load GUI support plugin '%s'\n-> expect missing widgets and dialog boxes\n", plugin_name);
	}
	if (!load && loaded && (puphand != NULL)) {
		pup_unload(&rnd_pup, puphand, NULL);
		loaded = 0;
		puphand = NULL;
	}
}

/* action table number of columns for a single action */
const char *sch_rnd_action_args[] = {
/*short, -long, action, help, hint-on-error */
	NULL, "-show-actions",    "PrintActions()",     "Print all available actions (human readable) and exit",   NULL,
	NULL, "-dump-actions",    "DumpActions()",      "Print all available actions (script readable) and exit",  NULL,
	NULL, "-dump-plugins",    "DumpPlugins()",      "Print all available plugins (script readable) and exit",  NULL,
	NULL, "-dump-plugindirs", "DumpPluginDirs()",   "Print directories plugins might be loaded from and exit", NULL,
	NULL, "-dump-oflags",     "DumpObjFlags()",     "Print object flags and exit",                             NULL,
	NULL, "-show-paths",      "PrintPaths()",       "Print all configured paths and exit",                     NULL,
	"V",  "-version",         "PrintVersion()",     "Print version info and exit",                             NULL,
	"V",  "-dump-version",    "DumpVersion()",      "Print version info in script readable format and exit",   NULL,
	NULL, "-copyright",       "PrintCopyright()",   "Print copyright and exit",                                NULL,
	NULL, NULL, NULL, NULL, NULL /* terminator */
};

#include "funchash_core.h"
#define action_entry(x) { #x, F_ ## x},
static rnd_funchash_table_t Functions[] = {
#include "funchash_core_list.h"
	{"F_END", F_END}
};

static void gui_act_init(void)
{

}

extern void sch_rnd_main_act_init2(void);
extern void sch_rnd_file_act_init2(void);
extern void sch_rnd_font_act_init2(void);
extern void sch_rnd_select_act_init2(void);
extern void sch_rnd_draw_init2(void);
extern void sch_rnd_buffer_init2(void);
extern void sch_rnd_sheet_init2(void);
extern void sch_rnd_project_init(void);

/*extern void rnd_main_act_init2(void);*/

/* call before conf init */
static void sch_rnd_csch_init()
{
	csch_init();
	sch_rnd_multi_init();
	sch_rnd_project_init();
}

/* call after conf init */
static void sch_rnd_main_init(void)
{
	csch_init_actions();
	sch_rnd_main_act_init2();
	sch_rnd_sheet_init2();
	sch_rnd_file_act_init2();
	sch_rnd_font_act_init2();
	sch_rnd_select_act_init2();
	sch_rnd_extobj_act_init();
	sch_rnd_font_init2();
	sch_rnd_draw_init2();
	sch_rnd_buffer_init2();
/*	rnd_main_act_init2();*/
}

#define LIBCSCHEM_CFG_BIND(csch_cfg, native) \
	do { \
		if (sizeof(*csch_cfg) == sizeof(native)) \
			csch_cfg = &native; \
		else \
			rnd_message(RND_MSG_ERROR, "Can not enable " #native " on this arch. Please report this bug.\n"); \
	} while(0)

/* Bind conf_core natives to libcschem config API; this is needed because
   libcschem can't access conf_core directly as it can't depend on app code */
static void libcschem_cfg_init(void)
{
	LIBCSCHEM_CFG_BIND(csch_cfg_multiport_net_merge, conf_core.compile.multiport_net_merge);
	LIBCSCHEM_CFG_BIND(csch_cfg_hier_net_allow_rename, conf_core.compile.hierarchic.net_allow_rename);
	LIBCSCHEM_CFG_BIND(csch_cfg_hier_net_rename_prefer_top, conf_core.compile.hierarchic.net_rename_prefer_top);
	csch_app_load_sheet_cb = sch_rnd_app_load_sheet;
}

extern void sch_rnd_draw_uninit(void);
extern void sch_rnd_buffer_uninit(void);
extern void sch_rnd_sheet_uninit(void);
extern void sch_rnd_project_uninit(void);

void sch_rnd_main_uninit(void)
{
	TODO("librnd4.3: can't enable this because librnd sefaults on exit when it prints a log message and the gui is already uninited; fixed inlibrnd 4.3");
/*	rnd_tool_uninit();*/
	sch_rnd_sheet_free_all();
	sch_rnd_multi_uninit();
	rnd_funchash_uninit();
	csch_plug_io_act_uninit();
	sch_rnd_extobj_act_uninit();
	csch_uninit();
	sch_rnd_draw_uninit();
	sch_rnd_font_uninit();
	sch_rnd_buffer_uninit();
	sch_rnd_sheet_uninit();
	sch_rnd_project_uninit();
	gui_support_plugins(0);
	rnd_hidlib_uninit(); /* plugin unload */
	main_path_uninit();
	fgw_atexit();
	conf_core_uninit();
	rnd_log_uninit();
	csch_uninit_last();
}

extern const char *rnd_conf_internal;
extern const char *rnd_hidlib_default_embedded_menu;

int main(int argc, char *argv[])
{
	int n, res = 0;
	const char *fmt = NULL, *first_fn = NULL;
	char *exec_prefix;
	rnd_main_args_t ga;
	csch_sheet_t *first_sheet = NULL;
	sch_rnd_export_appspec_t appspec = {0};

	srand(time(NULL));  /* Set seed for rand() */

	rnd_app.package = "sch-rnd";
	rnd_app.version = SCH_VERSION;
	rnd_app.url = "http://www.repo.hu/projects/sch-rnd";

	rnd_app.menu_file_paths = menu_file_paths;
	rnd_app.menu_name_fmt   = menu_name_fmt;
	rnd_app.default_embedded_menu = rnd_hidlib_default_embedded_menu;

	rnd_app.conf_core_postproc = sch_rnd_conf_core_postproc;
	rnd_app.conf_userdir_path = CONF_USER_DIR;
	rnd_app.conf_user_path = CONF_USER_DIR "/sch-rnd-conf.lht";
	rnd_app.conf_internal = rnd_conf_internal;
	rnd_app.dot_dir = DOT_SCH_RND;

	rnd_app.crosshair_move_to = sch_rnd_hidlib_crosshair_move_to;
	rnd_app.draw_attached = sch_rnd_draw_attached;
	rnd_app.expose_main = sch_rnd_expose_main;
	rnd_app.expose_preview = sch_rnd_expose_preview;
	rnd_app.conf_dont_merge_node = sch_rnd_conf_dont_merge_node;
	rnd_app.multi_design = 1;

	rnd_printf_app_format = csch_printf_app_format;

	rnd_fix_locale_and_env();
	exec_prefix = rnd_exec_prefix(argv[0], BINDIR, BINDIR_TO_EXECPREFIX);
	main_path_init(exec_prefix);
	sch_rnd_csch_init();

	rnd_main_args_init(&ga, argc, sch_rnd_action_args);
	rnd_hidlib_init1(conf_core_init, exec_prefix);
	free(exec_prefix);

	csch_event_init_app();
	for(n = 1; n < argc; n++)
		n += rnd_main_args_add(&ga, argv[n], argv[n+1]);

	rnd_hidlib_init2(pup_buildins, sch_rnd_buildins);

	csch_init_units();
	gui_act_init();
	csch_plug_io_act_init();
	sch_rnd_main_init();
	rnd_conf_set(RND_CFR_CLI, "editor/view/flip_y", 0, "1", RND_POL_OVERWRITE);

	libcschem_cfg_init();

	rnd_funchash_set_table(Functions, RND_ENTRIES(Functions), NULL);

	/* Would be called from signal handlers too, using exit() */
	atexit(sch_rnd_emergency_save);


	rnd_hidlib_init3_auto();

	if (rnd_main_args_setup1(&ga) != 0) {
		sch_rnd_main_uninit();
		rnd_main_args_uninit(&ga);
		exit(1);
	}

/* Initialize actions only when the gui is already known so only the right
   one is registered (there can be only one GUI). */
TODO("action list");
/*#include "generated_lists.h"*/

	if (rnd_main_args_setup2(&ga, &res) != 0) {
		sch_rnd_main_uninit();
		rnd_main_args_uninit(&ga);
		exit(res);
	}

	for(n = 0; ga.hid_argc > 0; n++, ga.hid_argc--) {
		const char *fn = ga.hid_argv[n];
		csch_sheet_t *sheet;
		int is_project = 0;

		sheet = sch_rnd_multi_load(fn, fmt, &is_project);
		if ((first_fn == NULL) && !is_project)
			first_fn = fn;

		/* check if failed to load the sheet */
		if (sheet == NULL) {
			if (rnd_main_exporting) {
				rnd_message(RND_MSG_ERROR, "Can not load file '%s' (specified on command line) for exporting or printing\n", fn);
				rnd_log_print_uninit_errs("Export load error");
				exit(1);
			}
			else if (!sch_rnd_is_fn_project_file(fn)) {
				sheet = sch_rnd_multi_new_empty(fn);
				rnd_message(RND_MSG_INFO, "Creating new empty sheet for non-existing file '%s' (specified on command line)\n", fn);
				if (sheet == NULL) {
					rnd_log_print_uninit_errs("Sheet creation error");
					fprintf(stderr, "Failed to create new sheet (by loading the default sheet), exiting\n");
					sch_rnd_main_uninit();
					rnd_main_args_uninit(&ga);
					exit(1);
				}
			}
			else {
				rnd_log_print_uninit_errs("Project load error");
				fprintf(stderr, "Failed to load project '%s', exiting\n", fn);
				sch_rnd_main_uninit();
				rnd_main_args_uninit(&ga);
				exit(1);
			}
		}


		if (first_sheet == NULL)
			first_sheet = sheet;
		else
			appspec.exp_prj = 1;
		if (is_project)
			appspec.exp_prj = 1;
	}

	main_sighand_init();

	/* switch to the first sheet for export so that the project config is applied */
	if (rnd_main_exporting)
		sch_rnd_multi_switch_to_initial(first_sheet);

	if (rnd_main_exported(&ga, &first_sheet->hidlib, 0, &appspec)) {
		rnd_log_print_uninit_errs("Export finished");
		sch_rnd_sheet_free_all();
		sch_rnd_main_uninit();
		rnd_main_args_uninit(&ga);
		exit(0);
	}

	/* started with no sheet, create an unknown */
	if (first_sheet == NULL) {
		first_sheet = sch_rnd_multi_new_empty(NULL);
		if (first_sheet == NULL) {
			fprintf(stderr, "Failed to create new sheet (by loading the default sheet)\n");
			sch_rnd_main_uninit();
			rnd_main_args_uninit(&ga);
			exit(1);
		}
	}

	sch_rnd_multi_switch_to_initial(first_sheet);
	sch_rnd_crosshair_gui_init();


	sch_rnd_enable_autosave();

	/* main loop */
	if (RND_HAVE_GUI_ATTR_DLG)
		gui_support_plugins(1);
	if (EXPERIMENTAL != NULL) {
		rnd_message(RND_MSG_ERROR, "******************************** IMPORTANT ********************************\n");
		rnd_message(RND_MSG_ERROR, "This revision of sch-rnd is experimental, unstable, do NOT attempt to use\n");
		rnd_message(RND_MSG_ERROR, "it for production. The reason for this state is:\n");
		rnd_message(RND_MSG_ERROR, "%s\n", EXPERIMENTAL);
		rnd_message(RND_MSG_ERROR, "******************************** IMPORTANT ********************************\n");
	}

	rnd_mainloop_interactive(&ga, &first_sheet->hidlib);

	sch_rnd_crosshair_gui_uninit();

	sch_rnd_main_uninit();
	rnd_main_args_uninit(&ga);
	return res;
}
