/*
 * Helper function for matching command-line options in the Windows
 * auxiliary tools (PuTTYgen and Pageant).
 *
 * Supports basically the usual kinds of option, including GNUish
 * --foo long options and simple -f short options. But historically
 * those tools have also supported long options with a single dash, so
 * we don't go full GNU and report those as syntax errors.
 */

#include "putty.h"

/*
 * Call this to initialise the state structure.
 */
AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index,
                               aux_opt_error_fn_t opt_error)
{
    AuxMatchOpt amo[1];

    amo->argc = argc;
    amo->argv = argv;
    amo->index = start_index;
    amo->doing_opts = true;
    amo->error = opt_error;

    return amo[0];
}

/*
 * Call this with a NULL-terminated list of synonymous option names.
 * Point 'val' at a char * to receive the option argument, if one is
 * expected. Set 'val' to NULL if no argument is expected.
 */
bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...)
{
    assert(amo->index < amo->argc);

    /* Find the end of the option name */
    char *opt = amo->argv[amo->index];
    ptrlen argopt;
    argopt.ptr = opt;
    argopt.len = strcspn(opt, "=");

    /* Normalise GNU-style --foo long options to the -foo that this
     * tool has historically used */
    ptrlen argopt2 = make_ptrlen(NULL, 0);
    if (ptrlen_startswith(argopt, PTRLEN_LITERAL("--"), NULL))
        ptrlen_startswith(argopt, PTRLEN_LITERAL("-"), &argopt2);

    /* See if this option matches any of our synonyms */
    va_list ap;
    va_start(ap, optname);
    bool matched = false;
    while (optname) {
        if (ptrlen_eq_string(argopt, optname)) {
            matched = true;
            break;
        }
        if (argopt2.ptr && strlen(optname) > 2 &&
            ptrlen_eq_string(argopt2, optname)) {
            matched = true;
            break;
        }
        optname = va_arg(ap, const char *);
    }
    va_end(ap);
    if (!matched)
        return false;

    /* Check for a value */
    if (opt[argopt.len]) {
        if (!val)
            amo->error("option '%s' does not expect a value", opt);
        *val = opt + argopt.len + 1;
        amo->index++;
    } else if (!val) {
        amo->index++;
    } else {
        if (amo->index + 1 >= amo->argc)
            amo->error("option '%s' expects a value", opt);
        *val = amo->argv[amo->index + 1];
        amo->index += 2;
    }

    return true;
}

/*
 * Call this to return a non-option argument in *val.
 */
bool aux_match_arg(AuxMatchOpt *amo, char **val)
{
    assert(amo->index < amo->argc);
    char *opt = amo->argv[amo->index];

    if (!amo->doing_opts || opt[0] != '-' || !strcmp(opt, "-")) {
        *val = opt;
        amo->index++;
        return true;
    }

    return false;
}

/*
 * And call this to test whether we're done. Also handles '--'.
 */
bool aux_match_done(AuxMatchOpt *amo)
{
    if (amo->index < amo->argc && !strcmp(amo->argv[amo->index], "--")) {
        amo->doing_opts = false;
        amo->index++;
    }

    return amo->index >= amo->argc;
}
