// wild.cpp -- Wildcard routines.
//
// $Id: wild.cpp 8 2006-09-05 01:55:58Z brazilofmux $
//
// Written by T. Alexander Popiel, 24 June 1993
// Last modified by T. Alexander Popiel, 19 August 1993
//
// Thanks go to Andrew Molitor for debugging
// Thanks also go to Rich $alz for code to benchmark against
//
// Copyright (c) 1993 by T. Alexander Popiel
// This code is hereby placed under GNU copyleft,
// see copyright.h for details.
//

#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"

#define EQUAL(a,b) (mux_tolower(a) == mux_tolower(b))
#define NOTEQUAL(a,b) (mux_tolower(a) != mux_tolower(b))

// Argument return space and size.
//
static char **arglist;
static int numargs;

//
// ---------------------------------------------------------------------------
// quick_wild: do a wildcard match, without remembering the wild data.
//
// This routine will cause crashes if fed NULLs instead of strings.
//
bool quick_wild(const char *tstr, const char *dstr)
{
    if (mudstate.wild_invk_ctr >= mudconf.wild_invk_lim)
    {
        return false;
    }
    mudstate.wild_invk_ctr++;

    while (*tstr != '*')
    {
        switch (*tstr)
        {
        case '?':

            // Single character match.  Return false if at end of data.
            //
            if (!*dstr)
            {
                return false;
            }
            break;

        case '\\':

            // Escape character.  Move up, and force literal match of next
            // character.
            //
            tstr++;

            // FALL THROUGH

        default:

            // Literal character.  Check for a match. If matching end of data,
            // return true.
            //
            if (NOTEQUAL(*dstr, *tstr))
            {
                return false;
            }
            if (!*dstr)
            {
                return true;
            }
        }
        tstr++;
        dstr++;
    }

    // Skip over '*'.
    //
    tstr++;

    // Return true on trailing '*'.
    //
    if (!*tstr)
    {
        return true;
    }

    // Skip over wildcards.
    //
    while (  *tstr == '?'
          || *tstr == '*')
    {
        if (*tstr == '?')
        {
            if (!*dstr)
            {
                return false;
            }
            dstr++;
        }
        tstr++;
    }

    // Skip over a backslash in the pattern string if it is there.
    //
    if (*tstr == '\\')
    {
        tstr++;
    }

    // Return true on trailing '*'.
    //
    if (!*tstr)
    {
        return true;
    }

    // Scan for possible matches.
    //
    while (*dstr)
    {
        if (  EQUAL(*dstr, *tstr)
           && quick_wild(tstr + 1, dstr + 1))
        {
            return true;
        }
        dstr++;
    }
    return false;
}

// ---------------------------------------------------------------------------
// wild1: INTERNAL: do a wildcard match, remembering the wild data.
//
// DO NOT CALL THIS FUNCTION DIRECTLY - DOING SO MAY RESULT IN SERVER CRASHES
// AND IMPROPER ARGUMENT RETURN.
//
// Side Effect: this routine modifies the 'arglist' static global variable.
//
static bool wild1(char *tstr, char *dstr, int arg)
{
    if (mudstate.wild_invk_ctr >= mudconf.wild_invk_lim)
    {
        return false;
    }
    mudstate.wild_invk_ctr++;

    char *datapos;
    int argpos, numextra;

    while (*tstr != '*')
    {
        switch (*tstr)
        {
        case '?':

            // Single character match.  Return false if at end of data.
            //
            if (!*dstr)
            {
                return false;
            }
            arglist[arg][0] = *dstr;
            arglist[arg][1] = '\0';
            arg++;

            // Jump to the fast routine if we can.
            //
            if (arg >= numargs)
            {
                return quick_wild(tstr + 1, dstr + 1);
            }
            break;

        case '\\':

            // Escape character.  Move up, and force literal match of next
            // character.
            //
            tstr++;

            // FALL THROUGH

        default:

            // Literal character.  Check for a match. If matching end of data,
            // return true.
            //
            if (NOTEQUAL(*dstr, *tstr))
            {
                return false;
            }
            if (!*dstr)
            {
                return true;
            }
        }
        tstr++;
        dstr++;
    }

    // If at end of pattern, slurp the rest, and leave.
    //
    if (!tstr[1])
    {
        mux_strncpy(arglist[arg], dstr, LBUF_SIZE - 1);
        return true;
    }

    // Remember current position for filling in the '*' return.
    //
    datapos = dstr;
    argpos = arg;

    // Scan forward until we find a non-wildcard.
    //
    do
    {
        if (argpos < arg)
        {
            // Fill in arguments if someone put another '*' before a fixed
            // string.
            //
            arglist[argpos][0] = '\0';
            argpos++;

            // Jump to the fast routine if we can.
            //
            if (argpos >= numargs)
            {
                return quick_wild(tstr, dstr);
            }

            // Fill in any intervening '?'s
            //
            while (argpos < arg)
            {
                arglist[argpos][0] = *datapos;
                arglist[argpos][1] = '\0';
                datapos++;
                argpos++;

                // Jump to the fast routine if we can.
                //
                if (argpos >= numargs)
                {
                    return quick_wild(tstr, dstr);
                }
            }
        }

        // Skip over the '*' for now...
        //
        tstr++;
        arg++;

        // Skip over '?'s for now...
        //
        numextra = 0;
        while (*tstr == '?')
        {
            if (!*dstr)
            {
                return false;
            }
            tstr++;
            dstr++;
            arg++;
            numextra++;
        }
    } while (*tstr == '*');

    // Skip over a backslash in the pattern string if it is there.
    //
    if (*tstr == '\\')
    {
        tstr++;
    }

    // Check for possible matches.  This loop terminates either at end of data
    // (resulting in failure), or at a successful match.
    //
    for (;;)
    {
        // Scan forward until first character matches.
        //
        if (*tstr)
        {
            while (NOTEQUAL(*dstr, *tstr))
            {
                if (!*dstr)
                {
                    return false;
                }
                dstr++;
            }
        }
        else
        {
            while (*dstr)
            {
                dstr++;
            }
        }

        // The first character matches, now.  Check if the rest does, using
        // the fastest method, as usual.
        //
        if (  !*dstr
           || ((arg < numargs) ? wild1(tstr + 1, dstr + 1, arg)
                               : quick_wild(tstr + 1, dstr + 1)))
        {
            // Found a match!  Fill in all remaining arguments. First do the
            // '*'...
            //
            mux_strncpy(arglist[argpos], datapos,
                            (dstr - datapos) - numextra);
            datapos = dstr - numextra;
            argpos++;

            // Fill in any trailing '?'s that are left.
            //
            while (numextra)
            {
                if (argpos >= numargs)
                {
                    return true;
                }
                arglist[argpos][0] = *datapos;
                arglist[argpos][1] = '\0';
                datapos++;
                argpos++;
                numextra--;
            }

            // It's done!
            //
            return true;
        }
        else
        {
            dstr++;
        }
    }
}

// ---------------------------------------------------------------------------
// wild: do a wildcard match, remembering the wild data.
//
// This routine will cause crashes if fed NULLs instead of strings.
//
// Side Effect: this routine modifies the 'arglist' and 'numargs' static
// global variables.
//
bool wild(char *tstr, char *dstr, char *args[], int nargs)
{
    mudstate.wild_invk_ctr = 0;

    int i;
    char *scan;

    // Initialize the return array.
    //
    for (i = 0; i < nargs; i++)
    {
        args[i] = NULL;
    }

    // Do fast match.
    //
    while (  *tstr != '*'
          && *tstr != '?')
    {
        if (*tstr == '\\')
        {
            tstr++;
        }
        if (NOTEQUAL(*dstr, *tstr))
        {
            return false;
        }
        if (!*dstr)
        {
            return true;
        }
        tstr++;
        dstr++;
    }

    // Allocate space for the return args.
    //
    i = 0;
    scan = tstr;
    while (  *scan
          && i < nargs)
    {
        switch (*scan)
        {
        case '?':

            args[i] = alloc_lbuf("wild.?");
            i++;
            break;

        case '*':

            args[i] = alloc_lbuf("wild.*");
            i++;
        }
        scan++;
    }

    // Put stuff in globals for quick recursion.
    //
    arglist = args;
    numargs = nargs;

    // Do the match.
    //
    bool value = nargs ? wild1(tstr, dstr, 0) : quick_wild(tstr, dstr);

    // Clean out any fake match data left by wild1.
    //
    for (i = 0; i < nargs; i++)
    {
        if (  args[i] != NULL
           && (  !*args[i]
              || !value))
        {
            free_lbuf(args[i]);
            args[i] = NULL;
        }
    }
    return value;
}

// ---------------------------------------------------------------------------
// wild_match: do either an order comparison or a wildcard match, remembering
// the wild data, if wildcard match is done.
//
// This routine will cause crashes if fed NULLs instead of strings.
//
bool wild_match(char *tstr, const char *dstr)
{
    switch (*tstr)
    {
    case '>':

        tstr++;
        if (  mux_isdigit(*tstr)
           || *tstr == '-')
        {
            long lt = mux_atol(tstr);
            long ld = mux_atol(dstr);
            return (lt < ld);
        }
        else
        {
            return (strcmp(tstr, dstr) < 0);
        }

    case '<':

        tstr++;
        if (  mux_isdigit(*tstr)
           || *tstr == '-')
        {
            long lt = mux_atol(tstr);
            long ld = mux_atol(dstr);
            return (lt > ld);
        }
        else
        {
            return (strcmp(tstr, dstr) > 0);
        }
    }
    mudstate.wild_invk_ctr = 0;
    return quick_wild(tstr, dstr);
}
