#include "pattern_replace.h"

#include <tfe_utils.h>

#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <assert.h>

#define	MAX_EDIT_MATCHES			16

enum replace_zone zone_name_to_id(const char * name)
{
	const char * std_name[] = {"http_req_uri",
		"http_req_header",
		"http_req_body",
		"http_resp_header",
		"http_resp_body",
		"http_resp_body"};
	size_t i = 0;
	for (i = 0; i < sizeof(std_name) / sizeof(const char *); i++)
	{
		if (0 == strcasecmp(name, std_name[i]))
		{
			break;
		}
	}
	return (enum replace_zone) i;
}
static char *__attribute__((__unused__))
strchr_esc(char * s, const char delim)
{
	char * token;
	if (s == NULL)
		return NULL;
	for (token = s; *token != '\0'; token++)
	{
		if (*token == '\\')
		{
			token++;
			continue;
		}
		if (*token == delim)
			break;
	}
	if (*token == '\0')
	{
		return NULL;
	}
	else
	{
		return token;
	}
}
static char *__attribute__((__unused__))
strtok_r_esc(char * s, const char delim, char ** save_ptr)
{
	char * token;

	if (s == NULL) s = *save_ptr;

	/* Scan leading delimiters.  */
	token = strchr_esc(s, delim);
	if (token == NULL)
	{
		*save_ptr = token;
		return s;
	}
	/* Find the end of the token.  */
	*token = '\0';
	token++;
	*save_ptr = token;

	return s;
}

char *execute_read_file(const char *filename, size_t *filelen)
{
    FILE *file = NULL;
    long length = 0;
    char *content = NULL;
    size_t read_chars = 0;

    file = fopen(filename, "rb");
    if (file == NULL)
    {
        goto cleanup;
    }
    if (fseek(file, 0, SEEK_END) != 0)
    {
        goto cleanup;
    }
    length = ftell(file);
    if (length < 0)
    {
        goto cleanup;
    }
    if (fseek(file, 0, SEEK_SET) != 0)
    {
        goto cleanup;
    }

    /* allocate content buffer */
    content = (char*)malloc((size_t)length + sizeof(""));
    if (content == NULL)
    {
        goto cleanup;
    }

    /* read the file into memory */
    read_chars = fread(content, sizeof(char), (size_t)length, file);
    if ((long)read_chars != length)
    {
        free(content);
        content = NULL;
        goto cleanup;
    }
	*filelen = read_chars;
    content[read_chars] = '\0';
cleanup:
    if (file != NULL)
    {
        fclose(file);
    }
    return content;
}
size_t __attribute__((__unused__))
format_replace_rule(const char * exec_para, struct replace_rule * replace, size_t n_replace)
{
	char * tmp = ALLOC(char, strlen(exec_para) + 1);
	char * token = NULL, * sub_token = NULL, * saveptr = NULL, * saveptr2 = NULL;
	size_t idx = 0;

	const char * str_zone = "zone=";
	const char * str_subs = "substitute=";
	memcpy(tmp, exec_para, strlen(exec_para));

	for (token = tmp;; token = NULL)
	{
		sub_token = strtok_r(token, ";", &saveptr);
		if (sub_token == NULL) break;

		if (0 == strncasecmp(sub_token, str_zone, strlen(str_zone)))
		{
			replace[idx].zone = zone_name_to_id(sub_token + strlen(str_zone));
			if (replace[idx].zone == kZoneMax)
			{
				break;
			}
		}

		sub_token = strtok_r(NULL, ";", &saveptr);
		if (0 == strncasecmp(sub_token, str_subs, strlen(str_subs)))
		{
			sub_token += strlen(str_subs) + 1;
			replace[idx].find = tfe_strdup(strtok_r_esc(sub_token, '/', &saveptr2));
			replace[idx].replace_with = tfe_strdup(strtok_r_esc(NULL, '/', &saveptr2));

			idx++;
			if (idx == n_replace)
			{
				break;
			}
		}
	}

	free(tmp);
	tmp = NULL;
	return idx;
}

size_t select_replace_rule(enum replace_zone zone, const struct replace_rule * replace, size_t n_replace,
	const struct replace_rule ** selected, size_t n_selected)
{
	size_t i = 0, j = 0;
	for (i = 0; i < n_replace && j < n_selected; i++)
	{
		if (replace[i].zone == zone)
		{
			selected[j] = replace + i;
			j++;
		}
	}
	return j;
}

size_t replace_string(const char * in, size_t in_sz, const struct replace_rule * zone, char** out)
{
	assert(strlen(zone->find) != 0);

    int error;
    PCRE2_SIZE erroffset;

    const PCRE2_SPTR pattern = (PCRE2_SPTR)zone->find;
    const PCRE2_SPTR subject = (PCRE2_SPTR)in;
    const PCRE2_SPTR replacement = (PCRE2_SPTR)zone->replace_with;

    pcre2_code *re = pcre2_compile(pattern, PCRE2_ZERO_TERMINATED, 0, &error, &erroffset, 0);
    if (re == 0)
        return -1;

    pcre2_jit_compile(re, PCRE2_JIT_COMPLETE);

    PCRE2_SIZE outbuff_size = in_sz+sizeof(replacement)*MAX_EDIT_MATCHES;
	PCRE2_SIZE outlen = 0;
	PCRE2_UCHAR* out_buffer = NULL;
not_enough_mem_retry:
    out_buffer = (PCRE2_UCHAR*)malloc(sizeof(PCRE2_UCHAR)*outbuff_size);
	outlen = outbuff_size;
    int rc = pcre2_substitute(re, subject, in_sz, 0, PCRE2_SUBSTITUTE_GLOBAL | PCRE2_SUBSTITUTE_EXTENDED | PCRE2_SUBSTITUTE_OVERFLOW_LENGTH, 0, 0, replacement, PCRE2_ZERO_TERMINATED, out_buffer, &outlen);
	if(outlen>outbuff_size)
	{
		outbuff_size=outlen;
		free(out_buffer);
		out_buffer=NULL;
		goto not_enough_mem_retry;		
	}
	if(rc<=0)
	{
		free(out_buffer);
		outlen=rc;
	}
	else
	{
		*out=(char*)out_buffer;
	}
	pcre2_code_free(re);
	return outlen;
}

size_t execute_replace_rule(const char * in, size_t in_sz,
	enum replace_zone zone, const struct replace_rule * rules, size_t n_rule, char** out)
{
	const struct replace_rule * todo[n_rule];
	size_t n_todo = 0, i = 0, interator_sz=0, pre_out_sz=0;
	const char * interator = NULL;
	char* new_out = NULL, * pre_out = NULL;
	size_t output_size=0;
	if (in_sz == 0 || in==NULL)
	{
		return 0;
	}
	n_todo = select_replace_rule(zone, rules, n_rule, todo, n_rule);
	interator = in;
	interator_sz = in_sz;
	for (i = 0; i < n_todo; i++)
	{
		output_size = replace_string(interator, interator_sz, todo[i], &new_out);
		if (output_size == 0)
		{
			continue;
		}
		if (pre_out != NULL)
		{
			free(pre_out);
			pre_out = NULL;
		}
		pre_out = new_out;
		pre_out_sz = output_size;
		
		interator = new_out;
		interator_sz = output_size;
		
		new_out=NULL;
		output_size=0;
	}
	if(pre_out_sz>0)
	{
		*out=pre_out;
		return pre_out_sz;
	}
	else
	{
		return 0;
	}
}

size_t insert_string(char * in, size_t in_sz, const char *insert_on, const char *stype, const char *type, char** out)
{
	char *target=NULL;
	size_t outlen=0, target_size=0;
	char position[]="</head>";

	/* "<script type=\"text/javascript\" class=\"RQ_SCRIPT\"></script>"*/
	int js_type_len = 58;
	/*"<style type=\"text/css\" class=\"RQ_SCRIPT\"></style>"*/
	int css_type_len = 49;

	char* head_string=NULL;

	if (0==strcasecmp(type, "css"))
	{
		target_size = in_sz+strlen(stype)+1+css_type_len;
		target = ALLOC(char, target_size + 1);
	}

	if (0==strcasecmp(type, "js"))
	{
		target_size = in_sz+strlen(stype)+1+js_type_len;
		target = ALLOC(char, target_size + 1);
	}

	if (insert_on != NULL && 0==strcasecmp(insert_on, "after-page-load"))
	{
		memcpy(position, "</body>", sizeof(position));
	}
	head_string=strstr(in, position);
	if (head_string != NULL)
	{
		strncat(target, in, MIN((unsigned int)(head_string-in), target_size));
		size_t style_len = 0; char *style_msg = NULL;
		if (0==strcasecmp(type, "js"))
		{
			style_len = strlen(stype)+1+js_type_len;
			style_msg = ALLOC(char, style_len);
			snprintf(style_msg, style_len, "<script type=\"text/javascript\" class=\"RQ_SCRIPT\">%s</script>", stype);
		}
		if (0==strcasecmp(type, "css"))
		{
			style_len = strlen(stype)+1+css_type_len;
			style_msg = ALLOC(char, style_len);
			snprintf(style_msg, style_len, "<style type=\"text/css\" class=\"RQ_SCRIPT\">%s</style>", stype);
		}

		strncat(target, style_msg, MIN(style_len, target_size));
		free(style_msg);
		style_msg = NULL;
		strncat(target, head_string, (target_size - (head_string-in) - style_len));
		target[target_size-1] = '\0';
		outlen = target_size;
		*out = target;
	}else
	{
		free(target);
		target = NULL;
		outlen = 0;
	}
	return outlen;
}

size_t execute_insert_rule(char * in, size_t in_sz, const struct insert_rule * rules, char** out)
{
	return insert_string(in, in_sz, rules->position, rules->stype, rules->type, out);
}

void simple_replace(const char* find, const char* replacement, const char* input, size_t in_sz, char** output, size_t *output_sz)
{
	char* exec_para=NULL;
	asprintf(&exec_para,"zone=http_resp_body;substitute=/%s/%s", find, replacement);
	size_t n_got_rule=0;
	struct replace_rule rules[16];
	n_got_rule=format_replace_rule(exec_para, rules, sizeof(rules)/sizeof(rules[0]));
	*output_sz=execute_replace_rule(input, strlen(input), kZoneResponseBody, rules, n_got_rule, output);
	free(exec_para);
	return;
}


