/*
 * Copyright (c) 2015 Peter Rosin <peda@lysator.liu.se>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <string.h>
#include <stdlib.h>
#include "confuse.h"

static cfg_t *cfg;
static int cmdc;
static char **cmdv;
static int cmdv_max;

static int reserve_cmdv(int cmdc)
{
	int cmd_max = cmdv_max;
	char **tmp;

	if (cmdc < cmd_max)
		return 0;

	do
		cmd_max += 10;
	while (cmdc >= cmd_max);

	tmp = realloc(cmdv, cmd_max * sizeof(*tmp));
	if (!tmp)
		return -1;

	cmdv = tmp;
	cmdv_max = cmd_max;
	return 0;
}

static int split_cmd(char *cmd)
{
	char *out = cmd;
	int arg;

	cmdc = 0;

	for (;;) {
		char ch;

		arg = 0;
		while (*cmd == ' ')
			++cmd;
 next_char:
		ch = *cmd++;
		if (ch == '"' || ch == '\'') {
			char end = ch;

			if (!arg) {
				if (reserve_cmdv(cmdc + 1))
					return -1;
				cmdv[cmdc++] = out;
				arg = 1;
			}
			while (*cmd && *cmd != end)
				*out++ = *cmd++;
			if (!*cmd++)
				return -1;
			goto next_char;
		}
		if (ch && ch != ' ') {
			if (!arg) {
				if (reserve_cmdv(cmdc + 1))
					return -1;
				cmdv[cmdc++] = out;
				arg = 1;
			}
			*out++ = ch;
			goto next_char;
		}
		*out++ = 0;
		if (!ch)
			break;
	}
	return cmdc;
}

static char *input_cmd(void)
{
	int ch;
	char *cmd = malloc(128);
	int len = 128;
	int pos = 0;

	if (!cmd)
		return NULL;

	for (;;) {
		ch = fgetc(stdin);
		if (ch < 0)
			break;
		switch (ch) {
		case '\r':
		case '\n':
			ch = 0;
			/* fall through */
		default:
			if (pos == len) {
				char *tmp = realloc(cmd, len * 2);

				if (!tmp)
					goto cleanup;
				cmd = tmp;
				len *= 2;
			}
			cmd[pos++] = ch;
			if (!ch)
				return cmd;
		}
	}
 cleanup:
	free(cmd);
	return NULL;
}

static const char *help(int argc, char *argv[])
{
	return  "Available commands:\n"
		"\n"
		"help\n"
		"set <option> <value> ...\n"
		"subset <section> <option> <value>\n"
		"create <section>\n"
		"destroy <section>\n"
		"dump [mod|modified]\n"
		"quit\n"
		"\n"
		"<option> is one of 'bool', 'int' 'string' and 'float'.\n";
}

static const char *set(int argc, char *argv[])
{
	if (argc < 3)
		return "Need more args\n";

	if (!cfg_getopt(cfg, argv[1]))
		return "Unknown option\n";

	if (cfg_setmulti(cfg, argv[1], argc - 2, &argv[2]))
		return "Failure\n";

	return "OK\n";
}

static const char *subset(int argc, char *argv[])
{
	cfg_t *sub;

	if (argc < 4)
		return "Need more args\n";
	if (argc > 4)
		return "Too many args\n";

	sub = cfg_gettsec(cfg, "sub", argv[1]);
	if (!sub)
		return "No such section\n";

	if (!cfg_getopt(sub, argv[2]))
		return "Unknown option\n";

	if (cfg_setmulti(sub, argv[2], argc - 3, &argv[3]))
		return "Failure\n";

	return "OK\n";
}

static const char *create(int argc, char *argv[])
{
	cfg_opt_t *opt;

	if (argc != 2)
		return "Need one arg\n";

	if (cfg_gettsec(cfg, "sub", argv[1]))
		return "Section exists already\n";

	opt = cfg_getopt(cfg, "sub");
	if (!opt || !cfg_setopt(cfg, opt, argv[1]))
		return "Failure\n";

	return "OK\n";
}

static const char *destroy(int argc, char *argv[])
{
	if (argc < 2)
		return "Need one arg\n";

	if (!cfg_gettsec(cfg, "sub", argv[1]))
		return "No such section\n";

	cfg_rmtsec(cfg, "sub", argv[1]);
	return "OK\n";
}

static int print_modified(cfg_t *cfg, cfg_opt_t *opt)
{
	cfg_t *sec;
	int i;

	if (opt->type != CFGT_SEC)
		return !(opt->flags & CFGF_MODIFIED);

	if (opt->flags & CFGF_MULTI)
		return 0;

	/*
	 * This cli example does not have any non-multi section
	 * options, but for completeness, this is a sane way to
	 * handle "static" sections. I.e. filter them out unless
	 * they have at least one sub-option that is not filtered
	 * out.
	 *
	 * A generic solution would have to examine if the section
	 * has its own print filter function and use that, but that
	 * feels out of scope and is left as an exercise for the
	 * reader. If it is needed at some point, a new supporting
	 * API should probably be added to help get it done without
	 * digging in the library guts...
	 */
	sec = cfg_opt_getnsec(opt, 0);
	if (!sec)
		return 1;

	for (i = 0; sec->opts[i].name; i++) {
		if (!print_modified(sec, &sec->opts[i]))
			return 0;
	}
	return 1;
}

static const char *dump(int argc, char *argv[])
{
	if (argc > 2)
		return "Too many args\n";
	if (argc == 2) {
		if (!strcmp(argv[1], "mod") || !strcmp(argv[1], "modified"))
			cfg_set_print_filter_func(cfg, print_modified);
		else
			return "Invalid arg\n";
	}
	else
		cfg_set_print_filter_func(cfg, NULL);

	cfg_print(cfg, stdout);
	return "";
}

static const char *quit(int argc, char *argv[])
{
	return NULL;
}

struct cmd_handler {
	const char *cmd;
	const char *(*handler)(int argc, char *argv[]);
};

static const struct cmd_handler cmds[] = {
	{ "help", help },
	{ "set", set },
	{ "subset", subset },
	{ "create", create },
	{ "destroy", destroy },
	{ "dump", dump },
	{ "quit", quit },
	{ "exit", quit },
	{ NULL, NULL }
};

int main(void)
{
	cfg_opt_t sub_opts[] = {
		CFG_BOOL("bool", cfg_false, CFGF_NONE),
		CFG_STR("string", NULL, CFGF_NONE),
		CFG_INT("int", 0, CFGF_NONE),
		CFG_FLOAT("float", 0.0, CFGF_NONE),
		CFG_END()
	};

	cfg_opt_t opts[] = {
		CFG_BOOL_LIST("bool", cfg_false, CFGF_NONE),
		CFG_STR_LIST("string", NULL, CFGF_NONE),
		CFG_INT_LIST("int", 0, CFGF_NONE),
		CFG_FLOAT_LIST("float", "0.0", CFGF_NONE),
		CFG_SEC("sub", sub_opts,
			CFGF_MULTI | CFGF_TITLE | CFGF_NO_TITLE_DUPES),
		CFG_END()
	};

	char *cmd = NULL;
	const char *reply;
	int res;
	int i;

	cfg = cfg_init(opts, CFGF_NONE);

	for (;;) {
		printf("cli> ");
		fflush(stdout);

		if (cmd)
			free(cmd);
		cmd = input_cmd();
		if (!cmd)
			exit(0);
		res = split_cmd(cmd);
		if (res < 0) {
			printf("Parse error\n");
			continue;
		}
		if (cmdc == 0)
			continue;
		for (i = 0; cmds[i].cmd; ++i) {
			if (strcmp(cmdv[0], cmds[i].cmd))
				continue;
			reply = cmds[i].handler(cmdc, cmdv);
			if (!reply)
				exit(0);
			printf("%s", reply);
			break;
		}
		if (!cmds[i].cmd)
			printf("Unknown command\n");
	}

	cfg_free(cfg);
	return 0;
}

/**
 * Local Variables:
 *  indent-tabs-mode: t
 *  c-file-style: "linux"
 * End:
 */
