/*
 * Worldvisions Weaver Software:
 *   Copyright (C) 1997-2004 Net Integration Technologies, Inc.
 * 
 * Implementation of globbing support through WvRegex
 */
#include "wvglob.h"

WvGlob::WvGlob() : WvRegex()
{
}

WvGlob::WvGlob(WvStringParm glob) : WvRegex()
{
    set(glob);
}

bool WvGlob::set(WvStringParm glob)
{
    WvString errstr;
    WvString regex = glob_to_regex(glob, &errstr);
    if (!!errstr)
        WvErrorBase::seterr(errstr);
    else if (!!regex)
        WvRegex::set(regex);
    else WvErrorBase::seterr("Failed to convert glob pattern to regex");
    return isok();
}

const bool WvGlob::normal_quit_chars[256] = {
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false
};
const bool WvGlob::brace_quit_chars[256] = {
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, true /* , */, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, true /* } */, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false
};

//
// Known bugs:
//
// - If / is part of a range it will not be excluded in the resulting regex
//   eg. fred[.-0]joe will match fred/joe (this violates glob(7))
//   However, explcit / in bracket expression results in error.
//
WvString WvGlob::glob_to_regex(const char *src, size_t &src_used,
        char *dst, size_t &dst_used, const bool quit_chars[256])
{
    enum { NORMAL, BACKSLASH, BRACKET, BRACKET_FIRST } state = NORMAL;
    src_used = 0;
    dst_used = 0;
    bool quit_now = false;
    while (!quit_now && src[src_used])
    {
        switch (state)
        {
            case NORMAL:
                if (quit_chars[(unsigned char)src[src_used]])
                {
                    quit_now = true;
                    break;
                }

                switch (src[src_used])
                {
                    case '\\':
                        state = BACKSLASH;
                        break;

                    case '[':
                        if (src[src_used+1] == '^' && src[src_used+2] == ']')
                        {
                            // Get rid of degenerate case:
                            src_used += 2;
                            if (dst) dst[dst_used] = '\\'; ++dst_used;
                            if (dst) dst[dst_used] = '^'; ++dst_used;
                        }
                        else
                        {
                            if (dst) dst[dst_used] = '('; ++dst_used;
                            state = BRACKET_FIRST;
                        }
                        break;

                    case '*':
                        if (dst) dst[dst_used] = '('; ++dst_used;
                        if (dst) dst[dst_used] = '['; ++dst_used;
                        if (dst) dst[dst_used] = '^'; ++dst_used;
                        if (dst) dst[dst_used] = '/'; ++dst_used;
                        if (dst) dst[dst_used] = ']'; ++dst_used;
                        if (dst) dst[dst_used] = '*'; ++dst_used;
                        if (dst) dst[dst_used] = ')'; ++dst_used;
                        break;

                    case '?':
                        if (dst) dst[dst_used] = '('; ++dst_used;
                        if (dst) dst[dst_used] = '['; ++dst_used;
                        if (dst) dst[dst_used] = '^'; ++dst_used;
                        if (dst) dst[dst_used] = '/'; ++dst_used;
                        if (dst) dst[dst_used] = ']'; ++dst_used;
                        if (dst) dst[dst_used] = ')'; ++dst_used;
                        break;

                    case '{':
                        if (dst) dst[dst_used] = '('; ++dst_used;
                        ++src_used;
                        while (true)
                        {
                            size_t sub_src_used, sub_dst_used;

                            WvString errstr =
                                glob_to_regex(&src[src_used], sub_src_used,
                                    dst? &dst[dst_used]: NULL, sub_dst_used,
                                    brace_quit_chars);
                            if (errstr) return errstr;

                            src_used += sub_src_used;
                            dst_used += sub_dst_used;

                            if (src[src_used] == '}')
                                break;
                            else if (src[src_used] != ',')
                                return WvString("Unfinished brace expression (index %s)", src_used);
                            if (dst) dst[dst_used] = '|'; ++dst_used;
                            ++src_used;
                        }
                        if (dst) dst[dst_used] = ')'; ++dst_used;
                        break;

                    case '^':
                    case '.':
                    case '$':
                    case '(':
                    case ')':
                    case '|':
                    case '+':
                        if (dst) dst[dst_used] = '\\'; ++dst_used;
                        if (dst) dst[dst_used] = src[src_used]; ++dst_used;
                        break;

                    default:
                        if (dst) dst[dst_used] = src[src_used]; ++dst_used;
                        break;
                }
                break;

            case BACKSLASH:
                switch (src[src_used])
                {
                    case '^':
                    case '.':
                    case '$':
                    case '(':
                    case ')':
                    case '|':
                    case '+':
                    case '[':
                    case '{':
                    case '*':
                    case '?':
                    case '\\':
                        if (dst) dst[dst_used] = '\\'; ++dst_used;
                        // fall through..
                    default:
                        if (dst) dst[dst_used] = src[src_used]; ++dst_used;
                        break;

                }
                state = NORMAL;
                break;

            case BRACKET_FIRST:
                switch (src[src_used])
                {
                    case '!':
                        if (dst) dst[dst_used] = '['; ++dst_used;
                        if (dst) dst[dst_used] = '^'; ++dst_used;
                        break;

                    case '^':
                        if (dst) dst[dst_used] = '\\'; ++dst_used;
                        if (dst) dst[dst_used] = '^'; ++dst_used;
                        if (dst) dst[dst_used] = '|'; ++dst_used;
                        if (dst) dst[dst_used] = '['; ++dst_used;
                        break;

                    case '/':
                        return WvString("Slash not allowed in bracket expression (index %s)", src_used);

                    default:
                        if (dst) dst[dst_used] = '['; ++dst_used;
                        if (dst) dst[dst_used] = src[src_used]; ++dst_used;
                        break;
                }
                state = BRACKET;
                break;

            case BRACKET:
                switch (src[src_used])
                {
                    case ']':
                        if (dst) dst[dst_used] = ']'; ++dst_used;
                        if (dst) dst[dst_used] = ')'; ++dst_used;
                        state = NORMAL;
                        break;

                    case '/':
                        return WvString("Slash not allowed in bracket expression (index %s)", src_used);

                    default:
                        if (dst) dst[dst_used] = src[src_used]; ++dst_used;
                        break;
                }
                break;
        }

        if (!quit_now) ++src_used;
    }

    if (state == BRACKET || state == BRACKET_FIRST)
        return WvString("Unfinished bracket expression (index %s)", src_used);
    else if (state == BACKSLASH)
        return WvString("Unfinished backslash expression (index %s)", src_used);
    else return WvString::null;
}

WvString WvGlob::glob_to_regex(WvStringParm glob, WvString *errstr)
{
    if (glob.isnull())
    {
        if (errstr) *errstr = WvString("Glob is NULL");
        return WvString::null;
    }

    size_t src_used, dst_used;
    WvString local_errstr = glob_to_regex(glob, src_used, NULL, dst_used, normal_quit_chars);
    if (!!local_errstr)
    {
        if (errstr) *errstr = local_errstr;
        return WvString::null;
    }
    
    WvString result;
    result.setsize(1+dst_used+1+1);
    char *dst = result.edit();
    *dst++ = '^';
    local_errstr = glob_to_regex(glob, src_used, dst, dst_used, normal_quit_chars);
    if (!!local_errstr)
    {
        if (errstr) *errstr = local_errstr;
        return WvString::null;
    }
    dst += dst_used;
    *dst++ = '$';
    *dst++ = '\0';

    return result;
}
