/* -*- mode: c ; c-file-style: "canonware-c-style" -*-
 ******************************************************************************
 *
 * Copyright (C) 1996-2004 Jason Evans <jasone@canonware.com>.
 * 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(s), this list of conditions and the following disclaimer
 *    unmodified other than the allowable addition of one or more
 *    copyright notices.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice(s), this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
 * EXPRESS 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 THE COPYRIGHT HOLDER(S) 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.
 *
 ******************************************************************************
 *
 * Version: Onyx 5.1.1
 *
 ******************************************************************************/

#define CW_NXO_REGEX_C_

#include "../include/libonyx/libonyx.h"
#include "../include/libonyx/nxa_l.h"
#include "../include/libonyx/nxo_l.h"
#include "../include/libonyx/nxo_regex_l.h"
#include "../include/libonyx/nxo_thread_l.h"

/* Do the work of initializing a regex, but don't do any of the typical
 * GC-related initialization, so that this function can be used for the case
 * where a regex is temporarily constructed for a single match. */
static cw_nxn_t
nxo_p_regex_init(cw_nxoe_regex_t *a_regex, const char *a_pattern,
		 uint32_t a_len, bool a_cont, bool a_global,
		 bool a_insensitive, bool a_multiline,
		 bool a_singleline)
{
    cw_nxn_t retval;
    char *pattern;
    const char *errptr;
    int options, erroffset, capturecount;

    nxoe_l_new(&a_regex->nxoe, NXOT_REGEX, false);

    /* Create a '\0'-terminated copy of a_pattern. */
    pattern = (char *) nxa_malloc(a_len + 1);
    memcpy(pattern, a_pattern, a_len);
    pattern[a_len] = '\0';

    /* Translate options to a format usable by pcre_compile(). */
    options = 0;
    /* $i. */
    if (a_insensitive)
    {
	options |= PCRE_CASELESS;
    }
    /* $m. */
    if (a_multiline)
    {
	options |= PCRE_MULTILINE;
    }
    /* $s. */
    if (a_singleline)
    {
	options |= PCRE_DOTALL;
    }

    /* Store the cont and global flags.  This information is not needed in this
     * function, but gets used when actually doing matches. */
    a_regex->cont = a_cont;
    a_regex->global = a_global;

    /* Compile the regex. */
    a_regex->pcre = pcre_compile(pattern, options, &errptr, &erroffset, NULL);
    nxa_free(pattern, a_len + 1);
    if (a_regex->pcre == NULL)
    {
	retval = NXN_regexerror;
	goto RETURN;
    }

    /* Call pcre_study(), which may improve matching performance. */
    a_regex->extra = pcre_study(a_regex->pcre, 0, &errptr);
    if (errptr != NULL)
    {
	free(a_regex->pcre);
	retval = NXN_regexerror;
	goto RETURN;
    }

    /* Get capturecount and the amount of space that was allocated for
     * a_regex->pcre and a_regex->extra. */
    if ((pcre_fullinfo(a_regex->pcre, a_regex->extra, PCRE_INFO_CAPTURECOUNT,
		       &capturecount) != 0)
	|| (pcre_fullinfo(a_regex->pcre, a_regex->extra, PCRE_INFO_SIZE,
			  &a_regex->size) != 0)
	|| (pcre_fullinfo(a_regex->pcre, a_regex->extra, PCRE_INFO_STUDYSIZE,
			  &a_regex->studysize) != 0)
	)
    {
	free(a_regex->pcre);
	if (a_regex->extra != NULL)
	{
	    free(a_regex->extra);
	}
	retval = NXN_regexerror;
	goto RETURN;
    }

    /* Use capturecount to calculate the size of vector needed for pcre_exec()
     * calls. */
    a_regex->ovcnt = (capturecount + 1) * 3;

    retval = NXN_ZERO;
    RETURN:
    return retval;
}

/* Returns true if match is successful. */
static bool
nxo_p_regex_match(cw_nxoe_regex_t *a_regex, cw_nxo_t *a_thread,
		  cw_nxo_t *a_input)
{
    bool retval;
    cw_nxo_regex_cache_t *cache;
    int ioff;

    cache = nxo_l_thread_regex_cache_get(a_thread);

    if (nxo_string_len_get(a_input) == 0)
    {
	cache->mcnt = -1;
	retval = false;
	goto RETURN;
    }

    /* Allocate or extend the vector for passing to pcre_exec(), if
     * necessary. */
    if (cache->ovp == NULL)
    {
	cache->ovp = nxa_malloc(sizeof(int) * a_regex->ovcnt);
	cache->ovcnt = a_regex->ovcnt;
    }
    else if (cache->ovcnt < a_regex->ovcnt)
    {
	cache->ovp = nxa_realloc(cache->ovp, sizeof(int) * a_regex->ovcnt,
				 sizeof(int) * cache->ovcnt);
	cache->ovcnt = a_regex->ovcnt;
    }

    /* Determine where in the string to start searching.  This depends on
     * whether $c or $g is set, as well as whether the previous match was
     * against the same string. */
    if (a_regex->cont || a_regex->global)
    {
	if (nxo_type_get(&cache->input) == NXOT_STRING
	    && nxo_compare(&cache->input, a_input) == 0)
	{
	    ioff = cache->cont;
	    if ((uint32_t) ioff >= nxo_string_len_get(a_input))
	    {
		cache->mcnt = -1;
		retval = false;
		goto NOMATCH;
	    }
	}
	else
	{
	    ioff = 0;
	}
    }
    else
    {
	ioff = 0;
    }

    /* Look for a match. */
    nxo_string_lock(a_input);
    cache->mcnt = pcre_exec(a_regex->pcre, a_regex->extra,
			    (char *) nxo_string_get(a_input),
			    (int) nxo_string_len_get(a_input),
			    ioff, 0, cache->ovp, cache->ovcnt);
    nxo_string_unlock(a_input);
    if (cache->mcnt <= 0)
    {
	switch (cache->mcnt)
	{
	    case 0:
	    case PCRE_ERROR_NOMATCH:
	    {
		/* No match found.  Not an error. */
		retval = false;
		break;
	    }
	    case PCRE_ERROR_NOMEMORY:
	    {
		xep_throw(CW_ONYXX_OOM);
	    }
	    case PCRE_ERROR_NULL:
	    case PCRE_ERROR_BADOPTION:
	    case PCRE_ERROR_BADMAGIC:
	    case PCRE_ERROR_UNKNOWN_NODE:
	    default:
	    {
		cw_not_reached();
	    }
	}
    }
    else
    {
	retval = true;
    }

    NOMATCH:
    /* Store the location to start the next search from, if $c or $g is set.
     * Also set the cached input string accordingly. */
    if (a_regex->cont)
    {
	if (cache->mcnt > 0)
	{
	    nxo_dup(&cache->input, a_input);
	    cache->cont = cache->ovp[1];
	}
    }
    else if (a_regex->global)
    {
	if (cache->mcnt > 0)
	{
	    nxo_dup(&cache->input, a_input);
	    cache->cont = cache->ovp[1];
	}
	else
	{
	    nxo_no_new(&cache->input);
	    cache->cont = 0;
	}
    }
    else
    {
	if (cache->mcnt > 0)
	{
	    nxo_dup(&cache->input, a_input);
	}
	else
	{
	    nxo_no_new(&cache->input);
	}
    }

    RETURN:
    return retval;
}

static void
nxo_p_regex_split(cw_nxoe_regex_t *a_regex, cw_nxo_t *a_thread,
		  uint32_t a_limit, cw_nxo_t *a_input, cw_nxo_t *r_array)
{
    cw_nxo_regex_cache_t *cache;
    cw_nxo_t *tstack, *tnxo;
    char *istr;
    int ilen, ioff;
    uint32_t i, acnt;

    cache = nxo_l_thread_regex_cache_get(a_thread);
    tstack = nxo_thread_tstack_get(a_thread);

    istr = nxo_string_get(a_input);
    ilen = (int) nxo_string_len_get(a_input);
    if (ilen == 0)
    {
	cache->mcnt = -1;
	acnt = 0;
	goto NOMATCH;
    }

    /* Allocate or extend the vector for passing to pcre_exec(), if
     * necessary. */
    if (cache->ovp == NULL)
    {
	cache->ovp = nxa_malloc(sizeof(int) * a_regex->ovcnt);
	cache->ovcnt = a_regex->ovcnt;
    }
    else if (cache->ovcnt < a_regex->ovcnt)
    {
	cache->ovp = nxa_realloc(cache->ovp, sizeof(int) * a_regex->ovcnt,
				 sizeof(int) * cache->ovcnt);
	cache->ovcnt = a_regex->ovcnt;
    }

    /* Iteratively search for matches with the splitting pattern and create
     * substrings until there is no more text, or the split limit has been
     * reached. */
    for (acnt = ioff = 0;
	 ioff < ilen && (acnt + 1 < a_limit || a_limit == 0);
	 )
    {
	/* Look for a match. */
	nxo_string_lock(a_input);
	cache->mcnt = pcre_exec(a_regex->pcre, a_regex->extra,
				(char *) istr, ilen, ioff, 0,
				cache->ovp, cache->ovcnt);
	nxo_string_unlock(a_input);
	if (cache->mcnt <= 0)
	{
	    switch (cache->mcnt)
	    {
		case 0:
		case PCRE_ERROR_NOMATCH:
		{
		    /* No match found.  Not an error. */
		    goto DONE;
		}
		case PCRE_ERROR_NOMEMORY:
		{
		    xep_throw(CW_ONYXX_OOM);
		}
		case PCRE_ERROR_NULL:
		case PCRE_ERROR_BADOPTION:
		case PCRE_ERROR_BADMAGIC:
		case PCRE_ERROR_UNKNOWN_NODE:
		default:
		{
		    cw_not_reached();
		}
	    }
	}

	/* Create a substring of the text that falls between the previous match
	 * and this match. */
	tnxo = nxo_stack_push(tstack);
	if (cache->ovp[0] < cache->ovp[1])
	{
	    /* Create a substring (normal case). */
	    nxo_string_substring_new(tnxo, a_input, ioff, cache->ovp[0] - ioff);
	    ioff = cache->ovp[1];
	}
	else
	{
	    /* The pattern matches the empty string, so split a single character
	     * to avoid an infinite loop. */
	    nxo_string_substring_new(tnxo, a_input, ioff, 1);
	    ioff++;
	}
	acnt++;

	/* If there were capturing subpatterns that matched, create substrings
	 * of them.  If there are interspersed subpatterns that did not match,
	 * create null objects for them. */
	if (cache->mcnt > 1)
	{
	    for (i = 1; i < cache->mcnt; i++)
	    {
		tnxo = nxo_stack_push(tstack);
		nxo_string_substring_new(tnxo, a_input,
					 cache->ovp[i * 2],
					 cache->ovp[i * 2 + 1]
					 - cache->ovp[i * 2]);
		acnt++;
	    }
	}
    }
    DONE:
    /* If there are trailing bytes after the last match, create a substring and
     * push it onto tstack. */
    if (ioff < ilen)
    {
	tnxo = nxo_stack_push(tstack);
	nxo_string_substring_new(tnxo, a_input, ioff,
				 nxo_string_len_get(a_input)
				 - (uint32_t) ioff);
	acnt++;
    }

    NOMATCH:
    /* Create an array that contains the substrings on tstack. */
    nxo_array_new(r_array, nxo_thread_currentlocking(a_thread), acnt);

    /* Dup the substrings into r_matches. */
    for (i = 0, tnxo = nxo_stack_get(tstack);
	 i < acnt;
	 i++, tnxo = nxo_stack_nget(tstack, i))
    {
	nxo_array_el_set(r_array, tnxo, acnt - 1 - i);
    }

    /* Clean up tstack. */
    nxo_stack_npop(tstack, acnt);
}

cw_nxn_t
nxo_regex_new(cw_nxo_t *a_nxo, const char *a_pattern,
	      uint32_t a_len, bool a_cont, bool a_global,
	      bool a_insensitive, bool a_multiline,
	      bool a_singleline)
{
    cw_nxn_t retval;
    cw_nxoe_regex_t *regex;

    regex = (cw_nxoe_regex_t *) nxa_malloc(sizeof(cw_nxoe_regex_t));

    retval = nxo_p_regex_init(regex, a_pattern, a_len, a_cont, a_global,
			      a_insensitive, a_multiline, a_singleline);
    if (retval)
    {
	nxa_free(regex, sizeof(cw_nxoe_regex_t));
	goto RETURN;
    }

    /* Tell the GC about the space being taken up by regex->pcre and
     * regex->extra. */
    nxa_l_count_adjust((cw_nxoi_t) regex->size + regex->studysize);

    /* Create a reference to the regex object. */
    nxo_no_new(a_nxo);
    a_nxo->o.nxoe = (cw_nxoe_t *) regex;
    nxo_p_type_set(a_nxo, NXOT_REGEX);

    /* Register the regex object with the GC. */
    nxa_l_gc_register((cw_nxoe_t *) regex);

    retval = NXN_ZERO;
    RETURN:
    return retval;
}

void
nxo_regex_match(cw_nxo_t *a_nxo, cw_nxo_t *a_thread, cw_nxo_t *a_input,
		bool *r_match)
{
    cw_nxoe_regex_t *regex;

    cw_check_ptr(a_nxo);
    cw_dassert(a_nxo->magic == CW_NXO_MAGIC);
    cw_assert(nxo_type_get(a_nxo) == NXOT_REGEX);

    regex = (cw_nxoe_regex_t *) a_nxo->o.nxoe;

    cw_check_ptr(regex);
    cw_dassert(regex->nxoe.magic == CW_NXOE_MAGIC);
    cw_assert(regex->nxoe.type == NXOT_REGEX);

    *r_match = nxo_p_regex_match(regex, a_thread, a_input);
}

/* Do a match without creating a regex object, in order to avoid putting
 * pressure on the GC. */
cw_nxn_t
nxo_regex_nonew_match(cw_nxo_t *a_thread, const char *a_pattern,
		      uint32_t a_len, bool a_cont, bool a_global,
		      bool a_insensitive, bool a_multiline,
		      bool a_singleline, cw_nxo_t *a_input,
		      bool *r_match)
{
    cw_nxn_t retval;
    cw_nxoe_regex_t regex;

    retval = nxo_p_regex_init(&regex, a_pattern, a_len, a_cont, a_global,
			      a_insensitive, a_multiline, a_singleline);
    if (retval)
    {
	goto RETURN;
    }

    *r_match = nxo_p_regex_match(&regex, a_thread, a_input);

    /* Clean up memory. */
    free(regex.pcre);
    if (regex.extra != NULL)
    {
	free(regex.extra);
    }

    retval = NXN_ZERO;
    RETURN:
    return retval;
}

void
nxo_regex_split(cw_nxo_t *a_nxo, cw_nxo_t *a_thread, uint32_t a_limit,
		cw_nxo_t *a_input, cw_nxo_t *r_array)
{
    cw_nxoe_regex_t *regex;

    cw_check_ptr(a_nxo);
    cw_dassert(a_nxo->magic == CW_NXO_MAGIC);
    cw_assert(nxo_type_get(a_nxo) == NXOT_REGEX);

    regex = (cw_nxoe_regex_t *) a_nxo->o.nxoe;

    cw_check_ptr(regex);
    cw_dassert(regex->nxoe.magic == CW_NXOE_MAGIC);
    cw_assert(regex->nxoe.type == NXOT_REGEX);

    nxo_p_regex_split(regex, a_thread, a_limit, a_input, r_array);
}

/* Do a split without creating a regex object, in order to avoid putting
 * pressure on the GC. */
cw_nxn_t
nxo_regex_nonew_split(cw_nxo_t *a_thread, const char *a_pattern,
		      uint32_t a_len, bool a_insensitive,
		      bool a_multiline, bool a_singleline,
		      uint32_t a_limit, cw_nxo_t *a_input, cw_nxo_t *r_array)
{
    cw_nxn_t retval;
    cw_nxoe_regex_t regex;

    retval = nxo_p_regex_init(&regex, a_pattern, a_len, false, false,
			      a_insensitive, a_multiline, a_singleline);
    if (retval)
    {
	goto RETURN;
    }

    nxo_p_regex_split(&regex, a_thread, a_limit, a_input, r_array);

    /* Clean up memory. */
    free(regex.pcre);
    if (regex.extra != NULL)
    {
	free(regex.extra);
    }

    retval = NXN_ZERO;
    RETURN:
    return retval;
}

void
nxo_regex_submatch(cw_nxo_t *a_thread, uint32_t a_capture, cw_nxo_t *r_match)
{
    cw_nxo_regex_cache_t *cache;

    cache = nxo_l_thread_regex_cache_get(a_thread);

    if ((int) a_capture < cache->mcnt
	&& nxo_type_get(&cache->input) == NXOT_STRING
	&& cache->ovp[a_capture * 2] != -1)
    {
	/* Create a substring for the capturing subpattern. */
	nxo_string_substring_new(r_match, &cache->input,
				 cache->ovp[a_capture * 2],
				 cache->ovp[a_capture * 2 + 1]
				 - cache->ovp[a_capture * 2]);
    }
    else
    {
	/* This subpattern wasn't matched (possible when the capture is for an
	 * alternative). */
	nxo_null_new(r_match);
    }
}
