#include "config.h"
#include	"search.h"
#include	"message.h"
#include	"messageinfo.h"
#include	"rematchstr.h"
#include	"rematchmsg.h"
#include	"varlist.h"
#include	<string.h>
#include	<ctype.h>
#include	<stdlib.h>
#include	<algorithm>
#include	<iostream>

void Search::cleanup()
{
	if (match_data)
	{
		pcre2_match_data_free(match_data);
		match_data=NULL;
	}

	if (pcre_regexp)
	{
		pcre2_code_free(pcre_regexp);
		pcre_regexp=NULL;
	}
}

int	Search::init(const char *expr, const char *opts)
{
	match_top_header=0;
	match_other_headers=0;
	match_body=0;
	weight1=1;
	weight2=1;
	scoring_match=0;
	score=0;

	if (strchr(opts, 'h'))	match_top_header=match_other_headers=1;
	if (strchr(opts, 'H'))	match_top_header=1;

	if (strchr(opts, 'b'))	match_body=1;
	if (!match_top_header && !match_other_headers && !match_body)
	{
		match_top_header=1;
		match_other_headers=1;
		if (strchr(opts, 'w'))	match_body=1;
	}

	int errcode;

	cleanup();

	PCRE2_SIZE errindex;

	pcre_regexp=pcre2_compile((PCRE2_SPTR8)expr,
				  PCRE2_ZERO_TERMINATED,
				  PCRE2_UTF | (strchr(opts, 'D') ? 0:PCRE2_CASELESS),
				  &errcode,
				  &errindex,
				  NULL);

	if (!pcre_regexp)
	{
		std::string b;

		PCRE2_UCHAR buffer[256];
		pcre2_get_error_message(errcode, buffer, sizeof(buffer));

		b="Invalid regular expression, offset ";
		add_integer(b, errindex);
		b += " of: ";
		b += expr;
		b += ": ";
		b += (char *)buffer;
		b += "\n";
		std::cerr << b << std::flush;
		return -1;
	}

	match_data= pcre2_match_data_create_from_pattern(
		pcre_regexp, NULL
	);

	if (!match_data)
	{
		std::string b;

		b="Failed to create match data for: ";
		b += expr;
		b += "\n";
		std::cerr << b << std::flush;
		cleanup();
		return -1;
	}
	search_expr=expr;

	while (*opts)
	{
		if (*opts == '.' || isdigit(*opts) || *opts == '-' ||
			*opts == '+')
		{
			weight1=atof(opts);
			while (*opts && *opts != ',')	++opts;
			if (*opts == ',')
			{
				++opts;
				if (*opts == '.' || isdigit(*opts) ||
					*opts == '-' || *opts == '+')
					weight2=atof(opts);
			}
			scoring_match=1;
			break;
		}
		++opts;
	}
	return (0);
}

int Search::find(Message &msg, MessageInfo &,
	const char *expr, const char *opts, foreach_t *foreachp)
{
	if (init(expr, opts))	return (-1);

	msg.Rewind();
	return (findinline(msg, expr, foreachp));
}

int Search::find(const char *str, const char *expr, const char *opts,
		foreach_t *foreachp)
{
	if (init(expr, opts))	return (-1);

	if (VerboseLevel() > 2)
	{
	std::string	msg;

		msg="Matching /";
		msg += expr;
		msg += "/ against ";
		msg += str;
		msg += "\n";
		std::cerr << msg << std::flush;
	}

	int startoffset=0;
	const char *orig_str=str;

	for (;;)
	{
		int rc=pcre2_match(pcre_regexp,
				   (PCRE2_SPTR8)orig_str,
				   strlen(orig_str),
				   startoffset,
				   0,
				   match_data,
				   NULL);

		if (rc < 0 )
			break;


		PCRE2_SIZE *ovector=pcre2_get_ovector_pointer(match_data);
		uint32_t ovector_count=pcre2_get_ovector_count(match_data);

		score += weight1;
		weight1 *= weight2;

		if (!scoring_match || foreachp)
		{
			init_match_vars(orig_str, ovector, ovector_count,
					foreachp);
			if (!foreachp)
				break;
		}

		if (!ovector || ovector_count <= 0)
			break;

		startoffset=ovector[1];

	}
	return (0);
}

//////////////////////////////////////////////////////////////////////////////
//
// Search individual lines for the pattern (transparently concatenate
// continued headers.
//
//////////////////////////////////////////////////////////////////////////////

int Search::findinline(Message &msg, const char *expr, foreach_t *foreachp)
{
	bool done=false;

	rfc2045::mime_decoder decoder{
		[this, &done]
		(const char *ptr, size_t n)
		{
			if (done)
				return;

			if (search_cb(ptr, n))
				done=true;
		},
		msg,
		unicode::utf_8
	};

	decoder.header_name_lc=false;
	if (!match_top_header && !match_other_headers)
		decoder.decode_header=false;
	else if (match_top_header && !match_other_headers)
		decoder.decode_subentities=false;

	if (!match_body)
		decoder.decode_body=false;

	current_line.clear();
	foreachp_arg=foreachp;
	decoder.decode<false>(msg.rfc2045p);
	if (current_line.size() >= 1)
		search_cb("\n", 1);
	return 0;
}

int Search::search_cb(const char *ptr, size_t cnt)
{
	while (cnt)
	{
		size_t i;

		if (*ptr == '\n')
		{

			if (VerboseLevel() > 2)
			{
			std::string	msg;

				msg="Matching /";

				msg += search_expr;

				msg += "/ against ";
				msg += current_line.c_str();
				msg += "\n";
				std::cerr << msg << std::flush;
			}

			const char *orig_str=current_line.c_str();

			int rc=pcre2_match(pcre_regexp,
					   (PCRE2_SPTR8)orig_str,
					   strlen(orig_str),
					   0,
					   0,
					   match_data,
					   NULL);

			if (rc >= 0)
			{
				score += weight1;
				weight1 *= weight2;

				if (!scoring_match || foreachp_arg)
				{
					PCRE2_SIZE *ovector=
						pcre2_get_ovector_pointer(
							match_data);
					uint32_t ovector_count=
						pcre2_get_ovector_count(
							match_data);

					init_match_vars(orig_str,
							ovector,
							ovector_count,
							foreachp_arg);
					if (!foreachp_arg)
					{
						current_line.clear();
						// Stop searching now
						return (-1);
					}
				}
			}
			else	if (VerboseLevel() > 2)
				std::cerr << "Not matched.\n" << std::flush;

			current_line.clear();

			++ptr;
			--cnt;
			continue;
		}


		for (i=0; i<cnt; ++i)
			if (ptr[i] == '\n')
				break;
		current_line.append(ptr, ptr+i);
		ptr += i;
		cnt -= i;
	}
	return (0);
}

void Search::init_match_vars(const char *str,
			     PCRE2_SIZE *offsets,
			     uint32_t nranges,
			     foreach_t *foreachp)
{
	std::string varname;
	uint32_t cnt;

	if (!offsets)
		return;

	std::list<std::string> foreach_values;

	for (cnt=0; cnt<nranges; cnt++)
	{
		varname="MATCH";
		if (cnt)
			add_integer(varname, cnt);


		std::string v;

		int i, j;

		i=offsets[cnt*2];
		j=offsets[cnt*2+1];

		if (i < j)
		{
			size_t s=j-i;

			char *ptr=(char *)malloc(s+1);
			if (ptr)
			{
				memcpy(ptr, str + i, s);
				ptr[s]=0;
				v=ptr;
				free(ptr);
			}
		}

		if (foreachp)
		{
			foreach_values.push_back(v);
		}

		SetVar(varname, v);
	}

	if (foreachp)
	{
		foreachp->push_back(std::move(foreach_values));
	}
}
