/*
 * setfont.c - Eugene Crosser & Andries Brouwer
 */
#include "config.h"

#include <stdio.h>
#include <memory.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sysexits.h>

#include <kfont.h>

#include "libcommon.h"

static void KBD_ATTR_NORETURN
usage(int retcode, const struct kbd_help *options)
{
	fprintf(stderr, _("Usage: %s [option...] [newfont...]\n"), program_invocation_short_name);
	fprintf(stderr, "\n");
	fprintf(stderr, _("Loads the console font, and possibly the corresponding screen map(s).\n"));

	print_options(options);
	fprintf(stderr, "\n");

	fprintf(stderr,
		_("The options -[o|O|om|ou] are processed before the new font is uploaded.\n"
		  "\n"
		  "If no <newfont> and no -[o|O|om|ou|m|u] option is given, a default\n"
		  "font is loaded.\n"
		  "\n"
		  "There are two kinds of screen maps, one [-m] giving the correspondence\n"
		  "between some arbitrary 8-bit character set currently in use and the\n"
		  "font positions, and the second [-u] giving the correspondence between\n"
		  "font positions and Unicode values.\n"
		  "\n"
		  "Explicitly (with -m or -u) or implicitly (in the fontfile) given\n"
		  "mappings will be loaded and, in the case of consolemaps, activated.\n"
		  "\n"
		  "Files are loaded from the %s/*/.\n"),
		DATADIR);

	print_report_bugs();

	exit(retcode);
}

enum kbd_getopt_arg {
	kbd_no_argument,
	kbd_required_argument
};

struct kbd_option {
	const char *short_name;
	const char *long_name;
	enum kbd_getopt_arg has_arg;
	int val;
};

static int
kbd_getopt(int argc, char **argv, const struct kbd_option *opts)
{
	const struct kbd_option *opt;
	char *p, *o, *option;
	const char *name;

	optarg = NULL;

	if (!optind)
		optind = 1;

	if (optind >= argc || !argv[optind])
		return -1;

	if (argv[optind][0] != '-')
		return '?';

	option = argv[optind];

	if (option[1] && option[1] != '-') {
		// A short option that starts with "-" (not "--"). Unlike the
		// standard getopt for historical reasons we have two character
		// short options.
		option += 1;

		for (opt = opts; opt->val; opt++) {
			name = opt->short_name + 1;

			for (p = (char *) name, o = option; *p && *p == *o; p++, o++);

			switch (opt->short_name[0]) {
				// Case: full match option.
				case '=':
					if (*p == *o) {
						optind++;

						if (opt->has_arg) {
							if (optind == argc)
								goto required_argument;
							optarg = argv[optind++];
						}
						return opt->val;
					}
					break;
				// Case: prefix option.
				case '+':
					if (*p == '\0' && *o != '\0') {
						optind++;

						if (opt->has_arg)
							optarg = o;
						return opt->val;
					}
					break;
			}
		}
	} else if (option[1] && option[1] == '-') {
		// The special argument "--" forces an end of option-scanning
		// regardless of the scanning mode.
		if (!option[2]) {
			optind++;
			return -1;
		}
		option += 2;

		for (opt = opts; opt->val; opt++) {
			name = opt->long_name;

			for (p = (char *) name, o = option; *p && *p == *o; p++, o++);

			// Case: --arg param
			if (*p == *o) {
				optind++;

				if (opt->has_arg) {
					if (optind == argc)
						goto required_argument;
					optarg = argv[optind++];
				}
				return opt->val;
			}

			if (!opt->has_arg)
				continue;

			// Case: --arg=param
			if (*p == '\0' && *o == '=') {
				optind++;
				optarg = ++o;

				if (!optarg[0])
					goto required_argument;
				return opt->val;
			}
		}
	}
	return '?';

required_argument:
	kbd_warning(0, "option '%s' requires an argument", name);
	return '?';
}

int main(int argc, char *argv[])
{
	const char *ifiles[MAXIFILES];
	char *mfil, *ufil, *Ofil, *ofil, *omfil, *oufil, *console;
	int ifilct = 0, fd, no_m, no_u;
	unsigned int iunit, hwunit;
	int restore = 0;
	int ret, c;

	struct kfont_context *kfont;

	const struct kbd_help opthelp[] = {
		{ "-<N>, --default8x <N>",           _("load font \"default8x<N>\".") },
		{ "-h<N>, --font-height <N>",        _("override font height (there shouldn't be a space in the short option).") },
		{ "-o, --output-font <FILE>",        _("write current font to <FILE>.") },
		{ "-om, --output-consolemap <FILE>", _("write current consolemap to <FILE>.") },
		{ "-ou, --output-unicodemap <FILE>", _("write current unicodemap to <FILE>.") },
		{ "-O, --output-fullfont <FILE>",    _("write current font and unicode map to <FILE>.") },
		{ "-m, --consolemap <FILE>",         _("load console screen map ('none' means don't load it).") },
		{ "-u, --unicodemap <FILE>",         _("load font unicode map ('none' means don't load it).") },
		{ "-C, --console <DEV>",             _("the console device to be used.") },
		{ "-d, --double",                    _("double size of font horizontally and vertically.") },
		{ "-f, --force",                     _("force load unicode map.") },
		{ "-R, --reset",                     _("reset the screen font, size, and unicode map to the bootup defaults.") },
		{ "-v, --verbose",                   _("be more verbose.") },
		{ "-V, --version",                   _("print version number.") },
		{ "-h, --help",                      _("print this usage message.") },
		{ NULL, NULL }
	};

	const struct kbd_option opts[] = {
		{ "=d",  "double",            kbd_no_argument,       'd' },
		{ "=f",  "force",             kbd_no_argument,       'f' },
		{ "=R",  "reset",             kbd_no_argument,       'R' },
		{ "=v",  "verbose",           kbd_no_argument,       'v' },
		{ "=V",  "version",           kbd_no_argument,       'V' },
		{ "=h",  "help",              kbd_no_argument,       'H' },
		{ "+h",  "font-height",       kbd_required_argument, 'h' },
		{ "=om", "output-consolemap", kbd_required_argument, 'M' },
		{ "=ou", "output-unicodemap", kbd_required_argument, 'U' },
		{ "=o",  "output-font",       kbd_required_argument, 'o' },
		{ "=O",  "output-fullfont",   kbd_required_argument, 'O' },
		{ "=m",  "consolemap",        kbd_required_argument, 'm' },
		{ "=u",  "unicodemap",        kbd_required_argument, 'u' },
		{ "=C",  "console",           kbd_required_argument, 'C' },
		{ "+",   "default8x",         kbd_required_argument, 'N' },
		{ NULL, NULL, 0, 0 },
	};

	setuplocale();

	if ((ret = kfont_init(program_invocation_short_name, &kfont)) < 0)
		return -ret;

	ifiles[0] = mfil = ufil = Ofil = ofil = omfil = oufil = NULL;
	iunit = hwunit = 0;
	no_m = no_u = 0;
	console = NULL;

	while ((c = kbd_getopt(argc, argv, opts)) != -1) {
		switch (c) {
			case 'h': {
					int num = atoi(optarg);
					if (num <= 0 || num > 32) {
						kbd_warning(0, "bad option '%s': not a number '%s'",
							    argv[optind - 1], optarg);
						usage(EX_USAGE, opthelp);
					}
					hwunit = (unsigned int) num;
				}
				break;
			case 'N': {
					int num = atoi(optarg);
					if (num <= 0 || num > 32) {
						kbd_warning(0, "unrecognized option '%s'",
							    argv[optind - 1]);
						usage(EX_USAGE, opthelp);
					}
					iunit = (unsigned int) num;
				}
				break;
			case 'o':
				ofil = optarg;
				break;
			case 'M':
				omfil = optarg;
				break;
			case 'U':
				oufil = optarg;
				break;
			case 'm':
				no_m = !strcmp(optarg, "none");
				if (!no_m)
					mfil = optarg;
				break;
			case 'u':
				no_u = !strcmp(optarg, "none");
				if (!no_u)
					ufil = optarg;
				break;
			case 'O':
				Ofil = optarg;
				break;
			case 'C':
				console = optarg;
				break;
			case 'R':
				restore = 1;
				break;
			case 'd':
				kfont_set_option(kfont, kfont_double_size);
				break;
			case 'f':
				kfont_set_option(kfont, kfont_force);
				break;
			case 'v':
				kfont_inc_verbosity(kfont);
				break;
			case 'V':
				print_version_and_exit();
				break;
			case 'H':
				usage(EXIT_SUCCESS, opthelp);
				break;
			case '?':
				if (argv[optind][0] == '-') {
					kbd_warning(0, "invalid option '%s'", argv[optind]);
					usage(EX_USAGE, opthelp);
				}

				if (ifilct == MAXIFILES)
					kbd_error(EX_USAGE, 0, _("Too many input files."));
				ifiles[ifilct++] = argv[optind++];
				break;
		}
	}

	for (; optind < argc; optind++) {
		if (ifilct == MAXIFILES)
			kbd_error(EX_USAGE, 0, _("Too many input files."));
		ifiles[ifilct++] = argv[optind];
	}

	if (ifilct && restore)
		kbd_error(EX_USAGE, 0, _("Cannot both restore from character ROM"
					 " and from file. Font unchanged."));

	if ((fd = getfd(console)) < 0)
		kbd_error(EX_OSERR, 0, _("Couldn't get a file descriptor referring to the console."));

	int kd_mode = -1;
	if (!ioctl(fd, KDGETMODE, &kd_mode) && (kd_mode == KD_GRAPHICS)) {
		/*
		 * PIO_FONT will fail on a console which is in foreground and in KD_GRAPHICS mode.
		 * 2005-03-03, jw@suse.de.
		 */
		if (kfont_get_verbosity(kfont))
			kbd_warning(0, "graphics console %s skipped", console ? console : "");
		close(fd);
		return EX_OK;
	}

	if (!ifilct && !mfil && !ufil &&
	    !Ofil && !ofil && !omfil && !oufil && !restore)
		/* reset to some default */
		ifiles[ifilct++] = "";

	if (Ofil && (ret = kfont_save_font(kfont, fd, Ofil, 1)) < 0)
		return -ret;

	if (ofil && (ret = kfont_save_font(kfont, fd, ofil, 0)) < 0)
		return -ret;

	if (omfil && (ret = kfont_save_consolemap(kfont, fd, omfil)) < 0)
		return -ret;

	if (oufil && (ret = kfont_save_unicodemap(kfont, fd, oufil)) < 0)
		return -ret;

	if (mfil) {
		if ((ret = kfont_load_consolemap(kfont, fd, mfil)) < 0)
			return -ret;
		kfont_activatemap(fd);
		no_m = 1;
	}

	if (ufil)
		no_u = 1;

	if (restore)
		kfont_restore_font(kfont, fd);

	if (ifilct && (ret = kfont_load_fonts(kfont, fd, ifiles, ifilct, iunit, hwunit, no_m, no_u)) < 0)
		return -ret;

	if (ufil && (ret = kfont_load_unicodemap(kfont, fd, ufil)) < 0)
		return -ret;

	return EX_OK;
}
