// SPDX-FileCopyrightText: 2023 Rot127 <rot127@posteo.com>
// SPDX-License-Identifier: LGPL-3.0-only

#define PCRE2_STATIC
#define PCRE2_CODE_UNIT_WIDTH 0
#include <pcre2.h>

#include <rz_util/rz_strbuf.h>
#include <rz_vector.h>
#include <rz_util/rz_regex.h>
#include <rz_types.h>
#include <rz_util/rz_assert.h>
#include <rz_util.h>
#include <rz_types.h>

// Default JIT stack size is 32KiB.
// But the wildcard pattern we have for strings takes more.
#define RZ_REGEX_JIT_STACK_MIN (32 * 1024)
// 1MiB are documented as reasoable maxima.
#define RZ_REGEX_JIT_STACK_MAX (1024 * 1024)

typedef pcre2_general_context_8 RzRegexGeneralContext8; ///< General context.
// typedef pcre2_compile_context RzRegexCompContext; ///< The context for compiling.
typedef pcre2_match_context_8 RzRegexMatchContext8; ///< The context for matching.

typedef struct {
	RzRegexGeneralContext8 *general;
	RzRegexCompContext *compile;
	RzRegexMatchContext8 *match;
} RzRegexContexts8;

static void check_faulty_pcre2_word_width(size_t pcre2_word_width) {
	if (pcre2_word_width != 8 && pcre2_word_width != 16 && pcre2_word_width != 32) {
		RZ_LOG_FATAL("PCRE2 word with of %" PFMTSZu " is not supported.\n", pcre2_word_width);
		abort();
	}
}

static void print_pcre2_err(RZ_NULLABLE const char *utf8_pattern, RzRegexStatus err_num, size_t err_off) {
	PCRE2_UCHAR8 buffer[256];
	pcre2_get_error_message_8(err_num, buffer, sizeof(buffer));
	RZ_LOG_ERROR("Regex compilation for '%s' failed at %" PFMTSZu ": %s\n", utf8_pattern ? utf8_pattern : "(null)", err_off,
		buffer);
}

static RZ_OWN void *regex_new(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags,
	RzRegexCompContext *ccontext, size_t pcre2_word_width) {
	rz_return_val_if_fail(pattern, NULL);
	check_faulty_pcre2_word_width(pcre2_word_width);
	if (jflags == RZ_REGEX_DEFAULT) {
		jflags = PCRE2_JIT_COMPLETE;
	}

	RzRegexStatus err_num;
	RzRegexSize err_off;
	ut32 supported = 0;
	if (pcre2_word_width == 8) {
		pcre2_config_8(PCRE2_CONFIG_UNICODE, &supported);
	} else if (pcre2_word_width == 16) {
		pcre2_config_16(PCRE2_CONFIG_UNICODE, &supported);
	} else {
		pcre2_config_32(PCRE2_CONFIG_UNICODE, &supported);
	}
	if (supported != 1) {
		RZ_LOG_ERROR("Unicode not supported by PCRE2 library.\n");
		return NULL;
	}
	char *fixed_pat = NULL;
	const char *pat = NULL;
	if (((cflags & RZ_REGEX_EXTENDED) || (cflags & RZ_REGEX_EXTENDED_MORE))) {
		// In PCRE2 with the extended flag set, ascii space characters ' ' are skipped.
		// We need to replace them with \s unfortunately to keep our API stable.
		if (!strchr(pattern, ' ')) {
			pat = pattern;
		} else {
			fixed_pat = rz_str_replace(rz_str_dup(pattern), " ", "\\s", 1);
			pat = fixed_pat;
		}
	} else {
		pat = pattern;
	}
	if (!(cflags & PCRE2_LITERAL)) {
		cflags |= PCRE2_UCP;
	}

	void *regex;
	if (pcre2_word_width == 8) {
		regex = pcre2_compile_8(
			(PCRE2_SPTR8)pat,
			PCRE2_ZERO_TERMINATED,
			cflags | PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF,
			&err_num,
			&err_off,
			ccontext);
		if (!regex) {
			print_pcre2_err(pat, err_num, err_off);
			free(fixed_pat);
			return NULL;
		}
#ifdef SUPPORTS_PCRE2_JIT
		RzRegexStatus jit_err = pcre2_jit_compile_8(regex, jflags);

		if (jit_err < 0) {
			print_pcre2_err(pat, jit_err, 0);
		}
#endif
	} else if (pcre2_word_width == 16) {
		ut16 *utf16_pat = rz_str_utf8_to_utf16(pat, RZ_HOST_IS_BIG_ENDIAN);
		regex = pcre2_compile_16(
			(PCRE2_SPTR16)utf16_pat,
			PCRE2_ZERO_TERMINATED,
			cflags | PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF,
			&err_num,
			&err_off,
			ccontext);
		if (!regex) {
			print_pcre2_err(pat, err_num, err_off);
			RZ_LOG_WARN("PCRE2 pattern offset might be invalid, due to different encoding.\n");
			free(fixed_pat);
			free(utf16_pat);
			return NULL;
		}
		free(utf16_pat);
#ifdef SUPPORTS_PCRE2_JIT
		RzRegexStatus jit_err = pcre2_jit_compile_16(regex, jflags);
		if (jit_err < 0) {
			print_pcre2_err(pat, jit_err, 0);
		}
#endif
	} else {
		ut32 *utf32_pat = rz_str_utf8_to_utf32(pat, RZ_HOST_IS_BIG_ENDIAN);
		regex = pcre2_compile_32(
			(PCRE2_SPTR32)utf32_pat,
			PCRE2_ZERO_TERMINATED,
			cflags | PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF,
			&err_num,
			&err_off,
			ccontext);
		if (!regex) {
			print_pcre2_err(pat, err_num, err_off / 4);
			free(fixed_pat);
			free(utf32_pat);
			return NULL;
		}
		free(utf32_pat);
#ifdef SUPPORTS_PCRE2_JIT
		RzRegexStatus jit_err = pcre2_jit_compile_32(regex, jflags);
		if (jit_err < 0) {
			print_pcre2_err(pat, jit_err, 0);
		}
#endif
	}
	free(fixed_pat);
	return regex;
}

/**
 * \brief Compile a regex pattern to a RzRegex and return it.
 * In case of an error, an error message is printed and NULL is returned.
 *
 * NOTE: If RZ_REGEX_EXTENDED is passed, spaces in the pattern will **not** be skipped!
 * This is contrary to the PCRE2 documentation. But keeps our internal regex usage stable.
 *
 * \param pattern The regex pattern string.
 * \param cflags The compilation flags or zero for default.
 *        PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF and PCRE2_UCP are enforced currently.
 * \param jflags The compilation flags for the JIT compiler.
 *        You can pass RZ_REGEX_JIT_PARTIAL_SOFT or RZ_REGEX_JIT_PARTIAL_HARD if you
 *        intend to use the pattern for partial matching. Otherwise set it to 0.
 * \param ccontext A compile context or NULL.
 *
 * \return The compiled regex or NULL in case of failure.
 */
RZ_API RZ_OWN RzRegex *rz_regex_new(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags,
	RzRegexCompContext *ccontext) {
	rz_return_val_if_fail(pattern, NULL);
	return regex_new(pattern, cflags, jflags, ccontext, 8);
}

/**
 * \brief Compile an UTF-16 regex pattern to a RzRegex16 and return it.
 * In case of an error, an error message is printed and NULL is returned.
 *
 * NOTE: The pattern and matching will always be in the host's endianness.
 * NOTE: If RZ_REGEX_EXTENDED is passed, spaces in the pattern **will** be skipped!
 * This is the opposite behavior to the default UTF-8 regular expressions.
 *
 * \param pattern The regex pattern string. It must be an UTF-8 encoded string.
 * \param cflags The compilation flags or zero for default.
 *        PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF and PCRE2_UCP are enforced currently.
 * \param jflags The compilation flags for the JIT compiler.
 *        You can pass RZ_REGEX_JIT_PARTIAL_SOFT or RZ_REGEX_JIT_PARTIAL_HARD if you
 *        intend to use the pattern for partial matching. Otherwise set it to 0.
 * \param ccontext A compile context or NULL.
 *
 * \return The compiled regex or NULL in case of failure.
 */
RZ_API RZ_OWN RzRegex16 *rz_regex_new_16(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags,
	RzRegexCompContext16 *ccontext) {
	rz_return_val_if_fail(pattern, NULL);
	return regex_new(pattern, cflags, jflags, ccontext, 16);
}

/**
 * \brief Compile an UTF-32 regex pattern to a RzRegex32 and return it.
 * In case of an error, an error message is printed and NULL is returned.
 *
 * NOTE: The pattern and matching will always be in the host's endianness.
 *
 * \param pattern The regex pattern string. It must be an UTF-8 encoded string.
 * \param cflags The compilation flags or zero for default.
 *        PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF and PCRE2_UCP are enforced currently.
 * \param jflags The compilation flags for the JIT compiler.
 *        You can pass RZ_REGEX_JIT_PARTIAL_SOFT or RZ_REGEX_JIT_PARTIAL_HARD if you
 *        intend to use the pattern for partial matching. Otherwise set it to 0.
 * \param ccontext A compile context or NULL.
 *
 * \return The compiled regex or NULL in case of failure.
 */
RZ_API RZ_OWN RzRegex32 *rz_regex_new_32(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags,
	RzRegexCompContext32 *ccontext) {
	rz_return_val_if_fail(pattern, NULL);
	return regex_new(pattern, cflags, jflags, ccontext, 32);
}

/**
 * \brief Compile an Regex pattern of \p type.
 *
 * NOTE: The pattern and matching will always be in the host's endianness.
 * NOTE: The patterns are compiled with PCRE2_NEWLINE_NUL.
 *
 * \param pattern The regex pattern string. It must be an UTF-8 encoded string.
 * \param cflags The compilation flags or zero for default.
 *        PCRE2_UCP | PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF are enforced currently.
 * \param jflags The compilation flags for the JIT compiler.
 *        You can pass RZ_REGEX_JIT_PARTIAL_SOFT or RZ_REGEX_JIT_PARTIAL_HARD if you
 *        intend to use the pattern for partial matching. Otherwise set it to RZ_REGEX_DEFAULT.
 * \param type The string encoding type the pattern should match.
 *
 * \return The compiled regex or NULL in case of failure.
 */
RZ_API RZ_OWN RzRegexMulti *rz_regex_new_multi(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags, RzRegexType type) {
	RzRegexMulti *re = RZ_NEW0(RzRegexMulti);
	if (!re) {
		return NULL;
	}
	if (jflags == RZ_REGEX_DEFAULT) {
		jflags = PCRE2_JIT_COMPLETE;
	}
	re->re_type = type;
	re->compile_flags_jit = jflags;
	switch (type) {
	default:
		rz_warn_if_reached();
		free(re);
		return NULL;
	case RZ_REGEX_UTF8: {
		RzRegexCompContext *ccontext = pcre2_compile_context_create_8(NULL);
		pcre2_set_newline_8(ccontext, PCRE2_NEWLINE_NUL);
		re->re8 = rz_regex_new(pattern, cflags, jflags, ccontext);
		re->ccontext = ccontext;
		break;
	}
	case RZ_REGEX_UTF16: {
		RzRegexCompContext *ccontext = pcre2_compile_context_create_16(NULL);
		pcre2_set_newline_16(ccontext, PCRE2_NEWLINE_NUL);
		re->re16 = rz_regex_new_16(pattern, cflags, jflags, ccontext);
		re->ccontext = ccontext;
		break;
	}
	case RZ_REGEX_UTF32: {
		RzRegexCompContext *ccontext = pcre2_compile_context_create_32(NULL);
		pcre2_set_newline_32(ccontext, PCRE2_NEWLINE_NUL);
		re->re32 = rz_regex_new_32(pattern, cflags, jflags, ccontext);
		re->ccontext = ccontext;
		break;
	}
	}
	if (!re->re8 && !re->re16 && !re->re32) {
		free(re);
		return NULL;
	}
#ifdef SUPPORTS_PCRE2_JIT
	switch (type) {
	default:
		rz_warn_if_reached();
		return NULL;
	case RZ_REGEX_UTF8:
		re->jit_stack = pcre2_jit_stack_create_8(RZ_REGEX_JIT_STACK_MIN, RZ_REGEX_JIT_STACK_MAX, NULL);
		break;
	case RZ_REGEX_UTF16:
		re->jit_stack = pcre2_jit_stack_create_16(RZ_REGEX_JIT_STACK_MIN, RZ_REGEX_JIT_STACK_MAX, NULL);
		break;
	case RZ_REGEX_UTF32:
		re->jit_stack = pcre2_jit_stack_create_32(RZ_REGEX_JIT_STACK_MIN, RZ_REGEX_JIT_STACK_MAX, NULL);
		break;
	}
	if (!re->jit_stack) {
		rz_warn_if_reached();
		rz_regex_free_multi(re);
		return NULL;
	}
#endif
	return re;
}

/**
 * \brief Compile a regex pattern with raw bytes.
 * In case of an error, an error message is printed and NULL is returned.
 *
 * Unlike `rz_regex_new()` it doesn't compile the pattern with the PCRE2_UTF flag.
 * So any byte can be part of a pattern, not just UTF-8 compatible ones.
 *
 * \param pattern The pattern. Bytes must not be escaped.
 * \param pattern_len The pattern length.
 * \param cflags The compilation flags.
 * \param jflags The compilation flags for the JIT compiler.
 *        You can pass RZ_REGEX_JIT_PARTIAL_SOFT or RZ_REGEX_JIT_PARTIAL_HARD if you
 *        intend to use the pattern for partial matching. Otherwise set it to 0.
 * \param ccontext A compile context or NULL.
 *
 * \return The compiled regex or NULL in case of failure.
 */
RZ_API RZ_OWN RzRegex *rz_regex_new_bytes(RZ_NONNULL const ut8 *pattern, size_t pattern_len, RzRegexFlags cflags, RzRegexFlags jflags,
	RzRegexCompContext *ccontext) {
	rz_return_val_if_fail(pattern, NULL);
	if (jflags == RZ_REGEX_DEFAULT) {
		jflags = PCRE2_JIT_COMPLETE;
	}

	RzRegexStatus err_num;
	RzRegexSize err_off;

	RzRegex *regex = pcre2_compile_8(
		pattern,
		pattern_len,
		cflags,
		&err_num,
		&err_off,
		ccontext);
	if (!regex) {
		print_pcre2_err((const char *)pattern, err_num, err_off);
		return NULL;
	}
#ifdef SUPPORTS_PCRE2_JIT
	RzRegexStatus jit_err = pcre2_jit_compile_8(regex, jflags);
	if (jit_err < 0) {
		print_pcre2_err((const char *)pattern, jit_err, 0);
	}
#endif
	return regex;
}

/**
 * \brief Frees a given RzRegex.
 *
 * \param regex The RzRegex to free.
 */
RZ_API void rz_regex_free(RZ_OWN RzRegex *regex) {
	pcre2_code_free_8(regex);
}

/**
 * \brief Frees a given RzRegex16.
 *
 * \param regex The RzRegex16 to free.
 */
RZ_API void rz_regex_free_16(RZ_OWN RzRegex16 *regex) {
	pcre2_code_free_8(regex);
}

/**
 * \brief Frees a given RzRegex32.
 *
 * \param regex The RzRegex32 to free.
 */
RZ_API void rz_regex_free_32(RZ_OWN RzRegex32 *regex) {
	pcre2_code_free_8(regex);
}

/**
 * \brief Makes a clone of the given multi \p regex object.
 *
 * NOTE: Cloning is only performed if the library is build with JIT support.
 * JIT patterns cannot be used by multiple threads.
 * Patterns without JIT matching can be used by multiple threads.
 * So JIT compiled patterns need to be clonedbefore being used
 * in a thread save manner.
 *
 * After usage the returned pointer should always
 * be freed via rz_regex_free_multi_clone().
 *
 * \param regex The multi regex to clone.
 * \param clone_jit If set, the clone will also have JIT pattern matching.
 *        If unset it still clones the pattern, but the clone won't support
 *        JIT pattern matching.
 *
 * \return The clone for \p regex or NULL in case of failure.
 */
RZ_API RZ_OWN RzRegexMulti *rz_regex_multi_clone(RZ_NONNULL RzRegexMulti *regex, bool clone_jit) {
	rz_return_val_if_fail(regex, NULL);
#ifndef SUPPORTS_PCRE2_JIT
	return regex;
#else
	RzRegexMulti *clone = RZ_NEW0(RzRegexMulti);
	if (!clone) {
		return NULL;
	}
	clone->re_type = regex->re_type;
	clone->compile_flags_jit = regex->compile_flags_jit;
	switch (regex->re_type) {
	case RZ_REGEX_UTF8:
		clone->re8 = pcre2_code_copy_with_tables_8(regex->re8);
		if (clone_jit) {
			pcre2_jit_compile_8(clone->re8, clone->compile_flags_jit);
		}
		break;
	case RZ_REGEX_UTF16:
		clone->re16 = pcre2_code_copy_with_tables_16(regex->re16);
		if (clone_jit) {
			pcre2_jit_compile_16(clone->re16, clone->compile_flags_jit);
		}
		break;
	case RZ_REGEX_UTF32:
		clone->re32 = pcre2_code_copy_with_tables_32(regex->re32);
		if (clone_jit) {
			pcre2_jit_compile_32(clone->re32, clone->compile_flags_jit);
		}
		break;
	}
	if (!(clone->re8 || clone->re16 || clone->re32)) {
		free(clone);
		return NULL;
	}
	return clone;
#endif
}

/**
 * \brief Frees a cloned RzRegexMulti object.
 * Only RzRegexMulti returned by rz_regex_clone_multi() should be passed here.
 *
 * This function behaves differently if Rizin is compiled with or without
 * JIT support. Without JIT support enabled, it will just return.
 * With JIT support it will free the \p regex.
 *
 * Please see rz_regex_clone_multi() for details.
 *
 * \param regex The cloned multi regex to free.
 */
RZ_API void rz_regex_free_multi_clone(RZ_NULLABLE RZ_OWN RzRegexMulti *regex) {
#ifndef SUPPORTS_PCRE2_JIT
	return;
#else
	rz_regex_free_multi(regex);
#endif
}

/**
 * \brief Frees a given RzRegexMulti.
 *
 * \param regex The RzRegexMulti to free.
 */
RZ_API void rz_regex_free_multi(RZ_NULLABLE RZ_OWN RzRegexMulti *regex_multi) {
	if (!regex_multi) {
		return;
	}
	switch (regex_multi->re_type) {
	case RZ_REGEX_UTF8:
		rz_regex_free(regex_multi->re8);
		pcre2_compile_context_free_8(regex_multi->ccontext);
		break;
	case RZ_REGEX_UTF16:
		rz_regex_free_16(regex_multi->re16);
		pcre2_compile_context_free_16(regex_multi->ccontext);
		break;
	case RZ_REGEX_UTF32:
		rz_regex_free_32(regex_multi->re32);
		pcre2_compile_context_free_32(regex_multi->ccontext);
		break;
	}
#ifdef SUPPORTS_PCRE2_JIT
	switch (regex_multi->re_type) {
	case RZ_REGEX_UTF8:
		pcre2_jit_stack_free_8(regex_multi->jit_stack);
		break;
	case RZ_REGEX_UTF16:
		pcre2_jit_stack_free_16(regex_multi->jit_stack);
		break;
	case RZ_REGEX_UTF32:
		pcre2_jit_stack_free_32(regex_multi->jit_stack);
		break;
	}
#endif
	free(regex_multi);
}

static void rz_regex_match_data_free(RZ_OWN RzRegexMatchData *match_data) {
	pcre2_match_data_free_8(match_data);
}

/**
 * \brief Matches the \p regex in the \p text and returns a status code with the result.
 *
 * \param regex The regex pattern to match.
 * \param text The text to search in.
 * \param text_size The length of the buffer pointed to by \p text.
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts.
 * \param mflags Match flags. PCRE2_NO_UTF_CHECK is enforced currently.
 *
 * \return A status code which describes the result.
 */
RZ_API RzRegexStatus rz_regex_match(RZ_NONNULL const RzRegex *regex, RZ_NONNULL const char *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	rz_return_val_if_fail(regex && text, RZ_REGEX_ERROR_NOMATCH);

	pcre2_match_data_8 *mdata = pcre2_match_data_create_from_pattern_8(regex, NULL);
	RzRegexStatus rc = pcre2_match_8(regex, (PCRE2_SPTR8)text, text_size, text_offset, mflags | PCRE2_NO_UTF_CHECK, mdata, NULL);
	pcre2_match_data_free_8(mdata);
	return rc;
}

/**
 * \brief Matches the \p regex in the \p text and returns a status code with the result.
 *
 * NOTE: This matches against UTF-16 strings (host-endianness).
 *
 * \param regex The regex pattern to match.
 * \param text The text to search in. UTF-16 encoded in the host's endianness.
 * \param text_size The length of the buffer pointed to by \p text.
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts.
 * \param mflags Match flags. PCRE2_NO_UTF_CHECK is enforced currently.
 *
 * \return A status code which describes the result.
 */
RZ_API RzRegexStatus rz_regex_match_16(RZ_NONNULL const RzRegex16 *regex, RZ_NONNULL const ut16 *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	rz_return_val_if_fail(regex && text, RZ_REGEX_ERROR_NOMATCH);

	pcre2_match_data_16 *mdata = pcre2_match_data_create_from_pattern_16(regex, NULL);
	RzRegexStatus rc = pcre2_match_16(regex, (PCRE2_SPTR16)text, text_size, text_offset, mflags | PCRE2_NO_UTF_CHECK, mdata, NULL);
	pcre2_match_data_free_16(mdata);
	return rc;
}

/**
 * \brief Matches the \p regex in the \p text and returns a status code with the result.
 *
 * NOTE: This matches against UTF-32 strings (host-endianness).
 *
 * \param regex The regex pattern to match.
 * \param text The text to search in. UTF-32 encoded in the host's endianness.
 * \param text_size The length of the buffer pointed to by \p text.
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts.
 * \param mflags Match flags. PCRE2_NO_UTF_CHECK is enforced currently.
 *
 * \return A status code which describes the result.
 */
RZ_API RzRegexStatus rz_regex_match_32(RZ_NONNULL const RzRegex32 *regex, RZ_NONNULL const ut32 *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	rz_return_val_if_fail(regex && text, RZ_REGEX_ERROR_NOMATCH);

	pcre2_match_data_32 *mdata = pcre2_match_data_create_from_pattern_32(regex, NULL);
	RzRegexStatus rc = pcre2_match_32(regex, (PCRE2_SPTR32)text, text_size, text_offset, mflags | PCRE2_NO_UTF_CHECK, mdata, NULL);
	pcre2_match_data_free_32(mdata);
	return rc;
}

/**
 * \brief Generates the error message to \p errcode.
 *
 * \param errcode The error code.
 * \param errbuf The error message buffer.
 * \param errbuf_size The error message buffer size in bytes.
 */
RZ_API void rz_regex_error_msg(RzRegexStatus errcode, RZ_OUT char *errbuf, RzRegexSize errbuf_size) {
	pcre2_get_error_message_8(errcode, (PCRE2_UCHAR8 *)errbuf, errbuf_size);
}

/**
 * \brief Returns the name of a group.
 *
 * \param regex The regex expression with named groups.
 * \param group_idx The index of the group to get the name for.
 *
 * \return The name of the group or NULL in case of failure or non is was set.
 */
RZ_API const ut8 *rz_regex_get_match_name(RZ_NONNULL const RzRegex *regex, ut32 group_idx) {
	rz_return_val_if_fail(regex, NULL);

	ut32 namecount;
	ut32 name_entry_size;
	PCRE2_SPTR8 nametable_ptr;

	pcre2_pattern_info_8(
		regex,
		PCRE2_INFO_NAMECOUNT,
		&namecount);

	pcre2_pattern_info_8(
		regex,
		PCRE2_INFO_NAMETABLE,
		&nametable_ptr);

	pcre2_pattern_info_8(
		regex,
		PCRE2_INFO_NAMEENTRYSIZE,
		&name_entry_size);

	for (size_t i = 0; i < namecount; i++) {
		int n = (nametable_ptr[0] << 8) | nametable_ptr[1];
		if (n == group_idx) {
			return nametable_ptr + 2;
		}
		nametable_ptr += name_entry_size;
	}
	return NULL;
}

/**
 * \brief Returns the index of a group in the pattern \p regex.
 *
 * \param regex The regex expression with named groups.
 * \param group The group name to get the index for.
 *
 * \return The index of the group or RZ_REGEX_ERROR_NOMATCH in case of failure or if no name was given.
 */
RZ_API RzRegexStatus rz_regex_get_group_idx_by_name(RZ_NONNULL const RzRegex *regex, const char *group) {
	rz_return_val_if_fail(regex, RZ_REGEX_ERROR_NOMATCH);

	ut32 namecount;
	ut32 name_entry_size;
	PCRE2_SPTR8 nametable_ptr;

	pcre2_pattern_info_8(
		regex,
		PCRE2_INFO_NAMECOUNT,
		&namecount);

	pcre2_pattern_info_8(
		regex,
		PCRE2_INFO_NAMETABLE,
		&nametable_ptr);

	pcre2_pattern_info_8(
		regex,
		PCRE2_INFO_NAMEENTRYSIZE,
		&name_entry_size);

	for (size_t i = 0; i < namecount; i++) {
		int n = (nametable_ptr[0] << 8) | nametable_ptr[1];
		if (RZ_STR_EQ((const char *)nametable_ptr + 2, group)) {
			return n;
		}
		nametable_ptr += name_entry_size;
	}
	return RZ_REGEX_ERROR_NOMATCH;
}

static RZ_OWN RzPVector /*<RzRegexMatch *>*/ *match_first_8(
	RZ_NONNULL const void *regex,
	RZ_NONNULL const ut8 *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags,
	RZ_NULLABLE void *jit_stack) {
	rz_return_val_if_fail(regex && text, NULL);

	RzPVector *matches = NULL;
	RzRegexMatchData *mdata = NULL;
	mdata = pcre2_match_data_create_from_pattern_8(regex, NULL);

	pcre2_match_context_8 *mcontext = NULL;
#ifdef SUPPORTS_PCRE2_JIT
	if (jit_stack) {
		mcontext = pcre2_match_context_create_8(NULL);
		if (!mcontext) {
			goto fini;
		}
		pcre2_jit_stack_assign_8(mcontext, NULL, jit_stack);
	}
#endif
	RzRegexStatus rc = 0;
	rc = pcre2_match_8(regex, (PCRE2_SPTR8)text, text_size, text_offset, mflags | PCRE2_NO_UTF_CHECK, mdata, mcontext);

	if (rc == PCRE2_ERROR_NOMATCH) {
		// Nothing matched return empty vector.
		goto fini;
	}

	if (rc < 0) {
		// Some error happend. Inform the user.
		PCRE2_UCHAR8 buffer[256];
		pcre2_get_error_message_8(rc, buffer, sizeof(buffer));
		RZ_LOG_WARN("Regex matching failed: %s\n", buffer);
		goto fini;
	}

	// Add groups to vector
	PCRE2_SIZE *ovector;
	ovector = pcre2_get_ovector_pointer_8(mdata);

	ut32 name_entry_size;
	PCRE2_SPTR8 nametable_ptr8;

	pcre2_pattern_info_8(
		regex,
		PCRE2_INFO_NAMETABLE,
		&nametable_ptr8);

	pcre2_pattern_info_8(
		regex,
		PCRE2_INFO_NAMEENTRYSIZE,
		&name_entry_size);

	matches = rz_pvector_new(free);
	for (size_t i = 0; i < rc; i++) {
		if (ovector[2 * i] > ovector[2 * i + 1]) {
			// This happens for \K lookaround. We fail if used.
			// See pcre2demo.c for details.
			RZ_LOG_ERROR("Usage of \\K to set start of the pattern later than the end, is not implemented.\n");
			goto fini;
		}

		// Offset and length of match
		RzRegexMatch *match = RZ_NEW0(RzRegexMatch);
		match->start = ovector[2 * i];
		match->len = ovector[2 * i + 1] - match->start;
		match->group_idx = i;
		nametable_ptr8 += name_entry_size;
		rz_pvector_push(matches, match);
	}

fini:
	rz_regex_match_data_free(mdata);
	pcre2_match_context_free_8(mcontext);
	return matches;
}

static RZ_OWN RzPVector /*<RzRegexMatch *>*/ *match_first_16(
	RZ_NONNULL const void *regex,
	RZ_NONNULL const ut16 *text,
	RzRegexSize text_size_code_units,
	RzRegexSize text_offset_code_units,
	RzRegexFlags mflags,
	RZ_NULLABLE void *jit_stack) {
	rz_return_val_if_fail(regex && text, NULL);

	RzPVector *matches = NULL;
	RzRegexMatchData *mdata = NULL;
	mdata = pcre2_match_data_create_from_pattern_16(regex, NULL);

	pcre2_match_context_16 *mcontext = NULL;
#ifdef SUPPORTS_PCRE2_JIT
	if (jit_stack) {
		mcontext = pcre2_match_context_create_16(NULL);
		if (!mcontext) {
			goto fini;
		}
		pcre2_jit_stack_assign_16(mcontext, NULL, jit_stack);
	}
#endif
	RzRegexStatus rc = 0;
	rc = pcre2_match_16(regex, (PCRE2_SPTR16)text, text_size_code_units, text_offset_code_units, mflags | PCRE2_NO_UTF_CHECK, mdata, mcontext);

	if (rc == PCRE2_ERROR_NOMATCH) {
		// Nothing matched, return an empty vector
		goto fini;
	}

	if (rc < 0) {
		// Some error happend. Inform the user.
		PCRE2_UCHAR8 buffer[256];
		pcre2_get_error_message_8(rc, buffer, sizeof(buffer));
		RZ_LOG_WARN("Regex matching failed: %s\n", buffer);
		goto fini;
	}

	// Add groups to vector
	PCRE2_SIZE *ovector;
	ovector = pcre2_get_ovector_pointer_16(mdata);

	ut32 name_entry_size;
	PCRE2_SPTR16 nametable_ptr16;

	pcre2_pattern_info_16(
		regex,
		PCRE2_INFO_NAMETABLE,
		&nametable_ptr16);

	pcre2_pattern_info_16(
		regex,
		PCRE2_INFO_NAMEENTRYSIZE,
		&name_entry_size);

	matches = rz_pvector_new(free);
	for (size_t i = 0; i < rc; i++) {
		if (ovector[2 * i] > ovector[2 * i + 1]) {
			// This happens for \K lookaround. We fail if used.
			// See pcre2demo.c for details.
			RZ_LOG_ERROR("Usage of \\K to set start of the pattern later than the end, is not implemented.\n");
			goto fini;
		}

		// Offset and length of match
		RzRegexMatch *match = RZ_NEW0(RzRegexMatch);
		match->start = ovector[2 * i];
		match->len = ovector[2 * i + 1] - match->start;
		match->group_idx = i;
		nametable_ptr16 += name_entry_size;
		rz_pvector_push(matches, match);
	}

fini:
	rz_regex_match_data_free(mdata);
	pcre2_match_context_free_16(mcontext);
	return matches;
}

static RZ_OWN RzPVector /*<RzRegexMatch *>*/ *match_first_32(
	RZ_NONNULL const void *regex,
	RZ_NONNULL const ut32 *text,
	RzRegexSize text_size_code_units,
	RzRegexSize text_offset_code_units,
	RzRegexFlags mflags,
	RZ_NULLABLE void *jit_stack) {
	rz_return_val_if_fail(regex && text, NULL);

	RzPVector *matches = NULL;
	RzRegexMatchData *mdata = NULL;
	mdata = pcre2_match_data_create_from_pattern_32(regex, NULL);

	pcre2_match_context_32 *mcontext = NULL;
#ifdef SUPPORTS_PCRE2_JIT
	if (jit_stack) {
		mcontext = pcre2_match_context_create_32(NULL);
		if (!mcontext) {
			goto fini;
		}
		pcre2_jit_stack_assign_32(mcontext, NULL, jit_stack);
	}
#endif
	RzRegexStatus rc = 0;
	rc = pcre2_match_32(regex, (PCRE2_SPTR32)text, text_size_code_units, text_offset_code_units, mflags | PCRE2_NO_UTF_CHECK, mdata, mcontext);

	if (rc == PCRE2_ERROR_NOMATCH) {
		// Nothing matched return empty vector.
		goto fini;
	}

	if (rc < 0) {
		// Some error happend. Inform the user.
		PCRE2_UCHAR8 buffer[256];
		pcre2_get_error_message_8(rc, buffer, sizeof(buffer));
		RZ_LOG_WARN("Regex matching failed: %s\n", buffer);
		goto fini;
	}

	// Add groups to vector
	PCRE2_SIZE *ovector;
	ovector = pcre2_get_ovector_pointer_32(mdata);

	ut32 name_entry_size;
	PCRE2_SPTR32 nametable_ptr32;

	pcre2_pattern_info_32(
		regex,
		PCRE2_INFO_NAMETABLE,
		&nametable_ptr32);

	pcre2_pattern_info_32(
		regex,
		PCRE2_INFO_NAMEENTRYSIZE,
		&name_entry_size);

	matches = rz_pvector_new(free);
	for (size_t i = 0; i < rc; i++) {
		if (ovector[2 * i] > ovector[2 * i + 1]) {
			// This happens for \K lookaround. We fail if used.
			// See pcre2demo.c for details.
			RZ_LOG_ERROR("Usage of \\K to set start of the pattern later than the end, is not implemented.\n");
			goto fini;
		}

		// Offset and length of match
		RzRegexMatch *match = RZ_NEW0(RzRegexMatch);
		match->start = ovector[2 * i];
		match->len = ovector[2 * i + 1] - match->start;
		match->group_idx = i;
		nametable_ptr32 += name_entry_size;
		rz_pvector_push(matches, match);
	}

fini:
	rz_regex_match_data_free(mdata);
	pcre2_match_context_free_32(mcontext);
	return matches;
}

/**
 * \brief Finds the first match in a text and returns it as a pvector.
 * First element in the vector is always the whole match, the following possible groups.
 *
 * \param regex The regex pattern to match.
 * \param text The text to search in.
 * \param text_size The length of the buffer pointed to by \p text.
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts.
 * \param mflags Match flags.
 *
 * \return The matches as pvector. NULL in case of failure. Empty for no matches or regex related errors.
 */
RZ_API RZ_OWN RzPVector /*<RzRegexMatch *>*/ *rz_regex_match_first(
	RZ_NONNULL const RzRegex *regex,
	RZ_NONNULL const char *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	rz_return_val_if_fail(regex && text, NULL);
	return match_first_8(
		regex,
		(ut8 *)text,
		text_size,
		text_offset,
		mflags, NULL);
}

/**
 * \brief Finds the first match in a UTF-16 text and returns it as a pvector.
 * First element in the vector is always the whole match, the following possible groups.
 *
 * NOTE: This matches against UTF-16 host's endianness strings.
 *
 * \param regex The regex pattern to match.
 * \param text The text to search in. It should be UTF-16 (host-endianness) encoded.
 * \param text_size The length of the buffer pointed to by \p text in code units.
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts in code units.
 * \param mflags Match flags.
 *
 * \return The matches as pvector. NULL in case of failure. Empty for no matches or regex related errors.
 */
RZ_API RZ_OWN RzPVector /*<RzRegexMatch *>*/ *rz_regex_match_first_16(
	RZ_NONNULL const RzRegex16 *regex,
	RZ_NONNULL const ut16 *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	rz_return_val_if_fail(regex && text, NULL);
	return match_first_16(
		regex,
		text,
		text_size,
		text_offset,
		mflags, NULL);
}

/**
 * \brief Finds the first match in a UTF-32 text and returns it as a pvector.
 * First element in the vector is always the whole match, the following possible groups.
 *
 * NOTE: This matches against UTF-32 host's endianness strings.
 *
 * \param regex The regex pattern to match.
 * \param text The text to search in. It should be UTF-32 (host-endianness) encoded.
 * \param text_size The length of the buffer pointed to by \p text in code units.
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts in code units.
 * \param mflags Match flags.
 *
 * \return The matches as pvector. NULL in case of failure. Empty for no matches or regex related errors.
 */
RZ_API RZ_OWN RzPVector /*<RzRegexMatch *>*/ *rz_regex_match_first_32(
	RZ_NONNULL const RzRegex32 *regex,
	RZ_NONNULL const ut32 *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	rz_return_val_if_fail(regex && text, NULL);
	return match_first_32(
		regex,
		text,
		text_size,
		text_offset,
		mflags, NULL);
}

/**
 * \brief Finds all matches in a text and returns them as vector.
 * The result is a flat vector of matches. A single match with multiple
 * groups is simply appeneded to the resulting vector.
 *
 * \param regex The regex pattern to match.
 * \param text The text to search in.
 * \param text_size The length of the buffer pointed to by \p text.
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts.
 * \param mflags Match flags.
 *
 * \return A vector of all matches or NULL in case of failure.
 * Sub-groups of a match are appended after their main match.
 */
RZ_API RZ_OWN RzPVector /*<RzRegexMatch *>*/ *rz_regex_match_all_not_grouped(
	RZ_NONNULL const RzRegex *regex,
	RZ_NONNULL const char *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	rz_return_val_if_fail(regex && text, NULL);

	RzPVector *all_matches = rz_pvector_new(free);
	RzPVector *matches = rz_regex_match_first(regex, text, text_size, text_offset, mflags);
	while (matches && rz_pvector_len(matches) > 0) {
		RzRegexMatch *whole_match = rz_pvector_head(matches);
		text_offset = whole_match->start + whole_match->len;

		size_t mlen = rz_pvector_len(matches);
		for (size_t i = 0; i < mlen; ++i) {
			RzRegexMatch *m = rz_pvector_pop_front(matches);
			rz_pvector_push(all_matches, m);
		}
		rz_pvector_free(matches);
		// Search again after the whole first match.
		matches = rz_regex_match_first(regex, text, text_size, text_offset, mflags);
	}

	// Free last vector without matches.
	rz_pvector_free(matches);
	return all_matches;
}

static RZ_OWN RzPVector /*<RzVector<RzRegexMatch *> *>*/ *match_all_internal_8(
	RZ_NONNULL const RzRegex *regex,
	RZ_NONNULL const ut8 *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags,
	bool allow_overlap,
	RZ_NULLABLE void *jit_stack) {
	rz_return_val_if_fail(regex && text, NULL);

	RzPVector *all_matches = rz_pvector_new((RzPVectorFree)rz_pvector_free);
	RzPVector *matches = NULL;
	matches = match_first_8(regex, text, text_size, text_offset, mflags, jit_stack);
	while (matches && rz_pvector_len(matches) > 0) {
		rz_pvector_push(all_matches, matches);
		RzRegexMatch *m = rz_pvector_head(matches);
		// Search again after the last match.
		text_offset = allow_overlap ? m->start + 1 : m->start + m->len;
		matches = match_first_8(regex, text, text_size, text_offset, mflags, jit_stack);
	}

	// Free last vector without matches.
	rz_pvector_free(matches);
	return all_matches;
}

static RZ_OWN RzPVector /*<RzVector<RzRegexMatch *> *>*/ *match_all_internal_16(
	RZ_NONNULL const RzRegex *regex,
	RZ_NONNULL const ut16 *text,
	RzRegexSize text_size_code_units,
	RzRegexSize text_offset_code_units,
	RzRegexFlags mflags,
	bool allow_overlap,
	RZ_NULLABLE void *jit_stack) {
	rz_return_val_if_fail(regex && text, NULL);

	RzPVector *all_matches = rz_pvector_new((RzPVectorFree)rz_pvector_free);
	RzPVector *matches = NULL;
	matches = match_first_16(regex, text, text_size_code_units, text_offset_code_units, mflags, jit_stack);
	while (matches && rz_pvector_len(matches) > 0) {
		rz_pvector_push(all_matches, matches);
		RzRegexMatch *m = rz_pvector_head(matches);
		// Search again after the last match.
		text_offset_code_units = allow_overlap ? m->start + 1 : m->start + m->len;
		matches = match_first_16(regex, text, text_size_code_units, text_offset_code_units, mflags, jit_stack);
	}

	// Free last vector without matches.
	rz_pvector_free(matches);
	return all_matches;
}

static RZ_OWN RzPVector /*<RzVector<RzRegexMatch *> *>*/ *match_all_internal_32(
	RZ_NONNULL const RzRegex *regex,
	RZ_NONNULL const ut32 *text,
	RzRegexSize text_size_code_units,
	RzRegexSize text_offset_code_units,
	RzRegexFlags mflags,
	bool allow_overlap,
	RZ_NULLABLE void *jit_stack) {
	rz_return_val_if_fail(regex && text, NULL);

	RzPVector *all_matches = rz_pvector_new((RzPVectorFree)rz_pvector_free);
	RzPVector *matches = NULL;
	matches = match_first_32(regex, text, text_size_code_units, text_offset_code_units, mflags, jit_stack);
	while (matches && rz_pvector_len(matches) > 0) {
		rz_pvector_push(all_matches, matches);
		RzRegexMatch *m = rz_pvector_head(matches);
		// Search again after the last match.
		text_offset_code_units = allow_overlap ? m->start + 1 : m->start + m->len;
		matches = match_first_32(regex, text, text_size_code_units, text_offset_code_units, mflags, jit_stack);
	}

	// Free last vector without matches.
	rz_pvector_free(matches);
	return all_matches;
}

/**
 * \brief Finds all matches in a text and returns them as vector of vector matches.
 *
 * \param pattern The regex pattern to match.
 * \param text The text to search in.
 * \param text_size The length of the buffer pointed to by \p text.
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts.
 * \param mflags Match flags.
 * \param allow_overlap If true it will match also overlapping patterns.
 *
 * \return PVector of every match in the given string or NULL in case of failure.
 *         One match with all its groups is again assembled in a pvector.
 */
RZ_API RZ_OWN RzPVector /*<RzVector<RzRegexMatch *> *>*/ *rz_regex_match_all_overlap(
	RZ_NONNULL const RzRegex *regex,
	RZ_NONNULL const char *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	return match_all_internal_8(regex, (ut8 *)text, text_size, text_offset, mflags, true, NULL);
}

/**
 * \brief Finds all matches in a text and returns them as vector of vector matches.
 *
 * \param pattern The regex pattern to match.
 * \param text The text to search in.
 * \param text_size The length of the buffer pointed to by \p text.
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts.
 * \param mflags Match flags.
 *
 * \return PVector of every match in the given string or NULL in case of failure.
 *         One match with all its groups is again assembled in a pvector.
 */
RZ_API RZ_OWN RzPVector /*<RzVector<RzRegexMatch *> *>*/ *rz_regex_match_all(
	RZ_NONNULL const RzRegex *regex,
	RZ_NONNULL const char *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	return match_all_internal_8(regex, (ut8 *)text, text_size, text_offset, mflags, false, NULL);
}

/**
 * \brief Finds all matches in a text and returns them as vector of vector matches.
 *
 * NOTE: This matches against UTF-16 host's endianness strings.
 *
 * \param pattern The regex pattern to match.
 * \param text The text to search in. It should be UTF-16 host-endianness encoded.
 * \param text_size The length of the buffer pointed to by \p text in code units..
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts in code units..
 * \param mflags Match flags.
 * \param allow_overlap If true it will match also overlapping patterns.
 *
 * \return PVector of every match in the given string or NULL in case of failure.
 *         One match with all its groups is again assembled in a pvector.
 */
RZ_API RZ_OWN RzPVector /*<RzVector<RzRegexMatch *> *>*/ *rz_regex_match_all_overlap_16(
	RZ_NONNULL const RzRegex16 *regex,
	RZ_NONNULL const ut16 *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	return match_all_internal_16(regex, text, text_size, text_offset, mflags, true, NULL);
}

/**
 * \brief Finds all matches in a text and returns them as vector of vector matches.
 *
 * NOTE: This matches against UTF-16 host's endianness strings.
 *
 * \param pattern The regex pattern to match.
 * \param text The text to search in. It should be UTF-16 host-endianness encoded.
 * \param text_size The length of the buffer pointed to by \p text in code units..
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts in code units..
 * \param mflags Match flags.
 *
 * \return PVector of every match in the given string or NULL in case of failure.
 *         One match with all its groups is again assembled in a pvector.
 */
RZ_API RZ_OWN RzPVector /*<RzVector<RzRegexMatch *> *>*/ *rz_regex_match_all_16(
	RZ_NONNULL const RzRegex16 *regex,
	RZ_NONNULL const ut16 *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	return match_all_internal_16(regex, text, text_size, text_offset, mflags, false, NULL);
}

/**
 * \brief Finds all matches in a text and returns them as vector of vector matches.
 *
 * NOTE: This matches against UTF-32 host's endianness strings.
 *
 * \param pattern The regex pattern to match.
 * \param text The text to search in. It should be UTF-32 host-endianness encoded.
 * \param text_size The length of the buffer pointed to by \p text in code units..
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts in code units..
 * \param mflags Match flags.
 * \param allow_overlap If true it will match also overlapping patterns.
 *
 * \return PVector of every match in the given string or NULL in case of failure.
 *         One match with all its groups is again assembled in a pvector.
 */
RZ_API RZ_OWN RzPVector /*<RzVector<RzRegexMatch *> *>*/ *rz_regex_match_all_overlap_32(
	RZ_NONNULL const RzRegex32 *regex,
	RZ_NONNULL const ut32 *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	return match_all_internal_32(regex, text, text_size, text_offset, mflags, true, NULL);
}

/**
 * \brief TODO: Note that size and offset are in bytes. Not code points.
 */
RZ_API RZ_OWN RzPVector /*<RzVector<RzRegexMatch *> *>*/ *rz_regex_match_all_overlap_multi(
	RZ_NONNULL const RzRegexMulti *regex,
	RZ_NONNULL const ut8 *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	switch (regex->re_type) {
	case RZ_REGEX_UTF8:
		return match_all_internal_8(regex->re8, (ut8 *)text, text_size, text_offset, mflags, true, regex->jit_stack);
	case RZ_REGEX_UTF16:
		return match_all_internal_16(regex->re16, (ut16 *)text, text_size / RZ_UTF16_CODE_POINT_WIDTH, text_offset / RZ_UTF16_CODE_POINT_WIDTH, mflags, true, regex->jit_stack);
	case RZ_REGEX_UTF32:
		return match_all_internal_32(regex->re32, (ut32 *)text, text_size / RZ_UTF32_CODE_POINT_WIDTH, text_offset / RZ_UTF32_CODE_POINT_WIDTH, mflags, true, regex->jit_stack);
	}
	rz_warn_if_reached();
	return NULL;
}

RZ_API RZ_OWN RzPVector /*<RzVector<RzRegexMatch *> *>*/ *rz_regex_match_all_multi(
	RZ_NONNULL const RzRegexMulti *regex,
	RZ_NONNULL const ut8 *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	switch (regex->re_type) {
	case RZ_REGEX_UTF8:
		return match_all_internal_8(regex->re8, (ut8 *)text, text_size, text_offset, mflags, false, regex->jit_stack);
	case RZ_REGEX_UTF16:
		return match_all_internal_16(regex->re16, (ut16 *)text, text_size / RZ_UTF16_CODE_POINT_WIDTH, text_offset / RZ_UTF16_CODE_POINT_WIDTH, mflags, false, regex->jit_stack);
	case RZ_REGEX_UTF32:
		return match_all_internal_32(regex->re32, (ut32 *)text, text_size / RZ_UTF32_CODE_POINT_WIDTH, text_offset / RZ_UTF32_CODE_POINT_WIDTH, mflags, false, regex->jit_stack);
	}
	rz_warn_if_reached();
	return NULL;
}

/**
 * \brief Finds all matches in a text and returns them as vector of vector matches.
 *
 * NOTE: This matches against UTF-32 host's endianness strings.
 *
 * \param pattern The regex pattern to match.
 * \param text The text to search in. It should be UTF-32 host-endianness encoded.
 * \param text_size The length of the buffer pointed to by \p text in code units..
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts in code units..
 * \param mflags Match flags.
 *
 * \return PVector of every match in the given string or NULL in case of failure.
 *         One match with all its groups is again assembled in a pvector.
 */
RZ_API RZ_OWN RzPVector /*<RzVector<RzRegexMatch *> *>*/ *rz_regex_match_all_32(
	RZ_NONNULL const RzRegex32 *regex,
	RZ_NONNULL const ut32 *text,
	RzRegexSize text_size,
	RzRegexSize text_offset,
	RzRegexFlags mflags) {
	return match_all_internal_32(regex, text, text_size, text_offset, mflags, false, NULL);
}

/**
 * \brief Checks if \p pattern can be found in \p text.
 *
 * \param pattern The regex pattern to match.
 * \param text The text to search in.
 * \param text_size The length of the buffer pointed to by \p text.
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts.
 * \param cflags Compile flags.
 * \param mflags Match flags.
 *
 * \return true if the text contains the patterns.
 * \return false Otherwise
 */
RZ_API bool rz_regex_contains(RZ_NONNULL const char *pattern, RZ_NONNULL const char *text,
	RzRegexSize text_size,
	RzRegexFlags cflags, RzRegexFlags mflags) {
	RzRegex *re = rz_regex_new(pattern, cflags, 0, NULL);
	if (!re) {
		return false;
	}
	RzPVector *matches = rz_regex_match_first(re, text, text_size, 0, mflags);
	bool found = matches != NULL && !rz_pvector_empty(matches);
	rz_pvector_free(matches);
	rz_regex_free(re);
	return found;
}

/**
 * \brief Searches for a \p pattern in \p text and returns all matches as concatenated string.
 * Only complete matches are concatenated. Sub-groups are skipped.
 *
 * \param pattern The regex pattern to match.
 * \param text The text to search in.
 * \param text_size The length of the buffer pointed to by \p text.
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param cflags Compile flags.
 * \param mflags Match flags.
 * \param separator A string to separate the matches.
 *
 * \return A string with all matches concatenated or NULL in case of failure.
 */
RZ_API RZ_OWN RzStrBuf *rz_regex_full_match_str(RZ_NONNULL const char *pattern, RZ_NONNULL const char *text,
	RzRegexSize text_size,
	RzRegexFlags cflags, RzRegexFlags mflags, RZ_NONNULL const char *separator) {
	rz_return_val_if_fail(pattern && text && separator, NULL);

	RzRegex *re = rz_regex_new(pattern, cflags, 0, NULL);
	RzStrBuf *sbuf = rz_strbuf_new("");
	RzPVector *matches = rz_regex_match_all(re, text, text_size, 0, mflags);
	if (!matches || !sbuf) {
		goto fini;
	}

	size_t i = 1;
	void **m;
	rz_pvector_foreach (matches, m) {
		RzPVector *match_groups = *m;
		RzRegexMatch *match = rz_pvector_head(match_groups);
		const char *t = text + match->start;
		if (((int)match->len) < 0) {
			goto fini;
		}
		// No separator in case of only one match
		if (i == rz_pvector_len(matches)) {
			rz_strbuf_appendf(sbuf, "%-.*s", (int)match->len, t);
		} else if (!rz_strbuf_appendf(sbuf, "%-.*s%s", (int)match->len, t, separator)) {
			goto fini;
		}
		++i;
	}

fini:
	rz_regex_free(re);
	rz_pvector_free(matches);
	return sbuf;
}

/**
 * \brief Searches for the given \p pattern in \p text and
 * returns an byte offset into the string where the pattern was found.
 *
 * \param pattern The regex pattern to match.
 * \param text The text to search in.
 * \param text_size The length of the buffer pointed to by \p text.
 *        Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string.
 * \param text_offset The offset into \p text from where the search starts.
 * \param cflags Compile flags.
 * \param mflags Match flags.
 *
 * \return Offset into the string, or SZT_MAX if pattern was not found.
 */
RZ_API RzRegexSize rz_regex_find(RZ_NONNULL const char *pattern, RZ_NONNULL RZ_BORROW char *text,
	RzRegexSize text_size, RzRegexSize text_offset,
	RzRegexFlags cflags, RzRegexFlags mflags) {
	rz_return_val_if_fail(pattern && text, SZT_MAX);
	RzRegex *regex = rz_regex_new(pattern, cflags, RZ_REGEX_DEFAULT, NULL);
	RzPVector *matches = rz_regex_match_first(regex, text, text_size, text_offset, mflags);
	if (rz_pvector_empty(matches)) {
		rz_pvector_free(matches);
		rz_regex_free(regex);
		return SZT_MAX;
	}
	RzRegexSize off = ((RzRegexMatch *)rz_pvector_head(matches))->start;
	rz_pvector_free(matches);
	rz_regex_free(regex);
	return off;
}

/**
 * \brief Returns a compile context.
 *
 * \return A PCRE2 compile context, or NULL in case of failure.
 */
RZ_API RZ_OWN RzRegexCompContext *rz_regex_compile_context_new() {
	return pcre2_compile_context_create_8(NULL);
}

/**
 * \brief Frees a compile context.
 *
 * \param A PCRE2 compile context.
 */
RZ_API void rz_regex_compile_context_free(RzRegexCompContext *ccontext) {
	pcre2_compile_context_free_8(ccontext);
}

/**
 * \brief Sets the newline convention of a compile context to the NUL character (\0).
 *
 * \param A PCRE2 compile context.
 */
RZ_API void rz_regex_set_nul_as_newline(RZ_NONNULL RzRegexCompContext *ccontext) {
	pcre2_set_newline_8(ccontext, PCRE2_NEWLINE_NUL);
}

/**
 * \brief Parses a string with regex flags characters and returns the numerical
 * value of it.
 *
 * - ''/NULL/'r' - Default: RZ_REGEX_DEFAULT
 * - 'l'         - RZ_REGEX_LITERAL
 * - 'i'         - RZ_REGEX_CASELESS
 * - 'e'         - RZ_REGEX_EXTENDED
 * - 'E'         - RZ_REGEX_EXTENDED_MORE
 * - 'm'         - RZ_REGEX_MULTILINE
 *
 * \param re_flags_desc The string containing several characters.
 *
 * \return The flags as numerical value. Or ~RZ_REGEX_DEFAULT if flags are invalid.
 *
 * Examples:
 *
 * - "ri"  => RZ_REGEX_DEFAULT | RZ_REGEX_CASELESS
 * - "li"  => RZ_REGEX_LITERAL | RZ_REGEX_CASELESS
 * - "Eim" => RZ_REGEX_EXTENDED | RZ_REGEX_CASELESS | RZ_REGEX_MULTILINE
 * - "rl"  => ~RZ_REGEX_DEFAULT => Error: Flag combination is invalid.
 * - "xX"  => ~RZ_REGEX_DEFAULT => Error: Flag combination is invalid.
 */
RZ_API RzRegexFlags rz_regex_parse_flag_desc(RZ_NULLABLE const char *re_flags_desc) {
	RzRegexFlags flags = RZ_REGEX_DEFAULT;
	if (RZ_STR_ISEMPTY(re_flags_desc)) {
		return flags;
	}
	size_t fcount = 0;
	if (strchr(re_flags_desc, 'i')) {
		fcount++;
		flags |= RZ_REGEX_CASELESS;
	}
	if (strchr(re_flags_desc, 'l')) {
		fcount++;
		flags |= RZ_REGEX_LITERAL;
		goto return_flags;
	}
	if (strchr(re_flags_desc, 'd')) {
		fcount++;
		flags |= RZ_REGEX_DOTALL;
	}
	if (strchr(re_flags_desc, 'm')) {
		fcount++;
		flags |= RZ_REGEX_MULTILINE;
	}
	if (strchr(re_flags_desc, 'r')) {
		fcount++;
		goto return_flags;
	}
	if (strchr(re_flags_desc, 'e')) {
		fcount++;
		flags |= RZ_REGEX_EXTENDED;
	}
	if (strchr(re_flags_desc, 'E')) {
		fcount++;
		flags |= RZ_REGEX_EXTENDED_MORE;
	}
return_flags:
	if (fcount != strlen(re_flags_desc)) {
		RZ_LOG_ERROR("Flag combination '%s' is invalid.\n", re_flags_desc);
		return ~RZ_REGEX_DEFAULT;
	}
	return flags;
}

/**
 * \brief The string characters in binary files.
 * The characters matched with this pattern are assumed to belong to
 * single string.
 * Any character not matching this pattern is assumed to end a string.
 * Note: A new line is a valid character within a string.
 */
#define RZ_REGEX_STR_WILDARD "[\\w\\e\\pS\\s\\n\\r\\pP]"

/**
 * \brief Returns a Regex pattern of the form "[\w\e\pS\s\n\r\pP]{min_len, max_len}".
 *
 * If min_len == max_len == 0, the function returns "[\w\e\pS\s\n\r\pP]*".
 * If min_len > max_len, the function returns "[\w\e\pS\s\n\r\pP]{min_len,}".
 *
 * \param min_len The minimum length of the wildcard regex pattern.
 * \param max_len The maximum length of the wildcard regex pattern.
 *
 * \return The pattern or NULL in case of failure.
 */
RZ_API RZ_OWN char *rz_regex_create_wildcard_pattern(size_t min_len, size_t max_len) {
	if (min_len == 0 && max_len == 0) {
		return rz_str_dup(RZ_REGEX_STR_WILDARD "*");
	} else if (min_len > max_len) {
		return rz_str_newf(RZ_REGEX_STR_WILDARD "{%" PFMTSZd ",}", min_len);
	}
	return rz_str_newf(RZ_REGEX_STR_WILDARD "{%" PFMTSZd ",%" PFMTSZd "}", min_len, max_len);
}
