/* SPDX-License-Identifier: GPL-2.0 */

// Read a toml keymap or plain text keymap (ir-keytable 1.14 or earlier
// format).

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdbool.h>
#include <sys/types.h>
#include <argp.h>

#include "keymap.h"
#include "toml.h"

#ifdef ENABLE_NLS
# define _(string) gettext(string)
# include "gettext.h"
# include <locale.h>
# include <langinfo.h>
# include <iconv.h>
#else
# define _(string) string
#endif

void free_keymap(struct keymap *map)
{
	struct scancode_entry *se;
	struct raw_entry *re;
	struct protocol_param *param;
	struct keymap *next;

	while (map) {
		re = map->raw;

		while (re) {
			struct raw_entry *next = re->next;
			free(re->keycode);
			free(re);
			re = next;
		}

		se = map->scancode;

		while (se) {
			struct scancode_entry *next = se->next;
			free(se->keycode);
			free(se);
			se = next;
		}

		param = map->param;

		while (param) {
			struct protocol_param *next = param->next;
			free(param->name);
			free(param);
			param = next;
		}

		next = map->next;

		free(map->name);
		free(map->protocol);
		free(map->variant);
		free(map);

		map = next;
	}
}

static error_t parse_plain_keymap(char *fname, struct keymap **keymap, bool verbose)
{
	FILE *fin;
	int line_no = 0;
	char *scancode, *keycode, *line = NULL;
	size_t line_size;
	struct scancode_entry *se;
	struct keymap *map;

	map = calloc(1, sizeof(*map));
	if (!map) {
		perror("parse_keymap");
		return ENOMEM;
	}

	if (verbose)
		fprintf(stderr, _("Parsing %s keycode file as plain text\n"), fname);

	fin = fopen(fname, "r");
	if (!fin) {
		fprintf(stderr, _("%s: error: cannot open: %m\n"), fname);
		return EINVAL;
	}

	while (getline(&line, &line_size, fin) >= 0) {
		char *p = line;

		line_no++;
		while (*p == ' ' || *p == '\t')
			p++;
		if (line_no==1 && p[0] == '#') {
			p++;
			p = strtok(p, "\n\t =:");
			do {
				if (!p)
					goto err_einval;
				if (!strcmp(p, "table")) {
					p = strtok(NULL,"\n, ");
					if (!p)
						goto err_einval;
					map->name = strdup(p);
				} else if (!strcmp(p, "type")) {
					p = strtok(NULL, " ,\n");
					if (!p)
						goto err_einval;

					while (p) {
						if (!map->protocol) {
							map->protocol = strdup(p);
						} else {
							struct keymap *k;

							k = calloc(1, sizeof(*k));
							k->protocol = strdup(p);
							k->next = map->next;
							map->next = k;
						}

						p = strtok(NULL, " ,\n");
					}
				} else {
					goto err_einval;
				}
				p = strtok(NULL, "\n\t =:");
			} while (p);
			continue;
		}

		if (*p == '\n' || *p == '#')
			continue;

		scancode = strtok(p, "\n\t =:");
		if (!scancode)
			goto err_einval;
		if (!strcasecmp(scancode, "scancode")) {
			scancode = strtok(NULL, "\n\t =:");
			if (!scancode)
				goto err_einval;
		}

		keycode = strtok(NULL, "\n\t =:(");
		if (!keycode)
			goto err_einval;

		se = calloc(1, sizeof(*se));
		if (!se) {
			free_keymap(map);
			perror("parse_keymap");
			fclose(fin);
			return ENOMEM;
		}

		se->scancode = strtoull(scancode, NULL, 0);
		se->keycode = strdup(keycode);
		se->next = map->scancode;
		map->scancode = se;
	}
	fclose(fin);
	free(line);

	if (!map->protocol) {
		fprintf(stderr, _("Missing protocol in %s\n"), fname);
		return EINVAL;
	}

	*keymap = map;

	return 0;

err_einval:
	free_keymap(map);
	fprintf(stderr, _("Invalid parameter on line %d of %s\n"),
		line_no, fname);
	return EINVAL;
}

static error_t parse_rawir_string(const char *fname, char *str, struct raw_entry **entry)
{
	struct raw_entry *re;
	const char sep[] = "\n\r\t\v ,";
	const char *p;
	char *copy;
	int i, size = 0;

	// First do one scan so that we know the length
	copy = strdup(str);
	p = strtok(copy, sep);
	while (p) {
		size++;
		p = strtok(NULL, sep);
	}

	re = calloc(1, sizeof(*re) + sizeof(re->raw[0]) * size);
	if (!re) {
		fprintf(stderr, _("Failed to allocate memory"));
		free(copy);
		return EINVAL;
	}

	// Second scan to extract values and validate
	strcpy(copy, str);
	p = strtok(copy, sep);
	i = 0;
	while (p) {
		long int value;
		char *q;

		value = strtoll(p, &q, 10);
		if (*q || value == 0 || errno) {
			fprintf(stderr, _("%s: incorrect raw value `%s'"),
				fname, p);
			free(copy);
			free(re);
			return EINVAL;
		}

		if (value < 0) {
			if (i % 2)
				value = -value;
			else {
				fprintf(stderr, _("%s: negative raw value `%ld` at position %d only allowed for gaps/spaces"),
					fname, value, i);
				free(copy);
				free(re);
				return EINVAL;
			}
		}

		if (value <= 0 || value > USHRT_MAX) {
			fprintf(stderr, _("%s: raw value %ld out of range"),
				fname, value);
			free(copy);
			free(re);
			return EINVAL;
		}

		re->raw[i++] = value;

		p = strtok(NULL, sep);
	}

	free(copy);
	re->raw_length = size;

	*entry = re;

	return 0;
}

static error_t parse_toml_raw_part(const char *fname, struct toml_array_t *raw, struct keymap *map, bool verbose)
{
	char *keycode, *raw_str;
	struct toml_table_t *t;
	struct raw_entry *re;
	const char *traw;
	int ind = 0;

	while ((t = toml_table_at(raw, ind++)) != NULL) {
		traw = toml_raw_in(t, "keycode");
		if (!traw) {
			fprintf(stderr, _("%s: invalid keycode for raw entry %d\n"),
				fname, ind);
			return EINVAL;
		}

		if (toml_rtos(traw, &keycode)) {
			fprintf(stderr, _("%s: bad value `%s' for keycode\n"),
				fname, traw);
			return EINVAL;
		}

		traw = toml_raw_in(t, "raw");
		if (!traw) {
			fprintf(stderr, _("%s: missing raw value for entry %d\n"),
				fname, ind);
			free(keycode);
			return EINVAL;
		}

		if (toml_rtos(traw, &raw_str)) {
			fprintf(stderr, _("%s: bad value `%s' for keycode\n"),
				fname, traw);
			free(keycode);
			return EINVAL;
		}

		if (parse_rawir_string(fname, raw_str, &re)) {
			free(keycode);
			free(raw_str);
			return EINVAL;
		}

		free(raw_str);
		re->keycode = keycode;
		re->next = map->raw;
		map->raw = re;
	}

	return 0;
}

static error_t parse_toml_protocol(const char *fname, struct toml_table_t *proot, struct keymap **keymap, bool verbose)
{
	struct toml_table_t *scancodes;
	struct toml_array_t *rawarray;
	const char *raw, *key;
	bool have_raw_protocol = false;
	struct keymap *map;
	char *p;
	int i = 0;

	map = calloc(1, sizeof(*map));
	if (!map) {
		perror("parse_toml_protocol");
		return ENOMEM;
	}
	*keymap = map;

	raw = toml_raw_in(proot, "protocol");
	if (!raw) {
		fprintf(stderr, _("%s: protocol missing\n"), fname);
		free_keymap(map);
		return EINVAL;
	}

	if (toml_rtos(raw, &p)) {
		fprintf(stderr, _("%s: bad value `%s' for protocol\n"), fname, raw);
		free_keymap(map);
		return EINVAL;
	}

	map->protocol = p;
	if (!strcmp(p, "raw"))
		have_raw_protocol = true;

	raw = toml_raw_in(proot, "variant");
	if (raw) {
		if (toml_rtos(raw, &p)) {
			fprintf(stderr, _("%s: bad value `%s' for variant\n"), fname, raw);
			free_keymap(map);
			return EINVAL;
		}

		map->variant = p;
	}

	raw = toml_raw_in(proot, "name");
	if (raw) {
		if (toml_rtos(raw, &p)) {
			fprintf(stderr, _("%s: bad value `%s' for name\n"), fname, raw);
			free_keymap(map);
			return EINVAL;
		}

		map->name = p;
	}

	rawarray = toml_array_in(proot, "raw");
	if (rawarray) {
		if (toml_raw_in(proot, "scancodes")) {
			fprintf(stderr, _("Cannot have both [raw] and [scancode] sections"));
			free_keymap(map);
			return EINVAL;
		}
		if (!have_raw_protocol) {
			fprintf(stderr, _("Keymap with raw entries must have raw protocol"));
			free_keymap(map);
			return EINVAL;
		}
		error_t err = parse_toml_raw_part(fname, rawarray, map, verbose);
		if (err != 0) {
			free_keymap(map);
			return err;
		}
	} else if (have_raw_protocol) {
		fprintf(stderr, _("Keymap with raw protocol must have raw entries"));
		free_keymap(map);
		return EINVAL;
	}

	for (i = 0; (key = toml_key_in(proot, i)) != NULL; i++) {
		int64_t value;

		raw = toml_raw_in(proot, key);
		if (!toml_rtoi(raw, &value)) {
			struct protocol_param *param;

			param = malloc(sizeof(*param));
			param->name = strdup(key);
			param->value = value;
			param->next = map->param;
			map->param = param;
			if (verbose)
				fprintf(stderr, _("%s: protocol parameter %s=%ld\n"), fname, param->name, param->value);
		}
	}

	scancodes = toml_table_in(proot, "scancodes");
	if (!scancodes) {
		if (verbose)
			fprintf(stderr, _("%s: no [protocols.scancodes] section\n"), fname);
		return 0;
	}

	struct scancode_entry **next = &map->scancode;
	i = 0;

	for (;;) {
		struct scancode_entry *se;
		const char *scancode;
		char *keycode;

		scancode = toml_key_in(scancodes, i++);
		if (!scancode)
			break;

		raw = toml_raw_in(scancodes, scancode);
		if (!raw) {
			fprintf(stderr, _("%s: invalid value `%s'\n"), fname, scancode);
			free_keymap(map);
			return EINVAL;
		}

		if (toml_rtos(raw, &keycode)) {
			fprintf(stderr, _("%s: bad value `%s' for keycode\n"),
				fname, keycode);
			free_keymap(map);
			return EINVAL;
		}

		se = calloc(1, sizeof(*se));
		if (!se) {
			perror("parse_keymap");
			free(keycode);
			free_keymap(map);
			return ENOMEM;
		}

		se->scancode = strtoull(scancode, NULL, 0);
		se->keycode = keycode;
		*next = se;
		next = &se->next;
	}

	return 0;
}

static error_t parse_toml_keymap(char *fname, struct keymap **keymap, bool verbose)
{
	struct toml_table_t *root, *proot;
	struct toml_array_t *arr;
	int ret, i = 0;
	char buf[200];
	FILE *fin;

	if (verbose)
		fprintf(stderr, _("Parsing %s keycode file as toml\n"), fname);

	fin = fopen(fname, "r");
	if (!fin) {
		fprintf(stderr, _("%s: error: cannot open: %m\n"), fname);
		return EINVAL;
	}

	root = toml_parse_file(fin, buf, sizeof(buf));
	fclose(fin);
	if (!root) {
		fprintf(stderr, _("%s: error: %s\n"), fname, buf);
		return EINVAL;
	}

	arr = toml_array_in(root, "protocols");
	if (!arr) {
		fprintf(stderr, _("%s: missing [protocols] section\n"), fname);
		goto out;
	}

	struct keymap *map = NULL;

	for (;;) {
		struct keymap *cur_map;

		proot = toml_table_at(arr, i);
		if (!proot)
			break;

		ret = parse_toml_protocol(fname, proot, &cur_map, verbose);
		if (ret)
			goto out;

		if (!map) {
			map = cur_map;
		} else {
			cur_map->next = map->next;
			map->next = cur_map;
		}
		i++;
	}

	if (i == 0) {
		fprintf(stderr, _("%s: no protocols found\n"), fname);
		goto out;
	}

	toml_free(root);
	*keymap = map;
	return 0;
out:
	toml_free(root);
	return EINVAL;
}

error_t parse_keymap(char *fname, struct keymap **keymap, bool verbose)
{
	size_t len = strlen(fname);

	if (len >= 5 && strcasecmp(fname + len - 5, ".toml") == 0)
		return parse_toml_keymap(fname, keymap, verbose);

	return parse_plain_keymap(fname, keymap, verbose);
}

int keymap_param(struct keymap *map, const char *name, int fallback)
{
	struct protocol_param *param;

	for (param = map->param; param; param = param->next) {
		if (!strcmp(param->name, name))
			return param->value;
	}

	return fallback;
}
