/*
 * fhist - file history and comparison tools
 * Copyright (C) 1991-1994, 1998, 2000-2002, 2008, 2010, 2012 Peter Miller
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <common/ac/stddef.h>
#include <common/ac/stdlib.h>
#include <common/ac/string.h>
#include <common/ac/ctype.h>
#include <math.h>
#include <libexplain/malloc.h>

#include <common/arglex.h>
#include <common/cmalloc.h>
#include <common/error_intl.h>
#include <common/language.h>
#include <common/sub.h>


static arglex_table_ty table[] =
{
    { "-",                  arglex_token_stdio,             },
    { "-Help",              arglex_token_help,              },
    { "-VERSion",           arglex_token_version,           },
    { 0, 0, }, /* end marker */
};

static int      argc;
static char     **argv;
arglex_value_ty arglex_value;
int             arglex_token;
static arglex_table_ty *utable;
static const char *partial;


void
arglex_init(int ac, char **av, arglex_table_ty *tp)
{
    language_init();

    argc = ac - 1;
    argv = av + 1;
    utable = tp;
}


int
arglex_compare(const char *formal, const char *actual)
{
    char            fc;
    char            ac;
    int             result;

    for (;;)
    {
        ac = *actual++;
        if (isupper((unsigned char)ac))
                ac = tolower((unsigned char)ac);
        fc = *formal++;
        switch (fc)
        {
        case 0:
            result = !ac;
            goto ret;

        case '_':
            if (ac == '-')
                    break;
            /* fall through... */

        case 'a': case 'b': case 'c': case 'd': case 'e':
        case 'f': case 'g': case 'h': case 'i': case 'j':
        case 'k': case 'l': case 'm': case 'n': case 'o':
        case 'p': case 'q': case 'r': case 's': case 't':
        case 'u': case 'v': case 'w': case 'x': case 'y':
        case 'z':
            /*
             * optional characters
             */
            if (ac == fc && arglex_compare(formal, actual))
            {
                result = 1;
                goto ret;
            }
            /*
             * skip forward to next
             * mandatory character, or after '_'
             */
            while (islower((unsigned char)*formal))
                ++formal;
            if (*formal == '_')
            {
                ++formal;
                if (ac == '_' || ac == '-')
                    ++actual;
            }
            --actual;
            break;

        case '*':
            /*
             * This is a hack, it should really
             * check for a match match the stuff after
             * the '*', too, a la glob.
             */
            if (!ac)
            {
                result = 0;
                goto ret;
            }
            partial = actual - 1;
            result = 1;
            goto ret;

        case '\\':
            if (actual[-1] != *formal++)
            {
                result = 0;
                goto ret;
            }
            break;

        case 'A': case 'B': case 'C': case 'D': case 'E':
        case 'F': case 'G': case 'H': case 'I': case 'J':
        case 'K': case 'L': case 'M': case 'N': case 'O':
        case 'P': case 'Q': case 'R': case 'S': case 'T':
        case 'U': case 'V': case 'W': case 'X': case 'Y':
        case 'Z':
            fc = tolower((unsigned char)fc);
            /* fall through... */

        default:
            /*
             * mandatory characters
             */
            if (fc != ac)
            {
                result = 0;
                goto ret;
            }
            break;
        }
    }
ret:
    return result;
}


static int
is_a_number(const char *s)
{
    char            *ep = 0;

    arglex_value.alv_number = strtol(s, &ep, 0);
    return (ep && s != ep && !*ep);
}


int
arglex(void)
{
    arglex_table_ty *tp;
    int             j;
    arglex_table_ty *hit[20];
    int             nhit;
    static char     *pushback;
    const char      *arg;

    if (pushback)
    {
        /*
         * the second half of a "-foo=bar" style argument.
         */
        arg = pushback;
        pushback = 0;
    }
    else
    {
        if (argc <= 0)
        {
            arglex_token = arglex_token_eoln;
            arg = "";
            goto ret;
        }
        arg = argv[0];
        argc--;
        argv++;

        /*
         * See if it looks like a GNU "-foo=bar" option.
         * Split it at the '=' to make it something the
         * rest of the code understands.
         */
        if (arg[0] == '-' && arg[1] != '=')
        {
            char    *eqp;

            eqp = strchr(arg, '=');
            if (eqp)
            {
                pushback = eqp + 1;
                *eqp = 0;
            }
        }

        /*
         * Turn the GNU-style leading "--"
         * into "-" if necessary.
         */
        if
        (
            arg[0] == '-'
        &&
            arg[1] == '-'
        &&
            arg[2]
        &&
            !is_a_number(arg + 1)
        )
            ++arg;
    }

    if (is_a_number(arg))
    {
        arglex_token = arglex_token_number;
        goto ret;
    }

    nhit = 0;
    partial = 0;
    for (tp = table; tp->name; tp++)
    {
        if (arglex_compare(tp->name, arg))
            hit[nhit++] = tp;
    }
    if (utable)
    {
        for (tp = utable; tp->name; tp++)
        {
            if (arglex_compare(tp->name, arg))
                hit[nhit++] = tp;
        }
    }
    switch (nhit)
    {
    case 0:
        /*
         * not found in the table
         */
        if (arg[0] == '-')
            arglex_token = arglex_token_option;
        else
            arglex_token = arglex_token_string;
        break;

    case 1:
        if (partial)
            arg = partial;
        else
            arg = hit[0]->name;
        arglex_token = hit[0]->token;
        break;

    default:
        {
            size_t              len;
            char                *buf;
            sub_context_ty      *scp;

            len = strlen(hit[0]->name + 1);
            for (j = 1; j < nhit; ++j)
                len += strlen(hit[j]->name) + 2;
            buf = explain_malloc_or_die(len);
            strcpy(buf, hit[0]->name);
            for (j = 1; j < nhit; ++j)
            {
                strcat(buf, ", ");
                strcat(buf, hit[j]->name);
            }

            scp = sub_context_new();
            sub_var_set_charstar(scp, "Name", arg);
            sub_var_set_charstar(scp, "Guess", buf);
            fatal_intl(scp, i18n("option \"$name\" ambiguous ($guess)"));
            /* NOTREACHED */
        }
    }

ret:
    arglex_value.alv_string = arg;
    return arglex_token;
}


const char *
arglex_token_name(int n)
{
    arglex_table_ty *tp;

    switch (n)
    {
    case arglex_token_eoln:
        return "end of command line";

    case arglex_token_number:
        return "number";

    case arglex_token_option:
        return "option";

    case arglex_token_stdio:
        return "standard input or output";

    case arglex_token_string:
        return "string";

    default:
        break;
    }
    for (tp = table; tp < ENDOF(table); tp++)
    {
        if (tp->token == n)
            return tp->name;
    }
    if (utable)
    {
        for (tp = utable; tp->name; tp++)
        {
            if (tp->token == n)
                return tp->name;
        }
    }

    return "unknown command line token";
}


/* vim: set ts=8 sw=4 et : */
