//
// This file is part of the aMule Project.

// Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
// Copyright (c) 2005-2011 Froenchenko Leonid ( lfroen@gmail.com / http://www.amule.org )
//
// Any parts of this program derived from the xMule, lMule or eMule project,
// or contributed by third-party developers are copyrighted by their
// respective authors.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
//

#ifdef HAVE_CONFIG_H
#	include "config.h"
#endif

#include <string> // Do_not_auto_remove (g++-4.0.1)

#ifdef HAVE_SYS_TYPES_H
#	include <sys/types.h>
#endif

#ifdef PHP_STANDALONE_EN
#	include <map>
#	include <string>
#	include <list>
#	include <regex.h>
#else
#	include "WebServer.h"
#	include <ec/cpp/ECSpecialTags.h>
#	include <wx/regex.h>
#	include <wx/datetime.h>
#endif

#include "php_syntree.h"
#include "php_core_lib.h"
#include <stdarg.h>

#ifdef ENABLE_NLS
#include <libintl.h>
#endif

/*
 * Built-in php functions. Those are both library and core internals.
 *
 * I'm not going event to get near to what Zend provide, but
 * at least base things must be here
 */

/*
 * Print info about variable: php var_dump()
 */
void php_var_dump(PHP_VALUE_NODE *node, int ident, int ref)
{
	for(int i = 0; i < ident;i++) {
		printf("\t");
	}
	if ( ref ) printf("&");
	switch(node->type) {
		case PHP_VAL_BOOL: printf("bool(%s)\n", node->int_val ? "true" : "false"); break;
		case PHP_VAL_INT: printf("int(%"PRIu64")\n", node->int_val); break;
		case PHP_VAL_FLOAT: printf("float(%f)\n", node->float_val); break;
		case PHP_VAL_STRING: printf("string(%d) \"%s\"\n", (int)strlen(node->str_val), node->str_val); break;
		case PHP_VAL_OBJECT: printf("Object(%s)\n", node->obj_val.class_name); break;
		case PHP_VAL_ARRAY: {
			int arr_size = array_get_size(node);
			printf("array(%d) {\n", arr_size);
			for(int i = 0; i < arr_size;i++) {
				const std::string &curr_key = array_get_ith_key(node, i);
				PHP_VAR_NODE *curr_val = array_get_by_str_key(node, curr_key);
				printf("\t[%s]=>\n", curr_key.c_str());
				php_var_dump(&curr_val->value, ident+1, curr_val->ref_count > 1);
			}
			for(int i = 0; i < ident;i++) {
				printf("\t");
			}
			printf("}\n");
			break;
		}
		case PHP_VAL_NONE: printf("NULL\n"); break;
		case PHP_VAL_VAR_NODE:
		case PHP_VAL_INT_DATA: assert(0); break;
	}
}

void php_native_var_dump(PHP_VALUE_NODE *)
{
	PHP_SCOPE_ITEM *si = get_scope_item(g_current_scope, "__param_0");
	if ( si ) {
		assert((si->type == PHP_SCOPE_VAR)||(si->type == PHP_SCOPE_PARAM));
		php_var_dump(&si->var->value, 0, 0);
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument");
	}
}


/*
 * Sorting stl-way requires operator ">"
 */
class SortElem {
	public:
		SortElem() {}
		SortElem(PHP_VAR_NODE *p) { obj = p; }

		PHP_VAR_NODE *obj;
		static PHP_SYN_FUNC_DECL_NODE *callback;

		friend bool operator<(const SortElem &o1, const SortElem &o2);
};

PHP_SYN_FUNC_DECL_NODE *SortElem::callback = 0;

bool operator<(const SortElem &o1, const SortElem &o2)
{
	PHP_VALUE_NODE result;

	value_value_assign(&SortElem::callback->params[0].si_var->var->value, &o1.obj->value);
	value_value_assign(&SortElem::callback->params[1].si_var->var->value, &o2.obj->value);

	switch_push_scope_table((PHP_SCOPE_TABLE_TYPE *)SortElem::callback->scope);

	//
	// params passed by-value, all & notations ignored
	//
	result.type = PHP_VAL_NONE;
	php_execute(SortElem::callback->code, &result);
	cast_value_dnum(&result);
	//
	// restore stack, free arg list
	//
	switch_pop_scope_table(0);

	value_value_free(&SortElem::callback->params[0].si_var->var->value);
	value_value_free(&SortElem::callback->params[1].si_var->var->value);

	return result.int_val != 0;
}

void php_native_usort(PHP_VALUE_NODE *)
{
	PHP_SCOPE_ITEM *si = get_scope_item(g_current_scope, "__param_0");
	if ( !si || (si->var->value.type != PHP_VAL_ARRAY)) {
		php_report_error(PHP_ERROR, "Invalid or missing argument (array)");
		return;
	}
	PHP_VAR_NODE *array = si->var;
	si = get_scope_item(g_current_scope, "__param_1");
	if ( !si || (si->var->value.type != PHP_VAL_STRING)) {
		php_report_error(PHP_ERROR, "Invalid or missing argument (func name)");
		return;
	}
	char *cmp_func_name = si->var->value.str_val;
	si = get_scope_item(g_global_scope, cmp_func_name);
	if ( !si || (si->type != PHP_SCOPE_FUNC)) {
		php_report_error(PHP_ERROR, "Compare function [%s] not found", cmp_func_name);
		return;
	}
	PHP_SYN_FUNC_DECL_NODE *func_decl = si->func->func_decl;

	//
	// usort invalidates keys, and sorts values
	//
	PHP_ARRAY_TYPE *arr_obj = (PHP_ARRAY_TYPE *)array->value.ptr_val;
	//
	// create vector of values
	//
	if ( arr_obj->array.empty() ) {
		php_report_error(PHP_WARNING, "Sorting array of size 0");
		return;
	}

	std::list<SortElem> sort_list;
	for(PHP_ARRAY_ITER_TYPE i = arr_obj->array.begin(); i != arr_obj->array.end(); ++i) {
		sort_list.push_back(SortElem(i->second));
	}
	SortElem::callback = func_decl;
	sort_list.sort();

	arr_obj->array.clear();
	arr_obj->sorted_keys.clear();
	unsigned int key = 0;
	for(std::list<SortElem>::iterator i = sort_list.begin(); i != sort_list.end(); ++i) {
		array_add_to_int_key(&array->value, key++, i->obj);
	}

}


/*
 * String functions
 */
void php_native_strlen(PHP_VALUE_NODE *result)
{
	PHP_SCOPE_ITEM *si = get_scope_item(g_current_scope, "__param_0");
	if ( si ) {
		PHP_VALUE_NODE *param = &si->var->value;
		cast_value_str(param);
		if ( result ) {
			cast_value_dnum(result);
			result->int_val = strlen(param->str_val);
		}
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument");
	}
}

void php_native_count(PHP_VALUE_NODE *result)
{
	PHP_SCOPE_ITEM *si = get_scope_item(g_current_scope, "__param_0");
	if ( si ) {
		PHP_VALUE_NODE *param = &si->var->value;
		if ( result ) {
			cast_value_dnum(result);
			if ( (si->var->value.type == PHP_VAL_NONE) || (si->var->value.type != PHP_VAL_ARRAY) ) {
				result->int_val = 0;
			} else {
				result->int_val = array_get_size(param);
			}
		}
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument");
	}
}

void php_native_isset(PHP_VALUE_NODE *result)
{
	PHP_SCOPE_ITEM *si = get_scope_item(g_current_scope, "__param_0");
	if ( si ) {
		PHP_VALUE_NODE *param = &si->var->value;
		cast_value_str(param);
		if ( result ) {
			cast_value_bool(result);
			result->int_val = (si->var->value.type == PHP_VAL_NONE) ? 0 : 1;
		}
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument");
	}
}

void php_native_substr(PHP_VALUE_NODE * /*result*/)
{
	PHP_SCOPE_ITEM *si_str = get_scope_item(g_current_scope, "__param_0");
	PHP_VALUE_NODE *str = &si_str->var->value;
	if ( si_str ) {
		cast_value_str(str);
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument 'str' for 'substr'");
		return;
	}
	PHP_SCOPE_ITEM *si_start = get_scope_item(g_current_scope, "__param_1");
	PHP_VALUE_NODE *start = &si_start->var->value;
	if ( si_start ) {
		cast_value_dnum(start);
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument 'start' for 'substr'");
		return;
	}
	// 3-rd is optional
	PHP_SCOPE_ITEM *si_end = get_scope_item(g_current_scope, "__param_2");
	PHP_VALUE_NODE end = { PHP_VAL_INT, { 0 } };
	if ( si_end ) {
		end = si_end->var->value;
	}
	cast_value_dnum(&end);


}


void php_native_split(PHP_VALUE_NODE *result)
{
	if ( result ) {
		cast_value_array(result);
	} else {
		return;
	}
	PHP_VALUE_NODE *pattern, *string_to_split, *split_limit;
	PHP_SCOPE_ITEM *si = get_scope_item(g_current_scope, "__param_0");
	if ( si ) {
		pattern = &si->var->value;
		cast_value_str(pattern);
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument: pattern");
		return;
	}
	si = get_scope_item(g_current_scope, "__param_1");
	if ( si ) {
		string_to_split = &si->var->value;
		cast_value_str(string_to_split);
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument: string");
		return;
	}
	si = get_scope_item(g_current_scope, "__param_2");
	if ( si ) {
		split_limit = &si->var->value;
		cast_value_dnum(split_limit);
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument: string");
		return;
	}
#ifdef PHP_STANDALONE_EN
	regex_t preg;
	char error_buff[256];
	int reg_result = regcomp(&preg, pattern->str_val, REG_EXTENDED);
	if ( reg_result ) {
		regerror(reg_result, &preg, error_buff, sizeof(error_buff));
		php_report_error(PHP_ERROR, "Failed in regcomp: %s", error_buff);
#else
	wxRegEx preg;
	if (!preg.Compile(wxString(char2unicode(pattern->str_val)), wxRE_EXTENDED)) {
		php_report_error(PHP_ERROR, "Failed in Compile of: %s", pattern->str_val);
#endif
		return;
	}

#ifdef PHP_STANDALONE_EN
	size_t nmatch = strlen(string_to_split->str_val);
	regmatch_t *pmatch = new regmatch_t[nmatch];
#endif
	char *str_2_match = string_to_split->str_val;
	char *tmp_buff = new char[strlen(string_to_split->str_val)+1];

	while ( 1 ) {
//		printf("matching: %s\n", str_2_match);
#ifdef PHP_STANDALONE_EN
		reg_result = regexec(&preg, str_2_match, nmatch, pmatch, 0);
		if ( reg_result ) {
#else
		if (!preg.Matches(wxString(char2unicode(str_2_match)))) {
#endif
			// no match
			break;
		}
#ifndef PHP_STANDALONE_EN
		// get matching position
		size_t start, len;
		if (!preg.GetMatch(&start, &len)) {
			break;	// shouldn't happen
		}
#endif
		/*
		 * I will use only first match, since I don't see any sense to have more
		 * then 1 match in split() call
		 */
#ifdef PHP_STANDALONE_EN
		for(int i = 0; i < pmatch[0].rm_so; i++) {
#else
		for(size_t i = 0; i < start; i++) {
#endif
			tmp_buff[i] = str_2_match[i];
		}
#ifdef PHP_STANDALONE_EN
		tmp_buff[pmatch[0].rm_so] = 0;
#else
		tmp_buff[start] = 0;
#endif
//		printf("Match added [%s]\n", tmp_buff);

		PHP_VAR_NODE *match_val = array_push_back(result);
		match_val->value.type = PHP_VAL_STRING;
		match_val->value.str_val = strdup(tmp_buff);

#ifdef PHP_STANDALONE_EN
		str_2_match += pmatch[0].rm_eo;
#else
		str_2_match += start + len;
#endif
	}

	PHP_VAR_NODE *match_val = array_push_back(result);
	match_val->value.type = PHP_VAL_STRING;
	match_val->value.str_val = strdup(str_2_match);

	delete [] tmp_buff;
#ifdef PHP_STANDALONE_EN
	delete [] pmatch;
	regfree(&preg);
#endif
}

#ifdef ENABLE_NLS

void php_native_gettext(PHP_VALUE_NODE *result)
{
	PHP_SCOPE_ITEM *si_str = get_scope_item(g_current_scope, "__param_0");
	PHP_VALUE_NODE *str = &si_str->var->value;
	if ( si_str ) {
		cast_value_str(str);
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument 'msgid' for 'gettext'");
		return;
	}
	if ( result ) {
		cast_value_dnum(result);
		result->type = PHP_VAL_STRING;
		result->str_val = strdup(gettext(str->str_val));
	}
}

void php_native_gettext_noop(PHP_VALUE_NODE *result)
{
	PHP_SCOPE_ITEM *si_str = get_scope_item(g_current_scope, "__param_0");
	PHP_VALUE_NODE *str = &si_str->var->value;
	if ( si_str ) {
		cast_value_str(str);
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument 'msgid' for 'gettext_noop'");
		return;
	}
	if ( result ) {
		cast_value_dnum(result);
		result->type = PHP_VAL_STRING;
		result->str_val = strdup(str->str_val);
	}
}

void php_native_ngettext(PHP_VALUE_NODE *result)
{
	PHP_SCOPE_ITEM *si_msgid = get_scope_item(g_current_scope, "__param_0");
	PHP_VALUE_NODE *msgid = &si_msgid->var->value;
	if ( si_msgid ) {
		cast_value_str(msgid);
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument 'msgid' for 'ngettext'");
		return;
	}
	PHP_SCOPE_ITEM *si_msgid_plural = get_scope_item(g_current_scope, "__param_1");
	PHP_VALUE_NODE *msgid_plural = &si_msgid_plural->var->value;
	if ( si_msgid_plural ) {
		cast_value_str(msgid_plural);
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument 'msgid_plural' for 'ngettext'");
		return;
	}
	PHP_SCOPE_ITEM *si_count = get_scope_item(g_current_scope, "__param_2");
	PHP_VALUE_NODE *count = &si_count->var->value;
	if ( si_count ) {
		cast_value_dnum(count);
	} else {
		php_report_error(PHP_ERROR, "Invalid or missing argument 'count' for 'ngettext'");
		return;
	}
	if ( result ) {
		cast_value_dnum(result);
		result->type = PHP_VAL_STRING;
		result->str_val = strdup(ngettext(msgid->str_val, msgid_plural->str_val, count->int_val));
	}
}
#endif

PHP_BLTIN_FUNC_DEF core_lib_funcs[] = {
	{
		"var_dump",
		1,
		php_native_var_dump,
	},
	{
		"strlen",
		1, php_native_strlen,
	},
	{
		"count",
		1, php_native_count,
	},
	{
		"isset",
		1, php_native_isset,
	},
	{
		"usort",
		2,
		php_native_usort,
	},
	{
		"split",
		3,
		php_native_split,
	},
#ifdef ENABLE_NLS
	{
		"_",
		1, php_native_gettext,
	},
	{
		"gettext",
		1, php_native_gettext,
	},
	{
		"gettext_noop",
		1, php_native_gettext_noop,
	},
	{
		"ngettext",
		3, php_native_ngettext,
	},
#endif
	{ 0, 0, 0, },
};

void php_init_core_lib()
{
	// load function definitions
	PHP_BLTIN_FUNC_DEF *curr_def = core_lib_funcs;
	while ( curr_def->name ) {
		php_add_native_func(curr_def);
		curr_def++;
	}
}

//
// lexer has no include file
//
extern "C"
void php_set_input_buffer(char *buf, int len);

CPhPLibContext::CPhPLibContext(CWebServerBase *server, const char *file)
{
	g_curr_context = this;

	m_server = server;

	php_engine_init();
	phpin = fopen(file, "r");
	if ( !phpin ) {
		return;
	}

	phpparse();

	m_syn_tree_top = g_syn_tree_top;
	m_global_scope = g_global_scope;
}

CPhPLibContext::CPhPLibContext(CWebServerBase *server, char *php_buf, int len)
{
	g_curr_context = this;

	m_server = server;

	php_engine_init();

	m_global_scope = g_global_scope;

	php_set_input_buffer(php_buf, len);
	phpparse();

	m_syn_tree_top = g_syn_tree_top;
}

CPhPLibContext::~CPhPLibContext()
{
	SetContext();
	php_engine_free();
}

void CPhPLibContext::SetContext()
{
	g_syn_tree_top = m_syn_tree_top;
	g_global_scope = m_global_scope;
}

void CPhPLibContext::Execute(CWriteStrBuffer *buf)
{
	m_curr_str_buffer = buf;

	PHP_VALUE_NODE val;
	php_execute(g_syn_tree_top, &val);
}

CPhPLibContext *CPhPLibContext::g_curr_context = 0;

/*
 * For simplicity and performance sake, this function can
 * only handle limited-length printf's. In should be NOT be used
 * for string concatenation like printf("xyz %s %s", s1, s2).
 *
 * Engine will call Print for "print" and "echo"
 */
void CPhPLibContext::Printf(const char *str, ...)
{
	va_list args;

	va_start(args, str);
	if ( !g_curr_context || !g_curr_context->m_curr_str_buffer ) {
		vprintf(str, args);
	} else {
		char buf[4096];
		vsnprintf(buf, sizeof(buf), str, args);
		g_curr_context->m_curr_str_buffer->Write(buf);
	}
	va_end(args);
}

void CPhPLibContext::Print(const char *str)
{
	if ( !g_curr_context || !g_curr_context->m_curr_str_buffer ) {
		printf("%s", str);
	} else {
		g_curr_context->m_curr_str_buffer->Write(str);
	}
}


CPhpFilter::CPhpFilter(CWebServerBase *server, CSession *sess,
			const char *file, CWriteStrBuffer *buff)
{
	FILE *f = fopen(file, "r");
	if ( !f ) {
		printf("ERROR: php can not open source file [%s]\n", file);
		return;
	}
	if ( fseek(f, 0, SEEK_END) != 0 ) {
		printf("ERROR: fseek failed on php source file [%s]\n", file);
		fclose(f);
		return;
	}
	int size = ftell(f);
	char *buf = new char [size+1];
	rewind(f);
	// fread may actually read less if it is a CR-LF-file in Windows
	size = fread(buf, 1, size, f);
	buf[size] = 0;
	fclose(f);
	char *scan_ptr = buf;
	char *curr_code_end = buf;
	while ( strlen(scan_ptr) ) {
		scan_ptr = strstr(scan_ptr, "<?php");
		if ( !scan_ptr ) {
			buff->Write(curr_code_end);
			break;
		}
		if ( scan_ptr != curr_code_end ) {
			buff->Write(curr_code_end, scan_ptr - curr_code_end);
		}
		curr_code_end = strstr(scan_ptr, "?>");
		if ( !curr_code_end ) {
			break;
		}
		curr_code_end += 2; // include "?>" in buffer

		int len = curr_code_end - scan_ptr;

		CPhPLibContext *context = new CPhPLibContext(server, scan_ptr, len);

#ifndef PHP_STANDALONE_EN
		load_session_vars("HTTP_GET_VARS", sess->m_get_vars);
		load_session_vars("_SESSION", sess->m_vars);
#endif

		context->Execute(buff);

#ifndef PHP_STANDALONE_EN
		save_session_vars(sess->m_vars);
#endif

		delete context;

		scan_ptr = curr_code_end;
	}

#ifndef PHP_STANDALONE_EN
	sess->m_get_vars.clear();
#endif

	delete [] buf;
}


/*
 * String buffer: almost same as regular 'string' class, but,
 * without reallocation when full. Instead, new buffer is
 * allocated, and added to list
 */
CWriteStrBuffer::CWriteStrBuffer()
{
	m_alloc_size = 1024;
	m_total_length = 0;

	AllocBuf();
}

CWriteStrBuffer::~CWriteStrBuffer()
{
	for(std::list<char *>::iterator i = m_buf_list.begin(); i != m_buf_list.end(); ++i) {
		delete [] *i;
	}
	delete [] m_curr_buf;
}

void CWriteStrBuffer::AllocBuf()
{
	m_curr_buf = new char [m_alloc_size];
	m_buf_ptr = m_curr_buf;
	m_curr_buf_left = m_alloc_size;
}

void CWriteStrBuffer::Write(const char *s, int len)
{
	if ( len == -1 ) {
		len = strlen(s);
	}
	m_total_length += len;

	while ( len ) {
		if ( (len + 1) <= m_curr_buf_left ) {
			strncpy(m_buf_ptr, s, len);
			m_buf_ptr += len;
			m_curr_buf_left -= len;
			len = 0;
		} else {
			memcpy(m_buf_ptr, s, m_curr_buf_left);
			int rem_len = len - m_curr_buf_left;
			s += m_curr_buf_left;

			len = rem_len;
			m_buf_list.push_back(m_curr_buf);
			AllocBuf();
		}
	}
}

void CWriteStrBuffer::CopyAll(char *dst_buffer)
{
	char *curr_ptr = dst_buffer;
	int rem_size = m_total_length;
	for(std::list<char *>::iterator i = m_buf_list.begin(); i != m_buf_list.end(); ++i) {
		memcpy(curr_ptr, *i, m_alloc_size);
		rem_size -= m_alloc_size;
		curr_ptr += m_alloc_size;
	}
	if ( rem_size ) {
		memcpy(curr_ptr, m_curr_buf, rem_size);
	}
	*(curr_ptr + rem_size) = 0;
}

void load_session_vars(const char *target, std::map<std::string, std::string> &varmap)
{
	PHP_EXP_NODE *sess_vars_exp_node = get_var_node(target);
	PHP_VAR_NODE *sess_vars = sess_vars_exp_node->var_si_node->var;
	// i'm not building exp tree, node not needed
	delete sess_vars_exp_node;
	cast_value_array(&sess_vars->value);
	for(std::map<std::string, std::string>::iterator i = varmap.begin(); i != varmap.end(); ++i) {
		PHP_VAR_NODE *curr_var = array_get_by_str_key(&sess_vars->value, i->first);
		PHP_VALUE_NODE val;
		val.type = PHP_VAL_STRING;
		val.str_val = const_cast<char *>(i->second.c_str());
		value_value_assign(&curr_var->value, &val);
	}
}

void save_session_vars(std::map<std::string, std::string> &varmap)
{
	PHP_EXP_NODE *sess_vars_exp_node = get_var_node("_SESSION");
	PHP_VAR_NODE *sess_vars = sess_vars_exp_node->var_si_node->var;

	delete sess_vars_exp_node;
	if ( sess_vars->value.type != PHP_VAL_ARRAY ) {
		return;
	}

	for(int i = 0; i < array_get_size(&sess_vars->value); i++) {
		std::string s = array_get_ith_key(&sess_vars->value, i);
		PHP_VAR_NODE *var = array_get_by_str_key(&sess_vars->value, s);
		cast_value_str(&var->value);
		varmap[s] = var->value.str_val;
	}
}
// File_checked_for_headers
