/* ====================================================================
 * Copyright (c) 1999 Ralf S. Engelschall. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY RALF S. ENGELSCHALL ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL RALF S. ENGELSCHALL OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 */

#include <stdarg.h>

#include "pcre_internal.h"

static char *strrspn(const char *string, const char *charset)
{
    char *cp;
    int i, n;
    int oneof;

    if (string == NULL || charset == NULL)
        return NULL;
    cp = (char *)string+strlen(string)-1;
    n = strlen(charset);
    while (cp >= string) {
        oneof = FALSE;
        for (i = 0; i < n; i++) {
            if (*cp == charset[i]) {
                oneof = TRUE;
                break;
            }
        }
        if (!oneof)
            break;
        cp--;
    }
    return cp;
}

static int pattern_compile(const char *ptr, int len, int opt, pcre **p_pcre, pcre_extra **p_pcre_extra)
{
    const char *err_str;
    int err_pos;
    char *cp;

    /* compile pattern */
    if (ptr[len] == '\0') {
        /* plain pattern, so we can speed up processing */
        if ((*p_pcre = pcre_compile(ptr, opt, &err_str, &err_pos, NULL)) == NULL)
            return FALSE;
    }
    else {
        /* else we have to create a temporary string */
        if ((cp = pcre_malloc(len+1)) == NULL)
            return FALSE;
        memcpy(cp, ptr, len);
        cp[len] = '\0';
        *p_pcre = pcre_compile(cp, opt, &err_str, &err_pos, NULL);
        pcre_free(cp);
        if (*p_pcre == NULL)
            return FALSE;
    }
    /* optionally study pattern */
    if (p_pcre_extra != NULL) {
        *p_pcre_extra = pcre_study(*p_pcre, 0, &err_str);
        if (err_str != NULL) {
            pcre_free(p_pcre);
            return FALSE;
        }
    }
    return TRUE;
}

struct hash_entry {
    struct hash_entry *next;
    char *key;
    int keylen;
    pcre *p_pcre;
    pcre_extra *p_pcre_extra;
};

#define HASH_SIZE 101 /* prime */

static struct hash_entry *pattern_hash[HASH_SIZE];

static int hash_initialized = FALSE;

static void hash_init(void)
{
    int i;
    for (i = 0; i < HASH_SIZE; i++)
        pattern_hash[i] = NULL;
    return;
}

static void hash_destroy(void)
{
    int i;
    struct hash_entry *he, *ohe;

    for (i = 0; i < HASH_SIZE; i++) {
        he = pattern_hash[i];
        pattern_hash[i] = NULL;
        while (he != NULL) {
            ohe = he;
            he = he->next;
            pcre_free(ohe);
        }
    }
    return;
}

static int hash_func(const char *key, int keylen)
{
    int h, i;

    h = key[0];
    for (i = 1; key[i] != '\0'; i++)
        h = ((((h<<5)+h)+key[i]) % HASH_SIZE);
    return h;
}

static void pattern_cache(const char *key, int keylen, pcre *p_pcre, pcre_extra *p_pcre_extra)
{
    int h;
    struct hash_entry *he, *che;

    if ((he = pcre_malloc(sizeof(struct hash_entry))) == NULL)
        return;
    if ((he->key = pcre_malloc(keylen)) == NULL) {
        pcre_free(he);
        return;
    }
    he->next = NULL;
    memcpy(he->key, key, keylen);
    he->keylen = keylen;
    he->p_pcre = p_pcre;
    he->p_pcre_extra = p_pcre_extra;
    h = hash_func(key, keylen);
    if (pattern_hash[h] == NULL)
        pattern_hash[h] = he;
    else {
        che = pattern_hash[h];
        while (che->next != NULL)
             che = che->next;
        che->next = he;
    }
    return;
}

static void pattern_lookup(const char *key, int keylen, pcre **p_pcre, pcre_extra **p_pcre_extra)
{
    int h;
    struct hash_entry *he;

    *p_pcre = NULL;
    *p_pcre_extra = NULL;

    h = hash_func(key, keylen);
    if ((he = pattern_hash[h]) == NULL)
        return;
    while (he->next != NULL) {
        if (he->keylen == keylen)
            if (memcmp(he->key, key, keylen))
                break;
        he = he->next;
    }
    *p_pcre = he->p_pcre;
    *p_pcre_extra = he->p_pcre_extra;
    return;
}

/* the API function */
int pcre_match(const char *string, const char *pattern, ...)
{
    pcre       *p_pcre;
    pcre_extra *p_pcre_extra;
    const char *match_ptr; 
    int         match_len;
    int         match_opt;
    int         match_once;
    int        *cap_vec;
    int         cap_num;
    int         cap_len;
    char       *cp;
    char      **cpp;
    int         n;
    int         i;
    int         k;
    va_list     ap;
    int         ismop;

    /* hash initialization */
    if (!hash_initialized) {
        hash_init();
        atexit(hash_destroy);
        hash_initialized = TRUE;
    }
    
    /* hash destruction */
    if (string == NULL && pattern == NULL) {
        hash_destroy();
        return -1;
    }

    /* check input parameters */
    if (string == NULL || pattern == NULL)
        return -1;

    /* parse pattern */
    match_ptr  = NULL;
    match_len  = 0;
    match_opt  = 0;
    match_once = FALSE;
    ismop = FALSE;
    cp = NULL;
    if (*pattern == 'm' && strlen(pattern) >= 3)
        if ((cp = strrspn(pattern, "imsxo")) > pattern+1)
            if (*(pattern+1) == *cp)
                ismop = TRUE;
    if (ismop) {
        match_ptr = pattern + 2;
        match_len = cp - match_ptr;
        cp++;
        for (i = 0; cp[i] != '\0'; i++) {
            switch (cp[i]) {
                case 'i': match_opt |= PCRE_CASELESS;  break;
                case 'm': match_opt |= PCRE_MULTILINE; break;
                case 's': match_opt |= PCRE_DOTALL;    break;
                case 'x': match_opt |= PCRE_EXTENDED;  break;
                case 'o': match_once = TRUE;           break;
                default:
                     return -1;
            }
        }
    }
    else {
        match_ptr = pattern;
        match_len = strlen(pattern);
    }

    if (match_once) {
        /* optimized processing: up to factor 15(!) for complex regular expressions */
        pattern_lookup(match_ptr, match_len, &p_pcre, &p_pcre_extra);
        fprintf(stderr, "hash lookup: %s\n", p_pcre == NULL ? "missed" : "found");
        if (p_pcre == NULL) {
            if (!pattern_compile(match_ptr, match_len, match_opt, &p_pcre, &p_pcre_extra))
                return -1;
            pattern_cache(match_ptr, match_len, p_pcre, p_pcre_extra);
        }
    }
    else {
        /* unoptimized processing */
        p_pcre_extra = NULL;
        if (!pattern_compile(match_ptr, match_len, match_opt, &p_pcre, NULL))
            return -1;
    }

    /* allocate storage for captured substrings */
    cap_vec = NULL;
    cap_len = 0;
    cap_num = pcre_info(p_pcre, NULL, NULL);
    if (cap_num > 0) {
        cap_len = (cap_num+1)*3;
        if ((cap_vec = pcre_malloc(cap_len*sizeof(int))) == NULL) {
            if (!match_once) {
                pcre_free(p_pcre);
                pcre_free(p_pcre_extra);
            }
            return -1;
        }
    }

    /* perform the matching */
    n = pcre_exec(p_pcre, p_pcre_extra, string, strlen(string), 0, 0, cap_vec, cap_len);

    /* error cases */
    if (n < 0) {
        if (cap_vec != NULL)
            pcre_free(cap_vec);
        if (!match_once) {
            pcre_free(p_pcre);
            pcre_free(p_pcre_extra);
        }
        if (n == PCRE_ERROR_NOMATCH)
            return 0;
        return -1;
    }

    /* extract captured substrings into caller provided pointer variables */
    if (cap_num > 0) {
        va_start(ap, pattern);
        for (i = 1; i <= cap_num; i++) {
            cpp = va_arg(ap, char **);
            if (cpp != NULL) {
                if (i <= (n-1)) {
                    if (cap_vec[(i*2)] != -1 && cap_vec[(i*2)+1] != -1) {
                        k = (cap_vec[(i*2)+1] - cap_vec[(i*2)]);
                        if (k > 0) {
                            if ((*cpp = pcre_malloc(k+1)) != NULL) {
                                memcpy(*cpp, (char *)(string+cap_vec[(i*2)]), k);
                                (*cpp)[k] = '\0';
                                continue;
                            }
                        }
                    }
                }
                *cpp = strdup("");
            }
        }
        va_end(ap);
    }

    /* cleanup */
    if (cap_vec != NULL)
        pcre_free(cap_vec);
    if (!match_once) {
        pcre_free(p_pcre);
        pcre_free(p_pcre_extra);
    }
    /* return success */
    return 1;
}

