/*-
 * Copyright (c) 2016 UPLEX Nils Goroll Systemoptimierung
 * All rights reserved
 *
 * Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
 *
 * 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 THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR 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 "vre2.h"

#define CATCHALL                                \
        catch (const runtime_error& err) {      \
                return err.what();              \
        }                                       \
        catch (const exception& ex) {           \
                return ex.what();               \
        }                                       \
        catch (...) {                           \
                return "Unknown error";         \
        }

using namespace std;

vre2::vre2(const char *pattern, RE2::Options * const opt) {
	re_ = new RE2(pattern, *opt);
	if (!re_->ok())
		throw runtime_error(re_->error());
	named_group = re_->NamedCapturingGroups();
}

vre2::~vre2() {
	if (re_) {
		delete re_;
		re_ = NULL;
	}
}

inline bool
vre2::match(const char *subject, const size_t len, const int ngroups,
    StringPiece* groups) const
{
	return re_->Match(subject, 0, len, RE2::UNANCHORED, groups, ngroups);
}

inline int
vre2::ngroups() const
{
	return re_->NumberOfCapturingGroups();
}

inline int
vre2::get_group(const char * const name) const
{
	try {
		return named_group.at(name);
	}
	catch(const out_of_range& ex) {
		return -1;
	}
}

inline bool
vre2::replace(string *text, const char * const rewrite) const
{
	return RE2::Replace(text, *re_, rewrite);
}

inline bool
vre2::global_replace(string *text, const char * const rewrite) const
{
	return RE2::GlobalReplace(text, *re_, rewrite);
}

inline bool
vre2::extract(string *out, const char * const text, const char * const rewrite)
	const
{
	return RE2::Extract(text, *re_, rewrite, out);
}

inline int
vre2::size() const
{
	return re_->ProgramSize();
}

const char *
vre2_init(vre2 **vre2p, const char *pattern, unsigned utf8,
    unsigned posix_syntax, unsigned longest_match, long max_mem,
    unsigned literal, unsigned never_nl, unsigned dot_nl,
    unsigned never_capture, unsigned case_sensitive, unsigned perl_classes,
    unsigned word_boundary, unsigned one_line)
{
	try {
		RE2::Options opt;
		opt.set_log_errors(false);
		if (utf8)
			opt.set_encoding(RE2::Options::EncodingUTF8);
		else
			opt.set_encoding(RE2::Options::EncodingLatin1);
		opt.set_posix_syntax(posix_syntax);
		opt.set_longest_match(longest_match);
		opt.set_max_mem(max_mem);
		opt.set_literal(literal);
		opt.set_never_nl(never_nl);
		opt.set_dot_nl(dot_nl);
		opt.set_never_capture(never_capture);
		opt.set_case_sensitive(case_sensitive);
		opt.set_perl_classes(perl_classes);
		opt.set_word_boundary(word_boundary);
		opt.set_one_line(one_line);
		*vre2p = new vre2(pattern, &opt);
		return NULL;
	}
	CATCHALL
}

size_t
vre2_matchsz(void)
{
	return sizeof(StringPiece);
}

const char *
vre2_match(vre2 *vre2, const char * const subject, const size_t len,
    int * const match, const int ngroups, void * const group)
{
	try {
		StringPiece* g = reinterpret_cast<StringPiece *>(group);
		*match = vre2->match(subject, len, ngroups, g);
		return NULL;
	}
	CATCHALL
}

const char *
vre2_capture(void *group, const int refnum, const char ** const capture,
    int * const len)
{
	try {
		StringPiece* g = reinterpret_cast<StringPiece *>(group);
		StringPiece str = g[refnum];
		*capture = str.data();
		*len = str.length();
		return NULL;
	}
	CATCHALL
}

const char *
vre2_ngroups(vre2 *vre2, int * const ngroups)
{
	try {
		*ngroups = vre2->ngroups();
		return NULL;
	}
	CATCHALL
}

const char *
vre2_get_group(vre2 *vre2, const char * const name, int * const refnum)
{
	try {
		*refnum = vre2->get_group(name);
		return NULL;
	}
	CATCHALL
}

const char *
vre2_rewrite(vre2 *vre2, const rewrite_e mode, const char * const text,
    const char * const rewrite, char * const dest, const size_t bytes,
    int * const match, size_t * const len)
{
	try {
		string result;

		switch(mode) {
		case SUB:
			result = text;
			*match = vre2->replace(&result, rewrite);
			break;
		case SUBALL:
			result = text;
			*match = vre2->global_replace(&result, rewrite);
			break;
		case EXTRACT:
			*match = vre2->extract(&result, text, rewrite);
			break;
		default:
			throw runtime_error("illegal mode");
		}

		if (!*match)
			return NULL;
		if (result.size() + 1 > bytes)
			throw runtime_error("insufficient workspace");
		*len = result.size();
		result.copy(dest, *len);
		dest[*len] = '\0';
		return NULL;
	}
	CATCHALL
}

const char *
vre2_quotemeta(const char * const unquoted, char * const dest,
    const size_t bytes, size_t * const len)
{
	try {
		string result;

		result = RE2::QuoteMeta(unquoted);
		if (result.size() + 1 > bytes)
			throw runtime_error("insufficient workspace");
		*len = result.size();
		result.copy(dest, *len);
		dest[*len] = '\0';
		return NULL;
	}
	CATCHALL
}

const char *
vre2_cost(vre2 *vre2, int *cost)
{
	try {
		*cost = vre2->size();
		return NULL;
	}
	CATCHALL
}

const char *
vre2_fini(vre2 **vre2)
{
	try {
		delete *vre2;
		return NULL;
	}
	CATCHALL
}
