/*
 * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
 *
 * Licensed under the LGPL v3, see https://www.gnu.org/licenses/lgpl-3.0.html
 */

#include <inttypes.h>

#define LLS_ABI_VERSION 2

/*
 * Concat "lls_abi_version" and the ABI version number to produce the name of a
 * variable. To concat the *expansion* of the macro arguments, we have to use
 * two levels of macros since otherwise the arguments are not macro-expanded
 * first.
 */
#define LLS_CONCAT_EXPAND(_prefix, _suffix) LLS_CONCAT(_prefix, _suffix)
#define LLS_CONCAT(_prefix, _suffix) _prefix ## _suffix
#define LLS_ABI_VERSION_VAR LLS_CONCAT_EXPAND(lls_abi_version, LLS_ABI_VERSION)

/*
 * Declare the variable. It is defined in lopsub.c, which is part of the
 * library. The single mission of this variable is to cause an error at link
 * time on ABI version mismatches. The actual value of the variable is
 * irrelevant. When the lopsubgen utility translates a suite into a .c file,
 * the generated C code references this variable, using the *lopsubgen* ABI
 * version for the reference. Therefore, if the lopsubgen ABI version differs
 * from the ABI version of the library, the symbol can not be resolved,
 * resulting in a link error.
 */
extern const unsigned LLS_ABI_VERSION_VAR;

/*
 * Options may have an optional or mandatory argument.
 *
 * The lobsubgen command initializes the ->arg_info field of each lls_option
 * structure from the corresponding line of the .suite file.
 *
 * See also: ->arg_info of struct lls_option.
 */
enum lls_arg_info {
	LLS_NO_ARGUMENT, /* Option does not take an argument. */
	LLS_REQUIRED_ARGUMENT, /* An argument must be given. */
	LLS_OPTIONAL_ARGUMENT, /* Option takes an optional argument. */
};

/*
 * In addition to the argument type, there is a set of flags associated with
 * each option. All but the LLS_IGNORED flag correspond directly to the
 * possible values of the flag directive in the suite file.
 */
enum lls_option_flag {
	/* Store each given argument (rather than only the last). */
	LLS_MULTIPLE = 1,
	/* It's an error if the option is not given. */
	LLS_REQUIRED = 2,
	/* Whether a default value was specified in the suite file. */
	LLS_HAS_DEFAULT = 4,
	/* For help text not related to any particular option. */
	LLS_IGNORED = 8,
};

/*
 * Ignored if arg_info is LLS_NO_ARGUMENT. A value of zero means no argument.
 * We don't need an identifier like LLS_NONE though, because we can test the
 * arg_info field to tell whether an argument can be supplied.
 *
 * See also: union lls_val.
 */
enum lls_arg_type {
	/* Option takes a string argument. */
	LLS_STRING = 1,
	/* Signed 32 bit integer. */
	LLS_INT32,
	/* Unsigned 32 bit integer. */
	LLS_UINT32,
	/* Signed 64 bit integer. */
	LLS_INT64,
	/* Unsigned 64 bit integer. */
	LLS_UINT64,
};

/*
 * Stores the argument to an option.
 *
 * The default value of an option, the set of possible values for an option and
 * the actually given value are all stored in an instance of this union.
 *
 * To determine the relevant alternative of the union, both the ->arg_type and
 * the ->values field of the corresponding struct lls_option are
 * important. For the values array itself, ->arg_type always determines the
 * alternative of each element in the array. For other objects the following
 * rule applies. If ->values is not given, ->arg_type determines the
 * alternative for both ->default_val and lls_opt_result->value. On the other
 * hand, if ->values is given, the ->uint32_val alternative is the index into
 * the ->values array that indicates which of the possible values is the
 * default value (for ->default_val) or was given in the arguments to
 * lls_parse() (lls_opt_result).
 */
union lls_val {
	char *string_val; /* LLS_STRING */
	int32_t int32_val; /* LLS_INT32 */
	uint32_t uint32_val; /* LLS_UINT32 */
	int64_t int64_val; /* LLS_INT64 */
	uint64_t uint64_val; /* LLS_UINT64 */
};

/*
 * Describes one option of one command of a suite.
 *
 * For each option of every command in the .suite file, lopsubgen
 * generates one structure of this type.
 */
struct lls_option {
	/* The long name (short-only options are not supported). */
	const char * const name;
	/* Optional one-character variant. */
	const char short_opt;
	/* Mandatory one-line summary. */
	const char * const summary;
	/* Whether the option takes an argument, and if it is mandatory. */
	const enum lls_arg_info arg_info;
	/* Which alternative of the lls_val union is chosen. */
	const enum lls_arg_type arg_type;
	/* Description of the type of values (used for help output). */
	const char * const typestr;
	/* See enum lls_option_flag. */
	const unsigned flags;
	/* If not given, integer values default to 0, strings to NULL. */
	const union lls_val default_val;
	/* Optional multi-line help text. */
	const char * const help;
	/* If this is not NULL, only the given values are allowed. */
	const union lls_val * const values;
};

/* Describes a command of a suite. */
struct lls_command {
	/* Only identifiers are allowed as command names. */
	const char * const name;
	/* One line of text, shown in all variants of help text. */
	const char * const purpose;
	/* Multi line text in long help and man page. */
	const char * const description;
	/* How non-option arguments should be called in the synopsis. */
	const char * const non_opts_name;
	/* Optional, will be computed if not given. */
	const char * const synopsis;
	/* Array of options for this command. */
	const struct lls_option * const options;
	/* Closing remarks after the option list in long help and man page. */
	const char * const closing;
	/* Initialized to com_NAME_user_data with NAME being the command name. */
	const void * const user_data;
	/* Contains the array size of ->options. */
	const int num_options;
};

struct lls_suite {
	const char * const name;
	const char * const caption;
	const struct lls_command * const commands;
	const int num_subcommands;
};

struct lls_opt_result {
	unsigned given;
	/* for multiple options, one value for each time the option was given */
	union lls_val *value;
};

struct lls_parse_result {
	char **inputs; /* non-options, non-args */
	unsigned num_inputs;
	struct lls_opt_result *opt_result; /* one per option */
};

/*
 * Most library functions return a negative error code on failure. The
 * LLS_ERRORS macro expands to a list of all possible errors, optionally
 * including the text of each error code. The LLS_ERROR macro (without the
 * trailing S) controls how each error is expanded.
 */
#define LLS_ERRORS \
	LLS_ERROR(SUCCESS, "success") \
	LLS_ERROR(NOMEM, "allocation failure") \
	LLS_ERROR(BAD_OPTION, "option not recognized") \
	LLS_ERROR(AMBIG_OPTION, "option is ambiguous") \
	LLS_ERROR(OPT_MANDATORY, "mandatory option not given") \
	LLS_ERROR(ARG_GIVEN, "argument given to non-arg option") \
	LLS_ERROR(NO_ARG_GIVEN, "argument required but not given") \
	LLS_ERROR(TRAILING_BACKSLASH, "unexpected trailing backslash") \
	LLS_ERROR(UNMATCHED_QUOTE, "unmatched quote character") \
	LLS_ERROR(TRAILING_GARBAGE, "garbage at end of line or argument") \
	LLS_ERROR(YY_SCAN, "error setting up lex input buffer") \
	LLS_ERROR(YY_LEX, "yylex() failed") \
	LLS_ERROR(BAD_SUBCMD, "invalid subcommand") \
	LLS_ERROR(AMBIG_SUBCMD, "ambiguous subcommand") \
	LLS_ERROR(BAD_ARG_COUNT, "invalid number of arguments") \
	LLS_ERROR(OVERFLOW, "value too large") \
	LLS_ERROR(NO_DIGITS, "no digits found in string") \
	LLS_ERROR(ENUM, "invalid value for enum option") \

/*
 * Given an identifier and a string literal, forget the string, and prefix the
 * identifier with E_LLS_ to produce an identifier for an error code like
 * E_LLS_OVERFLOW. Finally, append a comma. While this definition of LLS_ERROR
 * is active, LLS_ERRORS expands to a comma-separated list of error codes. See
 * enum lls_errors below.
 */
#define LLS_ERROR(_n, _s) E_LLS_ ## _n,

/*
 * Declare the enumeration of all error codes as identifiers like like
 * E_LLS_OVERFLOW. Also, the total number of error codes is declared as
 * NUM_LLS_ERRORS.
 */
enum lls_errors {LLS_ERRORS NUM_LLS_ERRORS};
#undef LLS_ERROR
