/*
 * Copyright (C) 1997-2004, Michael Jennings
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

static const char __attribute__((unused)) cvs_ident[] = "$Id: regexp.c,v 1.15 2004/07/23 21:38:39 mej Exp $";

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <libast_internal.h>

/* *INDENT-OFF* */
static SPIF_CONST_TYPE(class) r_class = {
    SPIF_DECL_CLASSNAME(regexp),
    (spif_func_t) spif_regexp_new,
    (spif_func_t) spif_regexp_init,
    (spif_func_t) spif_regexp_done,
    (spif_func_t) spif_regexp_del,
    (spif_func_t) spif_regexp_show,
    (spif_func_t) spif_regexp_comp,
    (spif_func_t) spif_regexp_dup,
    (spif_func_t) spif_regexp_type
};
SPIF_TYPE(class) SPIF_CLASS_VAR(regexp) = &r_class;
/* *INDENT-ON* */

spif_regexp_t
spif_regexp_new(void)
{
    spif_regexp_t self;

    self = SPIF_ALLOC(regexp);
    if (!spif_regexp_init(self)) {
        SPIF_DEALLOC(self);
        self = SPIF_NULL_TYPE(regexp);
    }
    return self;
}

spif_regexp_t
spif_regexp_new_from_str(spif_str_t other)
{
    spif_regexp_t self;

    self = SPIF_ALLOC(regexp);
    if (!spif_regexp_init_from_str(self, other)) {
        SPIF_DEALLOC(self);
        self = SPIF_NULL_TYPE(regexp);
    }
    return self;
}

spif_regexp_t
spif_regexp_new_from_ptr(spif_charptr_t other)
{
    spif_regexp_t self;

    self = SPIF_ALLOC(regexp);
    if (!spif_regexp_init_from_ptr(self, other)) {
        SPIF_DEALLOC(self);
        self = SPIF_NULL_TYPE(regexp);
    }
    return self;
}

spif_bool_t
spif_regexp_init(spif_regexp_t self)
{
    ASSERT_RVAL(!SPIF_REGEXP_ISNULL(self), FALSE);
    if (!spif_str_init(SPIF_STR(self))) {
        return FALSE;
    }
    spif_obj_set_class(SPIF_OBJ(self), SPIF_CLASS_VAR(regexp));
    self->data = SPIF_NULL_TYPE(ptr);
    spif_regexp_set_flags(self, SPIF_NULL_TYPE(charptr));
    return TRUE;
}

spif_bool_t
spif_regexp_init_from_str(spif_regexp_t self, spif_str_t other)
{
    ASSERT_RVAL(!SPIF_REGEXP_ISNULL(self), FALSE);
    if (!spif_str_init_from_ptr(SPIF_STR(self), SPIF_STR_STR(other))) {
        return FALSE;
    }
    spif_obj_set_class(SPIF_OBJ(self), SPIF_CLASS_VAR(regexp));
    self->data = SPIF_NULL_TYPE(ptr);
    spif_regexp_set_flags(self, SPIF_CHARPTR(""));
    return TRUE;
}

spif_bool_t
spif_regexp_init_from_ptr(spif_regexp_t self, spif_charptr_t other)
{
    ASSERT_RVAL(!SPIF_REGEXP_ISNULL(self), FALSE);
    if (!spif_str_init_from_ptr(SPIF_STR(self), other)) {
        return FALSE;
    }
    spif_obj_set_class(SPIF_OBJ(self), SPIF_CLASS_VAR(regexp));
    self->data = SPIF_NULL_TYPE(ptr);
    spif_regexp_set_flags(self, SPIF_CHARPTR(""));
    return TRUE;
}

spif_bool_t
spif_regexp_done(spif_regexp_t self)
{
    ASSERT_RVAL(!SPIF_REGEXP_ISNULL(self), FALSE);
    spif_str_done(SPIF_STR(self));
    if (self->data != SPIF_NULL_TYPE(ptr)) {
        FREE(self->data);
    }
    self->flags = 0;
    return TRUE;
}

spif_bool_t
spif_regexp_del(spif_regexp_t self)
{
    ASSERT_RVAL(!SPIF_REGEXP_ISNULL(self), FALSE);
    spif_regexp_done(self);
    SPIF_DEALLOC(self);
    return TRUE;
}

spif_str_t
spif_regexp_show(spif_regexp_t self, spif_charptr_t name, spif_str_t buff, size_t indent)
{
    spif_char_t tmp[4096];

    if (SPIF_REGEXP_ISNULL(self)) {
        SPIF_OBJ_SHOW_NULL(regexp, name, buff, indent, tmp);
        return buff;
    }

    memset(tmp, ' ', indent);
    snprintf(SPIF_CHARPTR_C(tmp) + indent, sizeof(tmp) - indent,
             "(spif_regexp_t) %s:  %10p {\n",
             name, SPIF_CAST(ptr) self);
    if (SPIF_REGEXP_ISNULL(buff)) {
        buff = spif_str_new_from_ptr(tmp);
    } else {
        spif_str_append_from_ptr(buff, tmp);
    }

    snprintf(SPIF_CHARPTR_C(tmp), sizeof(tmp), "}\n");
    spif_str_append_from_ptr(buff, tmp);
    return buff;
}

spif_cmp_t
spif_regexp_comp(spif_regexp_t self, spif_regexp_t other)
{
    SPIF_OBJ_COMP_CHECK_NULL(self, other);
    return spif_str_comp(SPIF_STR(self), SPIF_STR(other));
}

spif_regexp_t
spif_regexp_dup(spif_regexp_t self)
{
    spif_regexp_t tmp;

    ASSERT_RVAL(!SPIF_REGEXP_ISNULL(self), SPIF_NULL_TYPE(regexp));

    tmp = spif_regexp_new_from_str(SPIF_STR(self));
    tmp->flags = self->flags;
    spif_regexp_compile(tmp);
    return tmp;
}

spif_classname_t
spif_regexp_type(spif_regexp_t self)
{
    ASSERT_RVAL(!SPIF_REGEXP_ISNULL(self), SPIF_NULL_TYPE(classname));
    return SPIF_OBJ_CLASSNAME(self);
}

spif_bool_t
spif_regexp_compile(spif_regexp_t self)
{
    ASSERT_RVAL(!SPIF_REGEXP_ISNULL(self), FALSE);
    if (self->data != SPIF_NULL_TYPE(ptr)) {
        FREE(self->data);
    }
#if LIBAST_REGEXP_SUPPORT_PCRE
    {
        const char *errptr;
        int erroffset;

        self->data = SPIF_CAST(ptr) pcre_compile(SPIF_CHARPTR_C(SPIF_STR_STR(SPIF_STR(self))),
                                                 self->flags, &errptr, &erroffset, NULL);
        if (SPIF_PTR_ISNULL(self->data)) {
            libast_print_error("PCRE compilation of \"%s\" failed at offset %d -- %s\n",
                               SPIF_STR_STR(SPIF_STR(self)), erroffset, errptr);
            return FALSE;
        }
        return TRUE;
    }
#elif (LIBAST_REGEXP_SUPPORT_POSIX)
    {
        char buff[256];
        int errcode;

        self->data = SPIF_CAST(ptr) MALLOC(sizeof(regex_t));
        if ((errcode = regcomp(SPIF_CAST_C(regex_t *) self->data, SPIF_STR_STR(SPIF_STR(self)), (self->flags & 0xffff))) != 0) {
            regerror(errcode, SPIF_CAST_C(regex_t *) self->data, buff, sizeof(buff));
            libast_print_error("POSIX regexp compilation of \"%s\" failed -- %s\n", SPIF_STR_STR(SPIF_STR(self)), buff);
            FREE(self->data);
            return FALSE;
        }
        return TRUE;
    }
#elif (LIBAST_REGEXP_SUPPORT_BSD)
    return TRUE;
#endif
    ASSERT_NOTREACHED_RVAL(FALSE);
}

spif_bool_t
spif_regexp_matches_str(spif_regexp_t self, spif_str_t subject)
{
    ASSERT_RVAL(!SPIF_REGEXP_ISNULL(self), FALSE);
    REQUIRE_RVAL(!SPIF_STR_ISNULL(subject), FALSE);
#if LIBAST_REGEXP_SUPPORT_PCRE
    {
        int rc;

        rc = pcre_exec(SPIF_CAST_C(pcre *) self->data, NULL, SPIF_CHARPTR_C(SPIF_STR_STR(subject)),
                       spif_str_get_len(subject), 0, 0, NULL, 0);
        if (rc == 0) {
            return TRUE;
        } else if (rc == PCRE_ERROR_NOMATCH) {
            return FALSE;
        } else {
            libast_print_error("PCRE matching error %d on \"%s\"\n", rc, SPIF_STR_STR(subject));
            return FALSE;
        }
    }
#elif (LIBAST_REGEXP_SUPPORT_POSIX)
    {
        int rc;
        char errbuf[256];

        rc = regexec(SPIF_CAST_C(regex_t *) self->data, SPIF_STR_STR(subject), (size_t) 0, (regmatch_t *) NULL,
                     ((self->flags >> 8) & 0xffff));
        if (rc == 0) {
            return TRUE;
        } else if (rc == REG_NOMATCH) {
            return FALSE;
        } else {
            regerror(rc, SPIF_CAST_C(regex_t *) self->data, errbuf, sizeof(errbuf));
            libast_print_error("POSIX regexp matching error on \"%s\" -- %s\n", SPIF_STR_STR(subject), errbuf);
            return FALSE;
        }
    }
#elif (LIBAST_REGEXP_SUPPORT_BSD)
    {
        spif_charptr_t err;

        err = SPIF_CAST(charptr) re_comp(SPIF_STR_STR(SPIF_STR(self)));
        if (err != SPIF_NULL_TYPE(charptr)) {
            libast_print_error("BSD regexp compilation of \"%s\" failed -- %s\n", SPIF_STR_STR(SPIF_STR(self)), err);
            return FALSE;
        }
        return ((re_exec(SPIF_STR_STR(subject)) == 0) ? (FALSE) : (TRUE));
    }
#endif
}

spif_bool_t
spif_regexp_matches_ptr(spif_regexp_t self, spif_charptr_t subject)
{
    ASSERT_RVAL(!SPIF_REGEXP_ISNULL(self), FALSE);
    REQUIRE_RVAL(!SPIF_PTR_ISNULL(subject), FALSE);
#if LIBAST_REGEXP_SUPPORT_PCRE
    {
        int rc;

        rc = pcre_exec(SPIF_CAST_C(pcre *) self->data, NULL, SPIF_CHARPTR_C(subject),
                       strlen(SPIF_CHARPTR_C(subject)), 0, 0, NULL, 0);
        if (rc == 0) {
            return TRUE;
        } else if (rc == PCRE_ERROR_NOMATCH) {
            return FALSE;
        } else {
            libast_print_error("PCRE matching error %d on \"%s\"\n", rc, subject);
            return FALSE;
        }
    }
#elif (LIBAST_REGEXP_SUPPORT_POSIX)
    {
        int rc;
        char errbuf[256];

        rc = regexec(SPIF_CAST_C(regex_t *) self->data, subject, (size_t) 0, (regmatch_t *) NULL,
                     ((self->flags >> 8) & 0xffff));
        if (rc == 0) {
            return TRUE;
        } else if (rc == REG_NOMATCH) {
            return FALSE;
        } else {
            regerror(rc, SPIF_CAST_C(regex_t *) self->data, errbuf, sizeof(errbuf));
            libast_print_error("POSIX regexp matching error on \"%s\" -- %s\n", subject, errbuf);
            return FALSE;
        }
    }
#elif (LIBAST_REGEXP_SUPPORT_BSD)
    {
        spif_charptr_t err;

        err = SPIF_CAST(charptr) re_comp(SPIF_STR_STR(SPIF_STR(self)));
        if (err != SPIF_NULL_TYPE(charptr)) {
            libast_print_error("BSD regexp compilation of \"%s\" failed -- %s\n", SPIF_STR_STR(SPIF_STR(self)), err);
            return FALSE;
        }
        return ((re_exec(subject) == 0) ? (FALSE) : (TRUE));
    }
#endif
}

int
spif_regexp_get_flags(spif_regexp_t self)
{
    ASSERT_RVAL(!SPIF_REGEXP_ISNULL(self), SPIF_CAST_C(int) 0);
    return self->flags;
}

spif_bool_t
spif_regexp_set_flags(spif_regexp_t self, spif_charptr_t flagstr)
{
    spif_charptr_t p;

    ASSERT_RVAL(!SPIF_REGEXP_ISNULL(self), FALSE);
#if LIBAST_REGEXP_SUPPORT_PCRE
    self->flags = 0;
#elif (LIBAST_REGEXP_SUPPORT_POSIX)
    self->flags = REG_EXTENDED | REG_NEWLINE;
#endif

    REQUIRE_RVAL(flagstr != SPIF_NULL_TYPE(charptr), FALSE);
    for (p = flagstr; *p; p++) {
        switch (*p) {
#if LIBAST_REGEXP_SUPPORT_PCRE
            case 'i':  self->flags |= PCRE_CASELESS; break;
            case 'm':  self->flags |= PCRE_MULTILINE; break;
            case 's':  self->flags |= PCRE_DOTALL; break;
            case 'x':  self->flags |= PCRE_EXTENDED; break;
            case 'u':  self->flags |= PCRE_UNGREEDY; break;
            case '8':  self->flags |= PCRE_UTF8; break;
            case '^':  self->flags |= PCRE_NOTBOL; break;
            case '$':  self->flags |= PCRE_NOTEOL; break;
            case 'E':  self->flags |= PCRE_NOTEMPTY; break;
#elif (LIBAST_REGEXP_SUPPORT_POSIX)
            case 'b':  self->flags &= ~REG_EXTENDED; break;
            case 'i':  self->flags |= REG_ICASE; break;
            case 'n':  self->flags |= REG_NOSUB; break;
            case 's':  self->flags &= ~REG_NEWLINE; break;
            case '^':  self->flags |= (REG_NOTBOL << 8); break;
            case '$':  self->flags |= (REG_NOTEOL << 8); break;
#endif
            default:
                libast_print_warning("Unrecognized regexp flag character \'%c\'\n", *p);
                break;
        }
    }
    return spif_regexp_compile(self);
}