//  Copyright (c) 2012-2026 Jakub Filipowicz <jakubf@gmail.com>
//
//  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

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "libem400.h"
#include "ui/ui.h"
#include "appcfg.h"
#include "cfg.h"
#include "default_config.h"

// -----------------------------------------------------------------------
static void em400_msg_to_ui(em400_sev_t sev, const char *text)
{
	ui_msg(sev, "%s", text);
}

// -----------------------------------------------------------------------
static int em400_top_init(em400_cfg *cfg, const char *machine_id, const char *legacy_import)
{
	const char *log_file_name = cfg_getstr(cfg, "log:file", cfg_default_log_file());
	em400_log_buf_type_t log_buf_type =
		cfg_getbool(cfg, "log:line_buffered", CFG_DEFAULT_LOG_LINE_BUFFERED)
		? EM400_LOG_LINE_BUFFERED
		: EM400_LOG_FULL_BUFFERED;
	const char *log_components = cfg_getstr(cfg, "log:components", CFG_DEFAULT_LOG_COMPONENTS);
	bool log_enabled = cfg_getbool(cfg, "log:enabled", CFG_DEFAULT_LOG_ENABLED);

	if (em400_log_init(log_file_name, log_buf_type, log_components, log_enabled) != E_OK) {
		return E_ERR;
	}

	if (appcfg_build_from_ini(cfg) != E_OK) {
		em400_log("Failed to build EM400 configuration");
		return E_ERR;
	}

	if (legacy_import) {
		em400_cfg *legacy_cfg = cfg_load(legacy_import);
		if (!legacy_cfg) {
			return em400_msg(EM400_MSG_ERROR, "Failed to load legacy config file for import: %s", legacy_import);
		}
		em400_log("Importing machine configuration from legacy file: %s", legacy_import);
		int rc = appcfg_import_legacy_machine(legacy_cfg, APPCFG_IMPORTED_MACHINE_ID, "Imported configuration");
		cfg_free(legacy_cfg);
		if (rc != E_OK) {
			return E_ERR;
		}
	}

	if (machine_id) {
		if (!appcfg_machine_find(&appcfg, machine_id)) {
			return em400_msg(EM400_MSG_ERROR, "No such machine in configuration: %s", machine_id);
		}
		em400_log("Active machine overridden from commandline: %s", machine_id);
		free(appcfg.active_id);
		appcfg.active_id = strdup(machine_id);
	}

	return E_OK;
}

// -----------------------------------------------------------------------
static void em400_top_shutdown()
{
	appcfg_free();
	em400_log_shutdown();
}

// -----------------------------------------------------------------------
static void em400_usage()
{
	fprintf(stdout, "EM400 version %s\n", em400_version());
	fprintf(stdout,
		"Usage: em400 [option] ...\n"
		"\n"
		"Options:\n"
		"   -h               : Display help\n"
		"   -c config        : Config file to use instead of the default one ($XDG_CONFIG_HOME/em400/em400.ini)\n"
		"   -m machine       : Select which configured machine to start (overrides machine set in [general] section)\n"
		"   -p program       : Load program image into OS memory at address 0\n"
		"   -l cmp,cmp,...   : Enable logging for specified components. Prefix a component\n"
		"                      with '-' to exclude it, e.g. \"all,-cpu\" logs everything but cpu\n"
		"                      (lib is always logged and cannot be excluded). Components:\n"
		"                      lib, app, crk5, mem, cpu, op, int, io, mx, cchr, cmem, uzdat, uzfx,\n"
		"                      meclo, term, 9425, wnch, flop, pnch, pnrd, tape, all\n"
		"   -L               : Disable logging\n"
		"   -u ui            : User interface to use. Available UIs (first is the default):"
	);
	ui_print_uis(stdout);
	fprintf(stdout, "\n");
	fprintf(stdout,
		"   -O sec:key=value : Override configuration entry \"key\" in section [sec] with a specific value\n"
	);
}

// -----------------------------------------------------------------------
static char * str_concat(const char *a, const char *b)
{
	if (!a || !b) return NULL;
	size_t len = strlen(a) + strlen(b) + 1;
	char *p = (char *) malloc(len);
	if (p) {
		snprintf(p, len, "%s%s", a, b);
	}
	return p;
}

#ifdef _WIN32
#define PATH_SEP "\\"
#else
#define PATH_SEP "/"
#endif

// -----------------------------------------------------------------------
// Config base directory: %APPDATA% on Windows, $XDG_CONFIG_HOME (or
// ~/.config when unset/empty) elsewhere.
static char * config_base()
{
#ifdef _WIN32
	const char *appdata = getenv("APPDATA");
	if (appdata && *appdata) {
		return strdup(appdata);
	}
	return NULL;
#else
	const char *xdg = getenv("XDG_CONFIG_HOME");
	if (xdg && *xdg) {
		return strdup(xdg);
	}
	return str_concat(getenv("HOME"), "/.config");
#endif
}

// -----------------------------------------------------------------------
static char * config_path()
{
	char *base = config_base();
	char *path = str_concat(base, PATH_SEP "em400" PATH_SEP "em400.ini");
	free(base);
	return path;
}

// -----------------------------------------------------------------------
static char * legacy_config_path()
{
#ifdef _WIN32
	return NULL; // no legacy ~/.em400 location on Windows
#else
	return str_concat(getenv("HOME"), "/.em400/em400.ini");
#endif
}

// -----------------------------------------------------------------------
static void em400_mkconfdir()
{
	char *base = config_base();
	if (!base) return;
#ifdef _WIN32
	mkdir(base);
#else
	mkdir(base, 0700); // ensure the XDG base (e.g. ~/.config) exists first
#endif

	char *dir = str_concat(base, PATH_SEP "em400");
	if (dir) {
#ifdef _WIN32
		mkdir(dir);
#else
		mkdir(dir, 0700);
#endif
		free(dir);
	}
	free(base);
}

// -----------------------------------------------------------------------
static int write_default_config(const char *path)
{
	FILE *f = fopen(path, "wb");
	if (!f) {
		return E_ERR;
	}
	size_t len = strlen(default_config_ini);
	int ok = (fwrite(default_config_ini, 1, len, f) == len);
	if (fclose(f) != 0) {
		ok = 0;
	}
	return ok ? E_OK : E_ERR;
}

const char em400_cmdline_opts[] = "hc:m:p:l:Lu:O:";

// -----------------------------------------------------------------------
static int em400_cmdline_1(int argc, char **argv, int *print_help, char **config, const char **ui)
{
	int option;

	while ((option = getopt(argc, argv, em400_cmdline_opts)) != -1) {
		switch (option) {
			case 'h':
				*print_help = 1;
				break;
			case 'c':
				*config = strdup(optarg);
				break;
			case 'u':
				*ui = optarg;
				break;
			case 'm':
			case 'p':
			case 'L':
			case 'l':
			case 'O':
				break;
			default:
				return em400_msg(EM400_MSG_ERROR, "Failed to parse commandline arguments (pass 1)");
		}
	}

	return E_OK;
}

// -----------------------------------------------------------------------
static int em400_cmdline_2(em400_cfg *cfg, int argc, char **argv, const char **program, const char **machine)
{
	int option;
	optind = 1; // reset to 1 so consecutive calls work

	while ((option = getopt(argc, argv, em400_cmdline_opts)) != -1) {
		switch (option) {
			case 'h':
			case 'c':
			case 'u':
				break;
			case 'm':
				*machine = optarg;
				break;
			case 'p':
				*program = optarg;
				break;
			case 'L':
				cfg_set(cfg, "log:enabled", "false");
				break;
			case 'l':
				cfg_set(cfg, "log:enabled", "true");
				cfg_set(cfg, "log:components", optarg);
				break;
			case 'O':
				const char *colon = strchr(optarg, ':');
				const char *key = strtok(optarg, "=");
				const char *val = strtok(NULL, "=");
				if (!colon || !key || !val) {
					return em400_msg(EM400_MSG_ERROR, "Syntax error for -O switch. Should be: -O section:key=value");
				}
				cfg_set(cfg, key, val);
				break;
			default:
				return em400_msg(EM400_MSG_ERROR, "Failed to parse commandline arguments (pass 2)");
		}
	}

	return E_OK;
}

// -----------------------------------------------------------------------
// ---- MAIN -------------------------------------------------------------
// -----------------------------------------------------------------------
int main(int argc, char** argv)
{
	int return_code = -1;

	int print_help = 0;
	char *config = NULL;
	char *legacy_path = NULL;
	const char *program = NULL;
	const char *machine_id = NULL;
	const char *ui_name = NULL;
	em400_cfg *cfg = NULL;
	struct ui *ui = NULL;

	em400_set_msg_sink(em400_msg_to_ui);
	em400_mkconfdir();

	if (em400_cmdline_1(argc, argv, &print_help, &config, &ui_name) != E_OK) {
		goto done;
	}

	if (print_help) {
		em400_usage();
		return_code = 0;
		goto done;
	}

	if (!(ui = ui_create(ui_name))) {
		goto done;
	}

	if (!config) {
		config = config_path();
		if (!config) {
			em400_msg(EM400_MSG_ERROR, "Config filename memory allocation error");
			goto done;
		}
		// first-run migration: no XDG config yet but a legacy one exists.
		// Start from a fresh default config rather than converting the legacy
		// file; the legacy machine is imported into it separately below.
		// Never touch the legacy file itself.
		if (access(config, F_OK) != 0) {
			char *legacy = legacy_config_path();
			if (legacy && (access(legacy, F_OK) == 0)) {
				em400_msg(EM400_MSG_INFO,
					"Creating a fresh configuration and importing your old machine setup:\n"
					"OLD: %s (now obsolete, left untouched)\n"
					"NEW: %s",
					legacy, config);
				legacy_path = legacy;
			} else {
				free(legacy);
			}
			em400_log("Creating a default configuration at: %s", config);
			if (write_default_config(config) != E_OK) {
				em400_msg(EM400_MSG_ERROR, "Failed to create default config file: %s", config);
				goto done;
			}
		}
	}

	cfg = cfg_load(config);
	if (!cfg) {
		em400_msg(EM400_MSG_ERROR, "Failed to load config file: %s", config);
		goto done;
	}

	// read the commandline again to build final configuration
	if (em400_cmdline_2(cfg, argc, argv, &program, &machine_id) != E_OK) {
		goto done;
	}

	if (em400_top_init(cfg, machine_id, legacy_path) != E_OK) {
		goto done;
	}
	em400_log("Configuration loaded from: %s", config);

	appcfg_set_path(config);

	// persist the imported machine; a failed migration is fatal so the user
	// is not left booting on a config that did not get saved
	if (legacy_path) {
		em400_log("First-run migration: saving imported machine to new config");
		if (appcfg_write(&appcfg, config) != E_OK) {
			goto done;
		}
	}

	// nothing needs cfg anymore
	cfg_free(cfg);
	cfg = NULL;

	appcfg_migrate_images(&appcfg);

	if (ui_run(ui, program) != E_OK) {
		goto done;
	}

	return_code = 0;

done:
	ui_shutdown(ui);
	em400_top_shutdown();
	cfg_free(cfg);
	free(config);
	free(legacy_path);
	return return_code;
}

// vim: tabstop=4 shiftwidth=4 autoindent
