/*!The Treasure Box Library
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * Copyright (C) 2009 - 2017, TBOOX Open Source Group.
 *
 * @author      ruki
 * @file        regex.c
 * @ingroup     platform
 *
 */

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "prefix.h"
#include <regex.h>

/* //////////////////////////////////////////////////////////////////////////////////////
 * types
 */

// the regex type
typedef struct __tb_regex_t
{
    // the code
    regex_t             code;

    // the results 
    tb_vector_ref_t     results;

    // the mode
    tb_size_t           mode;

    // the match data
    regmatch_t*         match_data;

    // the match maxn
    tb_size_t           match_maxn;

    // the buffer data
    tb_char_t*          buffer_data;

    // the buffer maxn
    tb_size_t           buffer_maxn;

}tb_regex_t;

/* //////////////////////////////////////////////////////////////////////////////////////
 * implementation
 */
tb_regex_ref_t tb_regex_init(tb_char_t const* pattern, tb_size_t mode)
{
    // check
    tb_assert_and_check_return_val(pattern, tb_null);

    // done
    tb_bool_t   ok = tb_false;
    tb_regex_t* regex = tb_null;
    do
    {
        // make regex
        regex = (tb_regex_t*)tb_malloc0_type(tb_regex_t);
        tb_assert_and_check_break(regex);

        // init options
        tb_int_t options = REG_EXTENDED;
        if (mode & TB_REGEX_MODE_CASELESS) options |= REG_ICASE;
        if (mode & TB_REGEX_MODE_MULTILINE) options |= REG_NEWLINE;
#ifdef REG_ENHANCED
        options |= REG_ENHANCED;
#endif

        // init code
        tb_int_t error = regcomp(&regex->code, pattern, options);
        if (error)
        {
#ifdef __tb_debug__
            tb_char_t info[256] = {0};
            regerror(error, &regex->code, info, sizeof(info));

            // trace
            tb_trace_d("compile failed: %s\n", info);
#endif

            // end
            break;
        }

        // save mode
        regex->mode = mode;

        // ok 
        ok = tb_true;

    } while (0);

    // failed?
    if (!ok)
    {
        // exit it
        if (regex) tb_regex_exit((tb_regex_ref_t)regex);
        regex = tb_null;
    }

    // ok?
    return (tb_regex_ref_t)regex;
}
tb_void_t tb_regex_exit(tb_regex_ref_t self)
{
    // check
    tb_regex_t* regex = (tb_regex_t*)self;
    tb_assert_and_check_return(regex);

    // exit buffer data
    if (regex->buffer_data) tb_free(regex->buffer_data);
    regex->buffer_data = tb_null;
    regex->buffer_maxn = 0;

    // exit match data
    if (regex->match_data) tb_free(regex->match_data);
    regex->match_data = tb_null;
    regex->match_maxn = 0;

    // exit results
    if (regex->results) tb_vector_exit(regex->results);
    regex->results = tb_null;

    // exit code
    regfree(&regex->code);

    // exit it
    tb_free(regex);
}
tb_long_t tb_regex_match(tb_regex_ref_t self, tb_char_t const* cstr, tb_size_t size, tb_size_t start, tb_size_t* plength, tb_vector_ref_t* presults)
{
    // check
    tb_regex_t* regex = (tb_regex_t*)self;
    tb_assert_and_check_return_val(regex && cstr, -1);

    // done
    tb_long_t ok = -1;
    do
    {
        // clear length first
        if (plength) *plength = 0;

        // end?
        tb_check_break(start < size);

        // init match data
        if (!regex->match_data)
        {
            regex->match_maxn = 16;
            regex->match_data = (regmatch_t*)tb_malloc_bytes(sizeof(regmatch_t) * regex->match_maxn);
        }
        tb_assert_and_check_break(regex->match_data);

        // check
        tb_assert(size <= tb_strlen(cstr));

        // match it
        tb_long_t error = -1;
        while (REG_ESPACE == (error = regexec(&regex->code, cstr + start, regex->match_maxn, regex->match_data, 0)))
        {
            // grow match data
            regex->match_maxn <<= 1;
            regex->match_data = (regmatch_t*)tb_ralloc_bytes(regex->match_data, sizeof(regmatch_t) * regex->match_maxn);
            tb_assert_and_check_break(regex->match_data);
        }
        if (error)
        {
            // no match?
            tb_check_break(error != REG_NOMATCH);

#ifdef __tb_debug__
            // get error info
            tb_char_t info[256] = {0};
            regerror(error, &regex->code, info, sizeof(info));

            // trace
            tb_trace_d("match failed at offset %lu: error: %s\n", start, info);
#endif

            // end
            break;
        }

        // get the match offset and length
        regmatch_t const*   match = regex->match_data;
        tb_size_t           count = 1 + regex->code.re_nsub;
        tb_size_t           offset = start + (tb_size_t)match[0].rm_so;
        tb_size_t           length = (tb_size_t)match[0].rm_eo - match[0].rm_so;
        tb_check_break(offset + length <= size);

        // trace
        tb_trace_d("matched count: %lu, offset: %lu, length: %lu", count, offset, length);

        // save results
        if (presults)
        {
            // init results if not exists
            tb_vector_ref_t results = *presults;
            if (!results)
            {
                // init it
                if (!regex->results) regex->results = tb_vector_init(16, tb_element_mem(sizeof(tb_regex_match_t), tb_regex_match_exit, tb_null));

                // save it
                *presults = results = regex->results;
            }
            tb_assert_and_check_break(results);

            // clear it first
            tb_vector_clear(results);

            // done
            tb_long_t           i = 0;
            tb_regex_match_t    entry;
            for (i = 0; i < count; i++)
            {
                // get substring offset and length
                tb_size_t substr_offset = start + match[i].rm_so;
                tb_size_t substr_length = match[i].rm_eo - match[i].rm_so;
                tb_assert_and_check_break(substr_offset + substr_length <= size);

                // make match entry
                entry.cstr  = tb_strndup(cstr + substr_offset, substr_length);
                entry.size  = substr_length;
                entry.start = substr_offset;
                tb_assert_and_check_break(entry.cstr);
                
                // trace
                tb_trace_d("    matched: [%lu, %lu]: %s", entry.start, entry.size, entry.cstr);

                // append it
                tb_vector_insert_tail(results, &entry);
            }
            tb_assert_and_check_break(i == count);
        }

        // save length 
        if (plength) *plength = length;

        // ok
        ok = offset;

    } while (0);

    // ok?
    return ok;
}
tb_char_t const* tb_regex_replace(tb_regex_ref_t self, tb_char_t const* cstr, tb_size_t size, tb_size_t start, tb_char_t const* replace_cstr, tb_size_t replace_size, tb_size_t* plength)
{
    // check
    tb_regex_t* regex = (tb_regex_t*)self;
    tb_assert_and_check_return_val(regex && cstr && replace_cstr, tb_null);

    // done
    tb_char_t const* result = tb_null;
    do
    {
        // clear length first
        if (plength) *plength = 0;

        // end?
        tb_check_break(start < size);

        // init buffer
        if (!regex->buffer_data)
        {
            regex->buffer_maxn = tb_max(size + replace_size + 64, 256);
            regex->buffer_data = tb_malloc_cstr(regex->buffer_maxn);
        }
        tb_assert_and_check_break(regex->buffer_data);

        // copy cstr
        tb_memcpy(regex->buffer_data, cstr, size);
        regex->buffer_data[size] = '\0';

        // done
        tb_size_t       count = 0;
        tb_long_t       suboffset = start;
        tb_size_t       sublength = 0;
        tb_size_t       length = 0;
        tb_vector_ref_t results = tb_null;
        while ((suboffset = tb_regex_match(self, regex->buffer_data, size, suboffset + sublength, &sublength, &results)) >= 0 && results)
        {
            // trace
            tb_trace_d("replace: match: [%lu, %lu]", suboffset, sublength);

            // calculate substring end
            tb_size_t subend = suboffset + sublength;
            tb_assert_and_check_break(subend <= size);

            // update length
            length = size - sublength + replace_size;

            // grow buffer
            if (regex->buffer_maxn < length)
            {
                regex->buffer_maxn = tb_max(regex->buffer_maxn << 1, length);
                regex->buffer_data = tb_ralloc_cstr(regex->buffer_data, regex->buffer_maxn + 1);
            }
            tb_assert_and_check_break(regex->buffer_data);

            // replace this match
            if (subend < size) tb_memmov(regex->buffer_data + suboffset + replace_size, regex->buffer_data + subend, size - subend);
            tb_memcpy(regex->buffer_data + suboffset, replace_cstr, replace_size);
            regex->buffer_data[length] = '\0';
           
            // trace
            tb_trace_d("replace: => %s", regex->buffer_data);

            // update matched count
            count++;

            // global replace?
            tb_check_break(regex->mode & TB_REGEX_MODE_GLOBAL);

            // update size
            size        = length;
            sublength   = replace_size;
        }

        // check
        tb_check_break(count);
        tb_assert_and_check_break(length < regex->buffer_maxn);

        // end
        regex->buffer_data[length] = '\0';

        // trace
        tb_trace_d("    replace: [%lu]: %s", length, regex->buffer_data);

        // save length 
        if (plength) *plength = length;

        // ok
        result = (tb_char_t const*)regex->buffer_data;

    } while (0);

    // ok?
    return result;
}
