// Copyright (C) 2006-2014 David Sugar, Tycho Softworks.
// Copyright (C) 2015-2020 Cherokees of Idaho.
//
// This file is part of GNU uCommon C++.
//
// GNU uCommon C++ is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU uCommon C++ is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with GNU uCommon C++.  If not, see <http://www.gnu.org/licenses/>.

#include <ucommon-config.h>
#include <ucommon/export.h>
#include <ucommon/string.h>
#include <ucommon/memory.h>
#include <stdarg.h>
#include <ctype.h>
#include <stdio.h>
#ifdef  HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <limits.h>
#ifdef  HAVE_REGEX_H
#include <regex.h>
#endif

namespace ucommon {

String::regex::regex(const char *pattern, size_t size)
{
#ifdef  HAVE_REGEX_H
    regex_t *r = (regex_t *)malloc(sizeof(regex_t));
    if(regcomp(r, pattern, 0)) {
        regfree(r);
        free(r);
        r = NULL;
    }
    object = r;
    count = size;
    results = (regmatch_t *)malloc(sizeof(regmatch_t) * size);
#else
    object = results = NULL;
    count = 0;
#endif
}

String::regex::regex(size_t size)
{
#ifdef  HAVE_REGEX_H
    count = size;
    results = (regmatch_t *)malloc(sizeof(regmatch_t) * size);
    object = NULL;
#else
    object = results = NULL;
    count = 0;
#endif
}

String::regex& String::regex::operator=(const char *pattern)
{
#ifdef  HAVE_REGEX_H
    if(object) {
        regfree((regex_t *)object);
        free(object);
    }
    regex_t *r = (regex_t *)malloc(sizeof(regex_t));
    if(regcomp(r, pattern, 0)) {
        regfree(r);
        free(r);
        r = NULL;
    }
    object = r;
#endif
    return *this;
}

bool String::regex::operator*=(const char *text)
{
    return match(text);
}

String::regex::~regex()
{
#ifdef  HAVE_REGEX_H
    if(object) {
        regfree((regex_t *)object);
        free(object);
    }
    if(results)
        free(results);
    object = results = NULL;
#endif
}

size_t String::regex::offset(unsigned member)
{
#ifdef  HAVE_REGEX_H
    if(!results)
        return (size_t)-1;

    regmatch_t *r = (regmatch_t *)results;

    if(member >= count)
        return (size_t)-1;
    return (size_t)r[member].rm_so;
#else
    return (size_t)-1;
#endif
}

size_t String::regex::size(unsigned member)
{
#ifdef  HAVE_REGEX_H
    if(!results)
        return 0;

    regmatch_t *r = (regmatch_t *)results;

    if(member >= count)
        return (size_t)-1;

    if(r[member].rm_so == -1)
        return 0;

    return (size_t)(r[member].rm_eo - r[member].rm_so);
#else
    return (size_t)0;
#endif
}

bool String::regex::match(const char *text, unsigned mode)
{
#ifdef  HAVE_REGEX_H
    int flags = 0;

    if((mode & 0x01) == INSENSITIVE)
        flags |= REG_ICASE;

    if(!text || !object || !results)
        return false;

    if(regexec((regex_t *)object, text, count, (regmatch_t *)results, flags))
        return false;

    return true;
#else
    return false;
#endif
}

const char *String::search(regex& expr, unsigned member, unsigned flags) const
{
    if(!str)
        return NULL;

#ifdef  HAVE_REGEX_H
    if(expr.match(str->text, flags))
        return NULL;

    if(member >= expr.members())
        return NULL;

    if(expr.size(member) == 0)
        return NULL;

    return str->text + expr.offset(member);
#else
    return NULL;
#endif
}

unsigned String::replace(regex& expr, const char *cp, unsigned flags)
{
#ifdef  HAVE_REGEX_H
    size_t cpl = 0;

    if(cp)
        cpl = strlen(cp);

    if(!str || str->len == 0)
        return 0;

    if(expr.match(str->text, flags))
        return 0;

    ssize_t adjust = 0;
    unsigned member = 0;

    while(member < expr.members()) {
        ssize_t tcl = expr.size(member);
        ssize_t offset = (expr.offset(member) + adjust);
        if(!tcl)
            break;

        ++member;
        cut(offset, tcl);
        if(cpl) {
            paste(offset, cp);
            adjust += (cpl - tcl);
        }
    }
    return member;
#else
    return 0;
#endif
}

bool String::operator*=(regex& expr)
{
    if(search(expr))
        return true;

    return false;
}

unsigned StringPager::split(stringex_t& expr, const char *string, unsigned flags)
{
    strdup_t tmp = String::dup(string);
    int prior = 0, match = 0;
    size_t tcl = strlen(string);
    unsigned count = 0, member = 0;

    if(!expr.match(string, flags))
        return 0;

    while(member < expr.members()) {
        if(!expr.size(member))
            break;
        match = (int)expr.offset(member++);
        if(match > prior) {
            tmp[match] = 0;
            add(tmp + (size_t)prior);
            ++count;
        }
        prior = (int)(match + tcl);
    }
    if(tmp[prior]) {
        add(tmp + (size_t)prior);
        ++count;
    }
    return count;
}

} // namespace ucommon
