//---------------------------------------------------------------------------//
// ==>/[_]\   fxlink: A community communication tool for CASIO calculators.  //
//    |:::|   Made by Lephe' as part of the fxSDK.                           //
//    \___/   License: MIT <https://opensource.org/licenses/MIT>             //
//---------------------------------------------------------------------------//
// fxlink.tui.command-util: Preprocessor black magic for command definition
//
// This header provides the following method for declaring TUI commands that
// are automatically registered at startup, and are invoked with arguments
// pre-parsed:
//
// FXLINK_COMMAND("<NAME>", <TYPE>(<NAME>), <TYPE>(<NAME>), ...) {
//    /* normal code... */
//    return <STATUS>;
// }
//
// The command name is a string. It can have multiple space-separated words as
// in "gintctl test", in which case it is matched against argv[0], argv[1], etc
// and the prefix ("gintctl") is automatically made into a sub-category command
// with relevant error messages.
//
// Each argument has a type and a name, as in INT(x). The type carries
// information on the parsing method, the acceptable range, and of course the
// actual runtime type of the argument. Available types are:
//
//   Name      Runtime type            Meaning and range
//   --------------------------------------------------------------------------
//   INT       int                     Any integer
//   STRING    char const *            Any string argument from argv[]
//   VARIADIC  char const **           End of the argv array (NULL-terminated)
//   --------------------------------------------------------------------------
//   DEVICE    struct fxlink_device *  Selected device (implicit; never NULL)
//   --------------------------------------------------------------------------
//
// The function returns a status code, which is an integer. The entire command
// declaration might look like:
//
// FXLINK_COMMAND("gintctl test", INT(lower_bound), INT(upper_bound)) {
//     int avg = (lower_bound + upper_bound) / 2;
//     return 0;
// }
//
// I considered doing the entire thing in C++, but absolute preprocessor abuse
// is fun once in a while.
//---

#include <fxlink/defs.h>

//---
// Shell-like command parsing (without the features)
//---

struct fxlink_tui_cmd {
    int argc;
    char const **argv;
    char *data;
};

/* Parse a string into an argument vector */
struct fxlink_tui_cmd fxlink_tui_cmd_parse(char const *input);

/* Dump a command to TUI console for debugging */
void fxlink_tui_cmd_dump(struct fxlink_tui_cmd const *cmd);

/* Free a command */
void fxlink_tui_cmd_free(struct fxlink_tui_cmd const *cmd);

//---
// Command registration and argument scanning
//---

/* Parse a list of arguments into structured data. The format is a string of
   argument specifiers, each of which can be:
     s   String (char *)
     d   Integer (int)
     *   Other variadic arguments (char **)
   (-- will probably be expanded later.)
   Returns true if parsing succeeded, false otherwise (including if arguments
   are missing) after printing an error message. */
bool fxlink_tui_parse_args(int argc, char const **argv, char const *fmt, ...);

/* Register a command with the specified name and invocation function. This can
   be called manually or generated (along with the parser) using the macro
   FXLINK_COMMAND. */
void fxlink_tui_register_cmd(char const *name,
    int (*func)(int argc, char const **argv));

/* Apply a macro to every variadic argument. _M1 is applied to the first
   argument and _Mn is applied to all subsequent arguments. */
#define MAPn(_M1,_Mn,...)             __VA_OPT__(MAP_1(_M1,_Mn,__VA_ARGS__))
#define MAP_1(_M1,_Mn,_X,...) _M1(_X) __VA_OPT__(MAP_2(_M1,_Mn,__VA_ARGS__))
#define MAP_2(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_3(_M1,_Mn,__VA_ARGS__))
#define MAP_3(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_4(_M1,_Mn,__VA_ARGS__))
#define MAP_4(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_5(_M1,_Mn,__VA_ARGS__))
#define MAP_5(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_6(_M1,_Mn,__VA_ARGS__))
#define MAP_6(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_7(_M1,_Mn,__VA_ARGS__))
#define MAP_7(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_8(_M1,_Mn,__VA_ARGS__))
#define MAP_8(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_MAX_8_ARGS(_))
#define MAP_MAX_8_ARGS()
/* Simpler version where the same macro is applied to all arguments */
#define MAP(_M, ...) MAPn(_M, _M, ##__VA_ARGS__)

/* Command declaration macro. Builds an invocation function and a registration
   function so the command name doesn't have to be repeated. */
#define FXLINK_COMMAND(_NAME, ...) DO_COMMAND1(_NAME, __COUNTER__, __VA_ARGS__)
/* This call forces __COUNTER__ to expand */
#define DO_COMMAND1(...) DO_COMMAND(__VA_ARGS__)

#define DO_COMMAND(_NAME, _COUNTER, ...) \
    static int ___command_ ## _COUNTER(); \
    static int ___invoke_command_ ## _COUNTER \
            (int ___argc, char const **___argv) { \
        MAP(MKVAR, ##__VA_ARGS__) \
        if(!fxlink_tui_parse_args(___argc, ___argv, \
            "" MAP(MKFMT, ##__VA_ARGS__) \
            MAP(MKPTR, ##__VA_ARGS__))) return 1; \
        return ___command_ ## _COUNTER( \
            MAPn(MKCALL_1, MKCALL_n, ##__VA_ARGS__)); \
    } \
    __attribute__((constructor)) \
    static void ___declare_command_ ## _COUNTER (void) { \
        fxlink_tui_register_cmd(_NAME, ___invoke_command_ ## _COUNTER); \
    } \
    static int ___command_ ## _COUNTER(MAPn(MKFML_1, MKFML_n, ##__VA_ARGS__))

/* Make the format string for an argument */
#define MKFMT(_TV) MKFMT_ ## _TV
#define MKFMT_INT(_X) "i"
#define MKFMT_STRING(_X) "s"
#define MKFMT_VARIADIC(_X) "*"
#define MKFMT_DEVICE(_X) "d"

/* Make the formal function parameter for an argument */
#define MKFML_1(_TV) MKFML_ ## _TV
#define MKFML_n(_TV) , MKFML_1(_TV)
#define MKFML_INT(_X) int _X
#define MKFML_STRING(_X) char const * _X
#define MKFML_VARIADIC(_X) char const ** _X
#define MKFML_DEVICE(_X) struct fxlink_device * _X

/* Create a variable */
#define MKVAR(_TV) MKFML_1(_TV);

/* Make a pointer to an argument (sadly we can't get the name directly) */
#define MKPTR(_TV) , MKPTR_ ## _TV
#define MKPTR_INT(_X) &_X
#define MKPTR_STRING(_X) &_X
#define MKPTR_VARIADIC(_X) &_X
#define MKPTR_DEVICE(_X) &_X

/* Pass a variable as a function argument */
#define MKCALL_1(_TV) MKCALL_ ## _TV
#define MKCALL_n(_TV) , MKCALL_1(_TV)
#define MKCALL_INT(_X) _X
#define MKCALL_STRING(_X) _X
#define MKCALL_VARIADIC(_X) _X
#define MKCALL_DEVICE(_X) _X