/*
 * Written 2016 by Andre Noll <maan@tuebingen.mpg.de>
 *
 * Public domain, no copyright claims.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdbool.h>

#include "lopsub.h"
#include "lopsubex.lsg.h"

typedef int (*example_handler_t)(struct lls_parse_result *);

struct local_command_info {
	example_handler_t handler;
};

#define EXPORT_CMD(_cmd) \
	const struct local_command_info lsg_lopsubex_com_ ## _cmd ## _user_data = { \
		.handler = com_ ## _cmd \
	};

#define CMD_PTR(_cmd) lls_cmd(LSG_LOPSUBEX_CMD_ ## _cmd, lopsubex_suite)
#define OPT_PTR(_cmd, _opt) \
	lls_opt(LSG_LOPSUBEX_ ## _cmd ## _OPT_ ## _opt, CMD_PTR(_cmd))

#define OPT_RESULT(_cmd, _opt, _lpr) \
	lls_opt_result(LSG_LOPSUBEX_ ## _cmd ## _OPT_ ## _opt, _lpr)

static void print_available_commands(void)
{
	const struct lls_command *cmd;
	int i;
	printf("Available subcommands:\n");
	for (i = 1; (cmd = lls_cmd(i, lopsubex_suite)); i++) {
		const char *name = lls_command_name(cmd);
		const char *purpose = lls_purpose(cmd);
		printf("%-20s%s\n", name, purpose);
	}
}

static const struct lls_command *lookup_subcmd_or_die(const char *str)
{
	char *errctx;
	int ret = lls_lookup_subcmd(str, lopsubex_suite, &errctx);

	if (ret < 0) {
		if (errctx)
			printf("%s: ", errctx);
		printf("%s\n", lls_strerror(-ret));
		print_available_commands();
		exit(EXIT_FAILURE);
	}
	return lls_cmd(ret, lopsubex_suite);
}

static int com_flag(struct lls_parse_result *lpr)
{
	const struct lls_opt_result *r = OPT_RESULT(FLAG, SIMPLE, lpr);

	/* flag count is obtained with lls_given() */
	printf("--simple is given %u times\n", lls_opt_given(r));

	r = OPT_RESULT(FLAG, SQRT4, lpr);
	printf("--sqrt4 (aka -2) is given %u times\n", lls_opt_given(r));
	return 0;
}
EXPORT_CMD(flag)

static int com_int_param(struct lls_parse_result *lpr)
{
	const struct lls_opt_result *r_r = OPT_RESULT(INT_PARAM, ROTATE, lpr);
	printf("rotating by %d degrees\n", lls_int32_val(0, r_r));
	return 0;
}
EXPORT_CMD(int_param)

static int com_multiple(struct lls_parse_result *lpr)
{
	const struct lls_opt_result *r;
	int i;
	unsigned given;

	r = OPT_RESULT(MULTIPLE, VERBOSE, lpr);
	printf("--verbose is given %d times\n", lls_opt_given(r));
	r = OPT_RESULT(MULTIPLE, INPUT_FILE, lpr);
	given = lls_opt_given(r);
	printf("--input-file is given %d times\n", lls_opt_given(r));
	for (i = 0; i < given; i++)
		printf("--input-file val #%d: %s\n", i, lls_string_val(i, r));
	r = OPT_RESULT(MULTIPLE, OUTPUT_FILE, lpr);
	printf("--output-file is given %d times\n", lls_opt_given(r));
	printf("--output-file val: %s\n", lls_string_val(0, r));
	return 0;
}
EXPORT_CMD(multiple)

static int com_custom_synopsis(struct lls_parse_result *lpr)
{
	const struct lls_command *cmd = CMD_PTR(CUSTOM_SYNOPSIS);
	char *long_help = lls_long_help(cmd);
	printf("%s\n", long_help);
	free(long_help);
	return 0;
}
EXPORT_CMD(custom_synopsis)

static void fruit_salad(struct lls_parse_result *lpr)
{
	const struct lls_opt_result *r_f = OPT_RESULT(SERIALIZE, FRUIT, lpr);
	const struct lls_opt_result *r_a = OPT_RESULT(SERIALIZE, AMOUNT, lpr);
	const struct lls_opt_result *r_s = OPT_RESULT(SERIALIZE, SUGAR, lpr);
	const struct lls_opt_result *r_c = OPT_RESULT(SERIALIZE, CONTAINER, lpr);
	int i, num_vals;

	printf("Put %d gramms of fruits (", lls_uint32_val(0, r_a));
	num_vals = lls_opt_given(r_f) > 0? lls_opt_given(r_f) : 1;
	for (i = 0; i < num_vals; i++)
		printf("%s%s", lls_string_val(i, r_f),
			i == num_vals - 1? "" : ", ");
	printf(") in a %s, ", lls_string_val(0, r_c));
	if (lls_opt_given(r_s))
		printf("add sugar, ");
	printf("and serve cold.\n");
}

static int com_serialize(struct lls_parse_result *lpr)
{
	const struct lls_command *cmd = CMD_PTR(SERIALIZE);
	char *buf = NULL;
	int ret;
	size_t nbytes;
	struct lls_parse_result *dlpr; /* deserialized */

	fruit_salad(lpr);
	ret = lls_serialize_parse_result(lpr, cmd, &buf, &nbytes);
	if (ret < 0)
		return ret;
	printf("serialized parse result into %zu byte buffer\n", nbytes);
	ret = lls_deserialize_parse_result(buf, cmd, &dlpr);
	free(buf);
	if (ret < 0)
		return ret;
	printf("successfully deserialized parse result\n");
	fruit_salad(dlpr);
	lls_free_parse_result(dlpr, cmd);
	return 1;
}
EXPORT_CMD(serialize)

static int com_non_ascii(struct lls_parse_result *lpr)
{
	const struct lls_opt_result *r_c = OPT_RESULT(NON_ASCII, CITY, lpr);
	const char *city = lls_string_val(0, r_c);

	if (strcmp(city, "Göttingen"))
		printf("I don't know anything about %s\n", city);
	else
		printf("One of the most famous citicens of Göttingen was\n"
			"the mathematician Carl Friedrich Gauß.\n");
	exit(EXIT_SUCCESS);
}
EXPORT_CMD(non_ascii)

static int com_enum(struct lls_parse_result *lpr)
{
	const struct lls_option *o_c = OPT_PTR(ENUM, COLOR);
	const struct lls_opt_result *r_c = OPT_RESULT(ENUM, COLOR, lpr);
	bool c_given = lls_opt_given(r_c);
	uint32_t num;
	const char *color;

	num = lls_uint32_val(0, r_c);
	color = lls_enum_string_val(num, o_c);
	printf("%s value: #%d: %s\n", c_given? "good" : "default", num, color);
	printf("Band names containing '%s'\n", color);
	switch (num) {
	case COLOR_RED:
		printf("Red Snapper\n");
		printf("Red Lorry Yellow Lorry\n");
		break;
	case COLOR_GREEN:
		printf("Green Day\n");
		printf("Green Jelly\n");
		break;
	case COLOR_BLUE:
		printf("Blue Cheer\n");
		printf("Blue Öyster Cult\n");
		break;
	default:
		printf("Nothing appropriate\n");
	}
	if (!c_given) {
		printf("Available colors:\n");
		for (num = 0; num < LSG_NUM_LOPSUBEX_ENUM_COLOR_VALUES; num++)
			printf("color #%d: %s\n", num,
				lls_enum_string_val(num, o_c));
	}
	return 1;
}
EXPORT_CMD(enum)

static int com_quotes(struct lls_parse_result *lpr)
{
	const struct lls_opt_result *r_s = OPT_RESULT(QUOTES, CHARS, lpr);
	const char *val= lls_string_val(0, r_s);

	printf("Special characters: %s\n", val);
	return 0;
}
EXPORT_CMD(quotes)

static int com_help(struct lls_parse_result *lpr)
{
	const struct lls_command *cmd = CMD_PTR(HELP);
	const struct lls_opt_result *r_l = OPT_RESULT(HELP, LONG, lpr);
	char *txt;
	int ret;

	ret = lls_check_arg_count(lpr, 0, 1, NULL);
	if (ret < 0)
		return ret;
	if (lls_num_inputs(lpr) > 0)
		cmd = lookup_subcmd_or_die(lls_input(0, lpr));
	if (lls_opt_given(r_l))
		txt = lls_long_help(cmd);
	else
		txt = lls_short_help(cmd);
	printf("%s", txt);
	free(txt);
	return 0;
}
EXPORT_CMD(help)

static int com_default_val(struct lls_parse_result *lpr)
{
	const struct lls_opt_result *r;
	uint32_t val1, val2;
	const char *txt1, *txt2;

	r = OPT_RESULT(DEFAULT_VAL, WIDTH, lpr);
	val1 = lls_uint32_val(0, r);
	r = OPT_RESULT(DEFAULT_VAL, HEIGHT, lpr);
	val2 = lls_uint32_val(0, r);
	printf("geometry: %" PRIu32 "x%" PRIu32 "\n", val1, val2);

	r = OPT_RESULT(DEFAULT_VAL, TOWN, lpr);
	txt1 = lls_string_val(0, r);
	r = OPT_RESULT(DEFAULT_VAL, STREET, lpr);
	txt2 = lls_string_val(0, r);
	printf("address: %s, %s\n", txt2, txt1);

	r = OPT_RESULT(DEFAULT_VAL, TIME, lpr);
	val1 = lls_uint32_val(0, r);
	txt1 = lls_enum_string_val(val1, OPT_PTR(DEFAULT_VAL, TIME));
	r = OPT_RESULT(DEFAULT_VAL, WEEKDAY, lpr);
	val2 = lls_uint32_val(0, r);
	txt2 = lls_enum_string_val(val2, OPT_PTR(DEFAULT_VAL, WEEKDAY));
	printf("when: %s %s\n", txt2, txt1);

	return 0;
}
EXPORT_CMD(default_val)

static int com_optional_arg(struct lls_parse_result *lpr)
{
	const struct lls_opt_result *r;

	r = OPT_RESULT(OPTIONAL_ARG, WIDTH, lpr);

	printf("width: %u (%u times given)\n", lls_uint32_val(0, r),
		lls_opt_given(r));
	r = OPT_RESULT(OPTIONAL_ARG, HEIGHT, lpr);
	printf("height: %u (%u times given)\n", lls_uint32_val(0, r),
		lls_opt_given(r));
	printf("%u non-option arguments\n", lls_num_inputs(lpr));
	return 0;
}
EXPORT_CMD(optional_arg)

/* stringify the first argument (author information) */
#define LOPSUBEX_AUX_INFO(_author, _perms) #_author,
static const char * const authors[] = {LSG_LOPSUBEX_AUX_INFOS};
#undef LOPSUBEX_AUX_INFO
#define LOPSUBEX_AUX_INFO(_author, _perms) _perms,
static const mode_t permissions[] = {LSG_LOPSUBEX_AUX_INFOS};
static int com_aux_info(struct lls_parse_result *lpr)
{
	const struct lls_command *cmd;
	int i;

	for (i = 0; (cmd = lls_cmd(i, lopsubex_suite)); i++) {
		const char *name = lls_command_name(cmd);
		printf("%s: ", name);
		printf("author: %s, permissions: %o\n", authors[i],
			permissions[i]);
	}
	return 0;
}
EXPORT_CMD(aux_info)

int main(int argc, char **argv)
{
	int ret;
	const struct lls_command *cmd;
	struct lls_parse_result *lpr;
	const struct local_command_info *lci;
	char *errctx;

	if (argc <= 1) {
		printf("Usage: %s <subcommand> [options]\n", argv[0]);
		print_available_commands();
		exit(EXIT_FAILURE);
	}
	cmd = lookup_subcmd_or_die(argv[1]);
	ret = lls_parse(argc - 1, argv + 1, cmd, &lpr, &errctx);
	if (ret < 0) {
		printf("%s: %s\n", errctx, lls_strerror(-ret));
		free(errctx);
		exit(EXIT_FAILURE);
	}
	lci = lls_user_data(cmd);
	ret = lci->handler(lpr);
	lls_free_parse_result(lpr, cmd);
	exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
}
