// vim: set ts=4 sw=4 tw=99 noet:
//
// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO").
// Copyright (C) The AMX Mod X Development Team.
//
// This software is licensed under the GNU General Public License, version 3 or higher.
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
//     https://alliedmods.net/amxmodx-license

#include <ctype.h>
#include "amxmodx.h"
#include "format.h"
#include "binlog.h"
#include <utf8rewind.h>

const char* stristr(const char* str, const char* substr)
{
	register char *needle = (char *)substr;
	register char *prevloc = (char *)str;
	register char *haystack = (char *)str;

	while (*haystack)
	{
		if (tolower(*haystack) == tolower(*needle))
		{
			haystack++;
			if (!*++needle)
				return prevloc;
		} else {
			haystack = ++prevloc;
			needle = (char *)substr;
		}
	}

	return NULL;
}

char* format_amxstring(AMX *amx, cell *params, int parm, int& len)
{
#if !defined BINLOG_ENABLED
	return g_langMngr.FormatAmxString(amx, params, parm, len);
#else
	char *ans = g_langMngr.FormatAmxString(amx, params, parm, len);
	if (g_binlog_level & 4)
	{
		CPluginMngr::CPlugin *pl = g_plugins.findPluginFast(amx);
		if (pl)
			g_BinLog.WriteOp(BinLog_FormatString, pl->getId(), parm, len, ans);
	}
	return ans;
#endif
}

int amxstring_len(cell* a)
{
	register int c = 0;

	while (a[c])
		++c;
	
	return c;
}

cell* get_amxaddr(AMX *amx, cell amx_addr)
{
	return (cell *)(amx->base + (int)(((AMX_HEADER *)amx->base)->dat + amx_addr));
}

int set_amxstring_simple(cell *dest, const char *source, int max)
{
	cell* start = dest;

	while (max-- && *source)
	{
		*dest++ = (unsigned char)*source++;
	}

	*dest = 0;

	return dest - start;
}

int set_amxstring(AMX *amx, cell amx_addr, const char *source, int max)
{
	register cell* dest = (cell *)(amx->base + (int)(((AMX_HEADER *)amx->base)->dat + amx_addr));
	register cell* start = dest;

#if defined BINLOG_ENABLED
	if (g_binlog_level & 2)
	{
		CPluginMngr::CPlugin *pl = g_plugins.findPluginFast(amx);
		if (pl)
			g_BinLog.WriteOp(BinLog_SetString, pl->getId(), amx_addr, max, source);
	}
#endif
	
	while (max-- && *source)
		*dest++ = (unsigned char)*source++;
	
	*dest = 0;
	
	return dest - start;
}

template int set_amxstring_utf8<cell>(AMX *, cell, const cell *, size_t, size_t);
template int set_amxstring_utf8<char>(AMX *, cell, const char *, size_t, size_t);

template <typename T>
int set_amxstring_utf8(AMX *amx, cell amx_addr, const T *source, size_t sourcelen, size_t maxlen)
{
	size_t len = sourcelen;
	bool needtocheck = false;

	register cell* dest = (cell *)(amx->base + (int)(((AMX_HEADER *)amx->base)->dat + amx_addr));
	register cell* start = dest;

	if (len > maxlen)
	{
		len = maxlen;
		needtocheck = true;
	}

	maxlen = len;

	while (maxlen-- && *source)
	{
		*dest++ = *(unsigned char*)source++;
	}

	if (needtocheck && (start[len - 1] & 1 << 7))
	{
		len -= UTIL_CheckValidChar(start + len - 1);
	}

	start[len] = '\0';

	return len;
}

int set_amxstring_utf8_char(AMX *amx, cell amx_addr, const char *source, size_t sourcelen, size_t maxlen)
{
	return set_amxstring_utf8(amx, amx_addr, source, sourcelen, maxlen);
}

int set_amxstring_utf8_cell(AMX *amx, cell amx_addr, const cell *source, size_t sourcelen, size_t maxlen)
{
	return set_amxstring_utf8(amx, amx_addr, source, sourcelen, maxlen);
}

extern "C" size_t get_amxstring_r(AMX *amx, cell amx_addr, char *destination, int maxlen)
{
	register cell *source = (cell *)(amx->base + (int)(((AMX_HEADER *)amx->base)->dat + amx_addr));
	register char *dest = destination;
	char *start = dest;

	while (maxlen-- && *source)
		*dest++=(char)(*source++);

	*dest = '\0';

#if defined BINLOG_ENABLED
	if (g_binlog_level & 2)
	{
		CPluginMngr::CPlugin *pl = g_plugins.findPluginFast(amx);
		if (pl)
			g_BinLog.WriteOp(BinLog_GetString, pl->getId(), amx_addr, destination);
	}
#endif

	return dest - start;
}

char *get_amxbuffer(int id)
{
	static char buffer[4][MAX_BUFFER_LENGTH];
	return buffer[id];
}

char *get_amxstring(AMX *amx, cell amx_addr, int id, int& len)
{
	auto buffer = get_amxbuffer(id);
	len = get_amxstring_r(amx, amx_addr, buffer, MAX_BUFFER_LENGTH - 1);
	return buffer;
}

char *get_amxstring_null(AMX *amx, cell amx_addr, int id, int& len)
{
	if (get_amxaddr(amx, amx_addr) == g_plugins.findPluginFast(amx)->getNullStringOfs())
	{
		return nullptr;
	}

	return get_amxstring(amx, amx_addr, id, len);
}

cell *get_amxvector_null(AMX *amx, cell amx_addr)
{
	cell *addr = get_amxaddr(amx, amx_addr);
	if (addr == g_plugins.findPluginFast(amx)->getNullVectorOfs())
	{
		return nullptr;
	}

	return addr;
}

void copy_amxmemory(cell* dest, cell* src, int len)
{
	while (len--)
		*dest++=*src++;
}

bool utf8isspace(const char* string)
{
	return utf8iscategory(string, 1, UTF8_CATEGORY_ISSPACE) != 0;
}

size_t utf8getspaces(const char* string)
{
	return utf8iscategory(string, SIZE_MAX, UTF8_CATEGORY_ISSPACE);
}

char* parse_arg(char** line, int& state)
{
	static char arg[3072];
	char* dest = arg;
	state = 0;
	
	while (**line)
	{
		if (utf8isspace(*line))
		{
			if (state == 1)
				break;
			else if (!state)
			{
				(*line)++;
				continue;
			}
		}
		else if (state != 2)
			state = 1;
		
		if (**line == '"')
		{
			(*line)++;
			
			if (state == 2)
				break;
			
			state = 2;
			continue;
		}
		
		*dest++ = *(*line)++;
	}
	
	*dest = '\0';
	return arg;
}

bool fastcellcmp(cell *a, cell *b, cell len)
{
	while (len--)
	{
		if (*a++ != *b++)
			return false;
	}

	return true;
}

static cell AMX_NATIVE_CALL replace(AMX *amx, cell *params) /* 4 param */
{
	cell *text = get_amxaddr(amx, params[1]);
	cell len = params[2];
	cell *what = get_amxaddr(amx, params[3]);
	cell *with = get_amxaddr(amx, params[4]);
	cell *textptr = text;

	int withLen = amxstring_len(with);
	int whatLen = amxstring_len(what);
	int textLen = amxstring_len(text);

	if (whatLen > textLen)
		return 0;

	if (whatLen < 1)
	{
		LogError(amx, AMX_ERR_NATIVE, "No search string specified.");
		return 0;
	}

	if (textLen - whatLen + withLen > len)
	{
		LogError(amx, AMX_ERR_NATIVE, "replace() buffer not big enough (%d>=%d)", (textLen - whatLen + withLen), len);
		return 0;
	}

	cell browsed = 0;
	while (*text && (browsed <= (textLen-whatLen)))
	{
		if (*text == *what)
		{
			if (fastcellcmp(text, what, whatLen))
			{
				cell *saveptr = text + whatLen;
				cell restlen = textLen - (browsed + whatLen);
				textptr = text + withLen;
				memmove(textptr, saveptr, (restlen + 1) * sizeof(cell));
				memcpy(text, with, withLen * sizeof(cell));
				return (textLen - whatLen + withLen);
			}
		}
		text++;
		browsed++;
	}
	
	return 0;
}

// native replace_string(text[], maxlength, const search[], const replace[], bool:caseSensitive = true);
static cell AMX_NATIVE_CALL replace_string(AMX *amx, cell *params)
{
	enum args { arg_count, arg_text, arg_maxlength, arg_search, arg_replace, arg_casesensitive };

	auto textLength    = 0;
	auto searchLength  = 0;
	auto replaceLength = 0;

	auto text    = get_amxstring(amx, params[arg_text]   , 0, textLength);
	auto search  = get_amxstring(amx, params[arg_search] , 1, searchLength);
	auto replace = get_amxstring(amx, params[arg_replace], 2, replaceLength);

	auto textMaxLength = params[arg_maxlength];
	auto caseSensitive = params[arg_casesensitive] != 0;

	if (!*search)
	{
		LogError(amx, AMX_ERR_NATIVE, "Cannot replace searches of empty strings.");
		return -1;
	}

	auto count = UTIL_ReplaceAll(text, textMaxLength + 1, search, searchLength, replace, replaceLength, caseSensitive); // + EOS

	set_amxstring(amx, params[arg_text], text, textMaxLength);

	return count;
}

// native replace_stringex(text[], maxlength, const search[], const replace[], searchLen = -1, replaceLen = -1, bool:caseSensitive = true);
static cell AMX_NATIVE_CALL replace_stringex(AMX *amx, cell *params)
{
	enum args { arg_count, arg_text, arg_maxlength, arg_search, arg_replace, arg_searchlen, arg_replacelen, arg_casesensitive };

	auto textLength    = 0;
	auto searchLength  = 0;
	auto replaceLength = 0;

	auto text    = get_amxstring(amx, params[arg_text]   , 0, textLength);
	auto search  = get_amxstring(amx, params[arg_search] , 1, searchLength);
	auto replace = get_amxstring(amx, params[arg_replace], 2, replaceLength);

	auto textMaxLength = params[arg_maxlength];
	auto caseSensitive = params[arg_casesensitive] != 0;

	if (params[arg_searchlen]  != -1) { searchLength  = params[arg_searchlen]; }
	if (params[arg_replacelen] != -1) { replaceLength = params[arg_replacelen]; }

	if (searchLength <= 0)
	{
		LogError(amx, AMX_ERR_NATIVE, "Cannot replace searches of empty strings.");
		return -1;
	}

	auto ptr = UTIL_ReplaceEx(text, textMaxLength + 1, search, searchLength, replace, replaceLength, caseSensitive); // + EOS

	if (!ptr)
	{
		return -1;
	}

	set_amxstring(amx, params[arg_text], text, textMaxLength);

	return ptr - text;
}

static cell AMX_NATIVE_CALL contain(AMX *amx, cell *params) /* 2 param */
{
	register cell *a = get_amxaddr(amx, params[2]);
	register cell *b = get_amxaddr(amx, params[1]);
	register cell *c = b;
	cell* str = b;
	cell* substr = a;
	
	while (*c)
	{
		if (*c == *a)
		{
			c++;
			if (!*++a)
				return b - str;
		} else {
			c = ++b;
			a = substr;
		}
	}
	
	return -1;
}

// native containi(const source[], const string[]);
static cell AMX_NATIVE_CALL containi(AMX *amx, cell *params)
{
	enum args { arg_count, arg_source, arg_search };

	auto sourceLength = 0;
	auto searchLength = 0;

	auto source = get_amxstring(amx, params[arg_source], 0, sourceLength);
	auto search = get_amxstring(amx, params[arg_search], 1, searchLength);

	if (sourceLength && searchLength)
	{
		auto sourceFolded = get_amxbuffer(2);
		auto searchFolded = get_amxbuffer(3);

		sourceLength = utf8casefold(source, sourceLength, sourceFolded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
		searchLength = utf8casefold(search, searchLength, searchFolded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);

		sourceFolded[sourceLength] = '\0';
		searchFolded[searchLength] = '\0';

		auto result = strstr(sourceFolded, searchFolded);

		if (result)
		{
			return result - sourceFolded;
		}
	}

	return -1;
}

static cell AMX_NATIVE_CALL strtonum(AMX *amx, cell *params) /* 1 param */
{
	int iLen;
	return atoi(get_amxstring(amx, params[1], 0, iLen));
}

static cell AMX_NATIVE_CALL amx_strtol(AMX *amx, cell *params) /* 3 param */
{
	int len;
	int base = params[3];

	if (base != 0 && (base < 2 || base > 36))
		base = 0;

	char *pString = get_amxstring(amx, params[1], 0, len);
	cell *endPos = get_amxaddr(amx, params[2]);

	char *pEnd = NULL;
	long result = strtol(pString, &pEnd, base);

	*endPos = pEnd - pString;

	return result;
}

static cell AMX_NATIVE_CALL amx_strtof(AMX *amx, cell *params) /* 2 param */
{
	int len;
	char *pString = get_amxstring(amx, params[1], 0, len);
	cell *endPos = get_amxaddr(amx, params[2]);

	char *pEnd = NULL;
	float result = strtod(pString, &pEnd);

	*endPos = pEnd - pString;

	return amx_ftoc(result);
}

static cell AMX_NATIVE_CALL numtostr(AMX *amx, cell *params) /* 3 param */
{
	char szTemp[32];
	sprintf(szTemp, "%d", (int)params[1]);

	return set_amxstring(amx, params[2], szTemp, params[3]);
}

static cell AMX_NATIVE_CALL str_to_float(AMX *amx, cell *params)
{
	cell *str = get_amxaddr(amx, params[1]);

	bool neg = false;
	unsigned long part1 = 0;

	if (*str == '-')
	{
		neg = true;
		++str;
	}
	else if (*str == '+')
		++str;

	while (*str)
	{
		if (*str == '.')
		{
			++str;
			break;
		}
		
		if (*str < '0' || *str > '9')
		{
			REAL fl = neg ? -static_cast<REAL>(part1) : static_cast<REAL>(part1);
			return amx_ftoc(fl);
		}

		part1 *= 10;
		part1 += *str - '0';

		++str;
	}

	unsigned long part2 = 0;
	unsigned long div = 1;
	
	while (*str)
	{
		if (*str < '0' || *str > '9')
			break;

		part2 *= 10;
		part2 += *str - '0';
		div *= 10;
		++str;
	}

	REAL fl = static_cast<REAL>(part1) + (static_cast<REAL>(part2) / div);
	
	if (neg)
		fl = -fl;
	
	return amx_ftoc(fl);
}

static cell AMX_NATIVE_CALL float_to_str(AMX *amx, cell *params)
{
	char szTemp[32];
	sprintf(szTemp, "%f", amx_ctof(params[1]));
	
	return set_amxstring(amx, params[2], szTemp, params[3]);
}

static cell AMX_NATIVE_CALL add(AMX *amx, cell *params) /* 4 param */
{
	cell *src = get_amxaddr(amx, params[3]);
	cell *dest = get_amxaddr(amx, params[1]);
	cell *start = dest;
	int c = params[2], d = params[4];
	
	while (*dest && c--)
		++dest;
	
	if (d)
	{
		while (c-- && d-- && *src)
			*dest++ =* src++;
		*dest = 0;
		
		return (dest - start);
	}

	while (c-- && *src)
		*dest++ =* src++;
	*dest = 0;
	
	return (dest-start);
}

static cell AMX_NATIVE_CALL copy(AMX *amx, cell *params) /* 4 param */
{
	cell *src = get_amxaddr(amx, params[3]);
	int c = params[2];

	cell *dest = get_amxaddr(amx, params[1]);
	cell *start = dest;

	while (c-- && *src)
	{
		*dest++ = *src++;
	}
	*dest = '\0';

	return (dest - start);
}

static cell AMX_NATIVE_CALL copyc(AMX *amx, cell *params) /* 4 param */
{
	cell *src = get_amxaddr(amx, params[3]);
	cell *dest = get_amxaddr(amx, params[1]);
	cell *start = dest;
	int c = params[2];
	cell ch = params[4];
	
	while (c-- && *src && *src != ch)
		*dest++ =* src++;
	*dest = 0;
	
	return (dest - start);
}

static cell AMX_NATIVE_CALL setc(AMX *amx, cell *params) /* 4 param */
{
	cell *src = get_amxaddr(amx, params[1]);
	int c = params[2];
	cell ch = params[3];
	
	while (c--)
		*src++ = ch;
	
	return 1;
}

static cell AMX_NATIVE_CALL equal(AMX *amx, cell *params) /* 3 param */
{
	cell *a = get_amxaddr(amx, params[1]);
	cell *b = get_amxaddr(amx, params[2]);
	int c = params[3];
	
	if (c)
	{
		while (--c && *a && (*a == *b))
			++a, ++b;
		return (*a-*b) ? 0 : 1;
	}
	
	int ret;
	
	while (!(ret = *a - *b) && *b)
		++a, ++b;
	
	return ret ? 0 : 1;
}

// native equali(const a[], const b[], c = 0);
static cell AMX_NATIVE_CALL equali(AMX *amx, cell *params)
{
	enum args { arg_count, arg_string1, arg_string2, arg_numbytes };

	auto string1Length = 0;
	auto string2Length = 0;

	auto string1 = get_amxstring(amx, params[arg_string1], 0, string1Length);
	auto string2 = get_amxstring(amx, params[arg_string2], 1, string2Length);

	auto string1Folded = get_amxbuffer(2);
	auto string2Folded = get_amxbuffer(3);

	string1Length = utf8casefold(string1, string1Length, string1Folded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
	string2Length = utf8casefold(string2, string2Length, string2Folded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);

	string1Folded[string1Length] = '\0';
	string2Folded[string2Length] = '\0';

	if (params[arg_numbytes] > 0)
	{
		return static_cast<cell>(strncmp(string1Folded, string2Folded, params[arg_numbytes]) == 0);
	}
	else
	{
		return static_cast<cell>(strcmp(string1Folded, string2Folded) == 0);
	}
}

static cell g_cpbuf[4096];

static cell AMX_NATIVE_CALL formatex(AMX *amx, cell *params)
{
	cell *buf = get_amxaddr(amx, params[1]);
	size_t maxlen = static_cast<size_t>(params[2]);
    cell *fmt = get_amxaddr(amx, params[3]);
	int param = 4;
	size_t total = atcprintf(buf, maxlen, fmt, amx, params, &param);
	return static_cast<cell>(total);
}

static cell AMX_NATIVE_CALL format(AMX *amx, cell *params) /* 3 param */
{
	cell *buf = get_amxaddr(amx, params[1]);
	cell *fmt = get_amxaddr(amx, params[3]);
	size_t maxlen = params[2];
	/** 
	 * SPECIAL CASE - check if the buffers overlap.
	 *  some users, for whatever reason, do things like:
	 *  format(buf, 255, buf....
	 *  this is considered "deprecated" but we have to support it.
	 * we do this by checking to see if reading from buf will overlap
	 */
	cell addr_start = params[1];
	cell addr_end = params[1] + maxlen * sizeof(cell);
	cell max = params[0] / sizeof(cell);
	bool copy = false;
	for (cell i = 3; i <= max; i++)
	{
		//does this clip the bounds?!?!? WELL, DOES IT!?!?! i am a loud dog
		if (params[i] >= addr_start && params[i] <= addr_end)
		{
			copy = true;
			break;
		}
	}
	if (copy)
		buf = g_cpbuf;
	int param = 4;
	size_t total = 0;

	total = atcprintf(buf, maxlen, fmt, amx, params, &param);

	if (copy)
	{
		/* copy back */
		cell *old = get_amxaddr(amx, params[1]);
		memcpy(old, g_cpbuf, (total+1) * sizeof(cell));
	}
	return total;
}

static cell AMX_NATIVE_CALL parse(AMX *amx, cell *params) /* 3 param */
{
	int inum = *params / sizeof(cell), iarg = 2, c;
	char* arg, *parse = get_amxstring(amx, params[1], 0, c);
	cell *cptr;
	int state;
	
	while (*parse)
	{
		arg = parse_arg(&parse,state);
		
		if (state)
		{
			if (inum <= iarg)
				return ((iarg - 2)>>1);
			
			cptr = get_amxaddr(amx, params[iarg++]);
			c = *get_amxaddr(amx, params[iarg++]);
			
			while (c-- && *arg)
				*cptr++ = (unsigned char)*arg++;
			*cptr = 0;
		}
	}

	return ((iarg - 2)>>1);
}

static cell AMX_NATIVE_CALL strtolower(AMX *amx, cell *params) /* 1 param */
{
	cell *cptr = get_amxaddr(amx, params[1]);
	cell *begin = cptr;
	
	while (*cptr)
	{
		*cptr = tolower(*cptr);
		cptr++;
	}

	return cptr - begin;
}

// native mb_strtolower(source[], maxlength = 0);
static cell AMX_NATIVE_CALL mb_strtolower(AMX *amx, cell *params)
{
	enum args { arg_count, arg_string, arg_maxlength };

	auto sourceLength = 0;
	auto source = get_amxstring(amx, params[arg_string], 0, sourceLength);

	auto outputMaxLength = params[arg_maxlength];

	if (outputMaxLength <= 0)
	{
		outputMaxLength = sourceLength;
	}

	auto output = get_amxbuffer(1);
	auto outputLength = utf8tolower(source, sourceLength, output, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
	
	output[outputLength] = '\0';

	return set_amxstring_utf8(amx, params[arg_string], output, outputLength, outputMaxLength);
}

static cell AMX_NATIVE_CALL strtoupper(AMX *amx, cell *params) /* 1 param */
{
	cell *cptr = get_amxaddr(amx, params[1]);
	cell *begin = cptr;
	
	while (*cptr)
	{
		*cptr = toupper(*cptr);
		cptr++;
	}
	
	return cptr - begin;
}

// native mb_strtoupper(source[], maxlength = 0);
static cell AMX_NATIVE_CALL mb_strtoupper(AMX *amx, cell *params)
{
	enum args { arg_count, arg_string, arg_maxlength };

	auto sourceLength = 0;
	auto source = get_amxstring(amx, params[arg_string], 0, sourceLength);
	
	auto outputMaxLength = params[arg_maxlength];

	if (outputMaxLength <= 0)
	{
		outputMaxLength = sourceLength;
	}

	auto output = get_amxbuffer(1);
	auto outputLength = utf8toupper(source, sourceLength, output, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);

	output[outputLength] = '\0';

	return set_amxstring_utf8(amx, params[arg_string], output, outputLength, outputMaxLength);
}

int fo_numargs(AMX *amx)
{
	unsigned char *data = amx->base + (int)((AMX_HEADER *)amx->base)->dat;
	cell bytes= *(cell *)(data + (int)amx->frm + 2 * sizeof(cell));
	
	return (int)(bytes / sizeof(cell));
}

int fo_getargnum(AMX *amx, int pos)
{
	unsigned char *data = amx->base + (int)((AMX_HEADER *)amx->base)->dat;
	cell value = *(cell *)(data + (int)amx->frm + (pos + 3) * sizeof(cell));
	
	return *(cell *)(data + (int)value);
}

float fo_getargfloat(AMX *amx, int pos)
{
	unsigned char *data = amx->base + (int)((AMX_HEADER *)amx->base)->dat;
	cell value = *(cell *)(data + (int)amx->frm + (pos + 3) * sizeof(cell));
	cell number = *(cell *)(data + (int)value);
	
	return *(REAL *)((void *)&number);
}

char* fo_getargstr(AMX *amx, int swap, int pos)
{
	unsigned char *data = amx->base + (int)((AMX_HEADER *)amx->base)->dat;
	cell src_value= *(cell *)(data + (int)amx->frm + (pos + 3) * sizeof(cell));
	cell value;
	static char buffer[2][3072];
	char* b = buffer[swap];
	int a = 0;
	
	do
	{
		value = src_value + a++ * sizeof(cell);
		value = *(cell *)(data + (int)value);
		*b++ = static_cast<char>(value);
	} while (value);

	return buffer[swap];
}

char* format_arguments(AMX *amx, int parm, int& len)
{
	static char buffer[2][3072];
	static char format[16];
	char *ptr, *arg, *dest = *buffer;
	char *src = fo_getargstr(amx, 0, parm++);
	int numparam = fo_numargs(amx);

	while (*src)
	{
		if (*src == '%' && *(src + 1))
		{
			ptr = format;
			*ptr++ = *src++;
			
			if (*src == '%')
			{
				*dest++ = *src++;
				continue;
			}
			
			while (!isalpha(*ptr++ = *src++));
			
			*ptr='\0';
			if (numparam < parm) continue;
			arg = buffer[1];
			
			switch (*(ptr - 1))
			{
				case 's': sprintf(arg, format, fo_getargstr(amx, 1, parm++)); break;
				case 'f': case 'g': sprintf(arg, format, fo_getargfloat(amx, parm++)); break;
				default: sprintf(arg, format, fo_getargnum(amx, parm++));
			}
			
			while (*arg) *dest++ = *arg++;
			continue;
		}
		*dest++ = *src++;
	}

	*dest = '\0';
	len = dest - *buffer;
	
	return *buffer;
}

//added by BAILOPAN for jtp10181
//takes a string and breaks it into a 1st param and rest params
//different from strbreak because it's more crafted for control
static cell AMX_NATIVE_CALL amx_strtok(AMX *amx, cell *params)
{
	int left_pos = 0;
	int right_pos = 0;
	unsigned int i = 0;
	bool done_flag = false;
	size_t spaces;
	int len = 0;

	//string[]
	char *string = get_amxstring(amx, params[1], 0, len);
	//left[]
	char *left = new char[len + 1];
	//right[]
	char *right = new char[len + 1];
	int leftMax = params[3];
	int rightMax = params[5];
	//token
	char token = static_cast<char>(params[6]);
	//trim
	int trim = params[7];
	
	for (i = 0; i < (unsigned int)len; i++)
	{
		if (trim && !done_flag)
		{
			if ((spaces = utf8getspaces(string + i) > 0))
			{
				i += spaces;
				done_flag = true;
			}
		}

		if (!done_flag && string[i] == token)
		{
			done_flag = true;
			i++;
		}

		if (done_flag)
		{
			right[right_pos++] = string[i];
		} else {
			left[left_pos++] = string[i];
		}
	}

	right[right_pos] = 0;
	left[left_pos] = 0;
	set_amxstring_utf8(amx, params[2], left, strlen(left), leftMax);
	set_amxstring_utf8(amx, params[4], right, strlen(right), rightMax);
	delete [] left;
	delete [] right;
	
	return 1;
}

// Same as amx_strtok but fixes and expands trim, returns token pos if token was found, -1 otherwise
static cell AMX_NATIVE_CALL amx_strtok2(AMX *amx, cell *params)
{
	int left_pos = 0, right_pos = 0, len, pos = -1;
	unsigned int i = 0;
	size_t spaces;

	char *string = get_amxstring(amx, params[1], 0, len);
	char *left = new char[len + 1], *right = new char[len + 1];
	int left_max = params[3], right_max = params[5];
	char token = static_cast<char>(params[6]);
	
	/*	Trim flags:
			1 - ltrim left
			2 - rtrim left
			4 - ltrim right
			8 - rtrim right
	*/
	int trim = params[7];

	// ltrim left
	if (trim & 1 && (spaces = utf8getspaces(string)) > 0)
	{
		i += spaces;
	}

	for (; i < (unsigned int) len; ++i)
	{
		if (string[i] == token)
		{
			pos = i;
			++i;
			break;
		}

		left[left_pos++] = string[i];
	}

	// rtrim left
	if (trim & 2 && left_pos && utf8isspace(&left[left_pos - 1]))
	{
		while (--left_pos >= 0 && utf8isspace(&left[left_pos]));
		
		++left_pos;
	}

	// ltrim right
	if (trim & 4 && (spaces = utf8getspaces(string + i)) > 0)
	{
		i += spaces;
	}

	for (; i < (unsigned int) len; ++i)
	{
		right[right_pos++] = string[i];	
	}

	// rtrim right
	if (trim & 8 && right_pos && utf8isspace(&right[right_pos - 1]))
	{
		while (--right_pos >= 0 && utf8isspace(&right[right_pos]));

		++right_pos;
	}

	right[right_pos] = 0;
	left[left_pos] = 0;

	set_amxstring_utf8(amx, params[2], left, strlen(left), left_max);
	set_amxstring_utf8(amx, params[4], right, strlen(right), right_max);

	delete [] left;
	delete [] right;

	return pos;
}

// native argparse(const text[], pos, buffer, maxlen);
static cell AMX_NATIVE_CALL argparse(AMX *amx, cell *params)
{
	int temp;
	const char *input = get_amxstring(amx, params[1], 0, temp);
	size_t input_len = size_t(temp);
	size_t start_pos = size_t(params[2]);

	cell *buffer = get_amxaddr(amx, params[3]);
	size_t buflen = size_t(params[4]);

	// Strip all left-hand whitespace.
	size_t i = start_pos;
	size_t spaces;

	if ((spaces = utf8getspaces(input + i)) > 0)
	{
		i += spaces;
	}

	if (i >= input_len) {
		*buffer = '\0';
		return -1;
	}

	cell *bufpos = buffer;

	bool in_quote = false;
	for (; i < input_len; i++) {
		// Ignore quotes, except as an indicator as to whether to stop
		// at a space.
		if (input[i] == '"') {
			in_quote = !in_quote;
			continue;
		}

		// If not in quotes, and we see a space, stop.
		if (utf8isspace(input + i) && !in_quote)
			break;

		if (size_t(bufpos - buffer) < buflen)
			*bufpos++ = (unsigned char)input[i];
	}

	*bufpos = '\0';
	return i;
}

//added by BAILOPAN :(
//Takes a string and breaks it into a 1st param and rest params
//strbreak(String[], First[], FirstLen, Rest[], RestLen)
static cell AMX_NATIVE_CALL strbreak(AMX *amx, cell *params)	/* 5 param */
{
	int _len;
	bool in_quote = false;
	bool had_quotes = false;
	size_t i = 0;
	size_t beg = 0;

	char *string = get_amxstring(amx, params[1], 0, _len);
	cell *right = get_amxaddr(amx, params[4]);
	int LeftMax = params[3];
	int RightMax = params[5];

	size_t len = (size_t)_len;
	size_t spaces;

	if ((spaces = utf8getspaces(string)) > 0)
	{
		i += spaces;
	}

	beg = i;
	for (; i<len; i++)
	{
		if (string[i] == '"' && !in_quote)
		{
			in_quote = (had_quotes = true);
		} else if (string[i] == '"' && in_quote) {
			in_quote = false;
			if (i == len-1)
				goto do_copy;
		} else {
			if (!in_quote && (spaces = utf8getspaces(string + i)) > 0)
			{
do_copy:
				size_t pos = i;
				i += spaces;

				const char *start = had_quotes ? &(string[beg+1]) : &(string[beg]);
				size_t _end = had_quotes ? (i==len-1 ? 1 : 2) : 0;
				size_t end = (pos - _end > (size_t)LeftMax) ? (size_t)LeftMax : pos - _end;
				
				// If there is anything to copy, make sure we copy min(maxlen, slicelen).
				size_t copylen = end >= beg
				                 ? ((end - beg > size_t(LeftMax))
				                    ? size_t(LeftMax)
				                    : end - beg
				                   )
				                 : 0;
				set_amxstring_utf8(amx, params[2], start, strlen(start), copylen);

				end = (len-i+1 > (size_t)RightMax) ? (size_t)RightMax : len-i+1;
				if (end)
				{
					start = &(string[i]);
					while (end--)
						*right++ = (unsigned char)*start++;
				}
				*right = '\0';
				return 1;
			}
		}
	}

	//if we got here, there was nothing to break
	set_amxstring_utf8(amx, params[2], &(string[beg]), strlen(&(string[beg])), LeftMax);
	if (RightMax)
		*right = '\0';

	return 1;
}

static cell AMX_NATIVE_CALL split_string(AMX *amx, cell *params)
{
	int textLen, splitLen;
	char *text = get_amxstring(amx, params[1], 0, textLen);
	const char *split = get_amxstring(amx, params[2], 1, splitLen);

	if (splitLen > textLen)
	{
		return -1;
	}

	int maxLen = params[4];

	/**
	* Note that it's <= ... you could also just add 1,
	* but this is a bit nicer
	*/
	for (int i = 0; i <= textLen - splitLen; i++)
	{
		if (strncmp(&text[i], split, splitLen) == 0)
		{
			/* Split hereeeee */
			if (i > maxLen)
			{
				set_amxstring_utf8(amx, params[3], text, textLen, maxLen);
			}
			else
			{
				set_amxstring_utf8(amx, params[3], text, textLen, i);
			}
			return i + splitLen;
		}
	}

	return -1;
}

static cell AMX_NATIVE_CALL format_args(AMX *amx, cell *params)
{
	int len;
	int pos = params[3];
	
	if (pos < 0)
	{
		LogError(amx, AMX_ERR_NATIVE, "Pos has to be a positive number");
		return 0;
	}

	char* string = format_arguments(amx, pos, len); // indexed from 0
	
	return set_amxstring_utf8(amx, params[1], string, len, params[2]);
}

static cell AMX_NATIVE_CALL is_digit(AMX *amx, cell *params)
{
	char chr = params[1];

	if (UTIL_GetUTF8CharBytes(&chr) != 1)
	{
		return 0;
	}

	return isdigit(chr);
}

static cell AMX_NATIVE_CALL is_alnum(AMX *amx, cell *params)
{
	char chr = params[1];

	if (UTIL_GetUTF8CharBytes(&chr) != 1)
	{
		return 0;
	}

	return isalnum(chr);
}

static cell AMX_NATIVE_CALL is_space(AMX *amx, cell *params)
{
	char chr = params[1];

	if (UTIL_GetUTF8CharBytes(&chr) != 1)
	{
		return 0;
	}

	return isspace(chr);
}

static cell AMX_NATIVE_CALL is_alpha(AMX *amx, cell *params)
{
	char chr = params[1];

	if (UTIL_GetUTF8CharBytes(&chr) != 1)
	{
		return 0;
	}

	return isalpha(chr);
}

static cell AMX_NATIVE_CALL is_char_upper(AMX *amx, cell *params)
{
	char chr = params[1];

	if (UTIL_GetUTF8CharBytes(&chr) != 1)
	{
		return 0;
	}

	return isupper(chr);
}

static cell AMX_NATIVE_CALL is_char_lower(AMX *amx, cell *params)
{
	char chr = params[1];

	if (UTIL_GetUTF8CharBytes(&chr) != 1)
	{
		return 0;
	}

	return islower(chr);
}

static cell AMX_NATIVE_CALL is_char_mb(AMX *amx, cell *params)
{
	char chr = params[1];

	unsigned int bytes = UTIL_GetUTF8CharBytes(&chr);
	if (bytes == 1)
	{
		return 0;
	}

	return bytes;
}

static cell AMX_NATIVE_CALL get_char_bytes(AMX *amx, cell *params)
{
	int len;
	char *str = get_amxstring(amx, params[1], 0, len);

	return UTIL_GetUTF8CharBytes(str);
};

static cell AMX_NATIVE_CALL amx_ucfirst(AMX *amx, cell *params)
{
	cell *str = get_amxaddr(amx, params[1]);
	
	if (!isalpha((char)str[0]) || !(str[0] & (1<<5)))
		return 0;
	str[0] &= ~(1<<5);
	
	return 1;
}

// native mb_ucfirst(string[], maxlength = 0);
static cell AMX_NATIVE_CALL mb_ucfirst(AMX *amx, cell *params)
{
	enum args { arg_count, arg_string, arg_maxlength };

	auto sourceLength = 0;
	auto source = get_amxstring(amx, params[arg_string], 0, sourceLength);

	auto outputMaxLength = params[arg_maxlength];

	if (outputMaxLength <= 0)
	{
		outputMaxLength = sourceLength;
	}

	// Retrieves the first character length in bytes.
	auto firstChLength = utf8seek(source, sourceLength, source, 1, SEEK_CUR) - source;

	if (firstChLength)
	{
		char output[8] = {};
		auto outputLength = utf8toupper(source, firstChLength, output, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);

		// The converted character is either larger or smaller in bytes.
		if (firstChLength != outputLength)
		{
			// Calculates the new string length and makes sure we don't go over the buffer size (fairly unlikely).
			sourceLength = ke::Min<int>(sourceLength + (outputLength - firstChLength), outputMaxLength);

			// Move data forward or backward minus the first character (whathever its size).
			memmove(source + outputLength, source + firstChLength, (sourceLength - outputLength) * sizeof(char));	
		}

		// Copy the new character at the start of the string.
		memcpy(source, output, outputLength);
	}

	return set_amxstring_utf8(amx, params[arg_string], source, sourceLength, outputMaxLength);
}

static cell AMX_NATIVE_CALL amx_strlen(AMX *amx, cell *params)
{
	int len;
	char *str = get_amxstring(amx, params[1], 0, len);

	return strlen(str);
}

static cell AMX_NATIVE_CALL amx_trim(AMX *amx, cell *params)
{
	int length;
	auto string = get_amxstring(amx, params[1], 0, length);

	auto leftSpaces  = utf8getspaces(string);
	auto rightSpaces = 0u;

	auto originalLength = length;

	if (leftSpaces < size_t(length))
	{
		while (--length >= 0 && utf8isspace(string + length))
		{
			++rightSpaces;
		}
	}

	auto totalSpaces = leftSpaces + rightSpaces;

	set_amxstring(amx, params[1], string + leftSpaces, originalLength - totalSpaces);

	return totalSpaces;
}

static cell AMX_NATIVE_CALL n_strcat(AMX *amx, cell *params)
{
	cell *cdest, *csrc;

	cdest = get_amxaddr(amx, params[1]);
	csrc = get_amxaddr(amx, params[2]);
	int num = params[3];
	
	while (*cdest && num)
	{
		cdest++;
		num--;
	}
	
	if (!num)
		return 0;
	
	while (*csrc && num)
	{
		*cdest++ = *csrc++;
		num--;
	}
	*cdest = 0;

	return params[3] - num;
}

// native strcmp(const string1[], const string2[], bool:ignorecase = false);
static cell AMX_NATIVE_CALL n_strcmp(AMX *amx, cell *params)
{
	enum args { arg_count, arg_string1, arg_string2, arg_ignorecase };

	auto string1Length = 0;
	auto string2Length = 0;

	auto string1 = get_amxstring(amx, params[arg_string1], 0, string1Length);
	auto string2 = get_amxstring(amx, params[arg_string2], 1, string2Length);

	if (params[arg_ignorecase] != 0)
	{
		auto string1Folded = get_amxbuffer(2);
		auto string2Folded = get_amxbuffer(3);

		string1Length = utf8casefold(string1, string1Length, string1Folded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
		string2Length = utf8casefold(string2, string2Length, string2Folded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);

		string1Folded[string1Length] = '\0';
		string2Folded[string2Length] = '\0';

		string1 = string1Folded;
		string2 = string2Folded;
	}

	return strcmp(string1, string2);
}

// native strncmp(const string1[], const string2[], num, bool:ignorecase = false);
static cell AMX_NATIVE_CALL n_strncmp(AMX *amx, cell *params)
{
	enum args { arg_count, arg_string1, arg_string2, arg_numbytes, arg_ignorecase };

	auto string1Length = 0;
	auto string2Length = 0;

	auto string1 = get_amxstring(amx, params[arg_string1], 0, string1Length);
	auto string2 = get_amxstring(amx, params[arg_string2], 1, string2Length);

	if (params[arg_ignorecase] != 0)
	{
		auto string1Folded = get_amxbuffer(2);
		auto string2Folded = get_amxbuffer(3);

		string1Length = utf8casefold(string1, string1Length, string1Folded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
		string2Length = utf8casefold(string2, string2Length, string2Folded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);

		string1Folded[string1Length] = '\0';
		string2Folded[string2Length] = '\0';

		string1 = string1Folded;
		string2 = string2Folded;
	}

	return strncmp(string1, string2, params[arg_numbytes]);
}

// native strfind(const string[], const sub[], bool:ignorecase = false, pos = 0);
static cell AMX_NATIVE_CALL n_strfind(AMX *amx, cell *params)
{
	enum args { arg_count, arg_source, arg_search, arg_ignorecase, arg_startpos };

	auto sourceLength = 0;
	auto searchLength = 0;

	auto source = get_amxstring(amx, params[arg_source], 0, sourceLength);
	auto search = get_amxstring(amx, params[arg_search], 1, searchLength);

	if (params[arg_ignorecase] != 0)
	{
		auto sourceFolded = get_amxbuffer(2);
		auto searchFolded = get_amxbuffer(3);

		sourceLength = utf8casefold(source, sourceLength, sourceFolded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);
		searchLength = utf8casefold(search, searchLength, searchFolded, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);

		sourceFolded[sourceLength] = '\0';
		searchFolded[searchLength] = '\0';

		source = sourceFolded;
		search = searchFolded;
	}

	auto position = params[arg_startpos];
	
	if (position < 0 || position > sourceLength)
	{
		return -1;
	}

	auto find = strstr(source + position, search);

	if (!find)
	{
		return -1;
	}

	return (find - source);
}

static cell AMX_NATIVE_CALL vformat(AMX *amx, cell *params)
{
	int vargPos = static_cast<int>(params[4]);

	/** get the parent parameter array */
	AMX_HEADER *hdr = (AMX_HEADER *)amx->base;
	cell *local_params = (cell *)(
		(char *)amx->base + (cell)hdr->dat +
		(cell)amx->frm + (2 * sizeof(cell))
		);

	cell max = local_params[0] / sizeof(cell);
	if (vargPos > (int)max + 1)
	{
		LogError(amx, AMX_ERR_NATIVE, "Invalid vararg parameter passed: %d", vargPos);
		return 0;
	}
	
	/**
	 * check for bounds clipping
	 */
	cell addr_start = params[1];
	cell addr_end = addr_start + params[2];
	bool copy = false;
	for (int i = vargPos; i <= max; i++)
	{
		//does this clip the bounds?
		if ( (local_params[i] >= addr_start)
			&& (local_params[i] <= addr_end) )
		{
			copy = true;
			break;
		}
	}

	/* get destination info */
	cell *fmt = get_amxaddr(amx, params[3]);
	cell *realdest = get_amxaddr(amx, params[1]);
	size_t maxlen = static_cast<size_t>(params[2]);
	cell *dest = realdest;

	/* if this is necessary... */
	static cell cpbuf[4096];
	if (copy)
		dest = cpbuf;

	/* perform format */
	size_t total = atcprintf(dest, maxlen, fmt, amx, local_params, &vargPos);

	/* copy back */
	if (copy)
	{
		memcpy(realdest, dest, (total+1) * sizeof(cell));
	}

	return total;
}


#define MAX_FMT_LENGTH 256

// native [MAX_FMT_LENGTH] fmt(const fmt[], any:...)
static cell AMX_NATIVE_CALL fmt(AMX *amx, cell *params)
{
	int length;
	const char *string = format_amxstring(amx, params, 1, length);

	size_t num_params = *params / sizeof(cell);

	set_amxstring_utf8_char(amx, params[num_params + 1], string, length, MAX_FMT_LENGTH - 1);

	return 1;
};

// native mb_strtotitle(source[], maxlength = 0);
static cell AMX_NATIVE_CALL mb_strtotitle(AMX *amx, cell *params)
{
	enum args { arg_count, arg_string, arg_maxlength };

	auto sourceLength = 0;
	auto source = get_amxstring(amx, params[arg_string], 0, sourceLength);

	auto outputMaxLength = params[arg_maxlength];

	if (outputMaxLength <= 0)
	{
		outputMaxLength = sourceLength;
	}

	auto output = get_amxbuffer(1);
	auto outputLength = utf8totitle(source, sourceLength, output, MAX_BUFFER_LENGTH - 1, UTF8_LOCALE_DEFAULT, nullptr, TRUE);

	output[outputLength] = '\0';

	return set_amxstring_utf8(amx, params[arg_string], output, outputLength, outputMaxLength);
}

// native bool:is_string_category(const input[], input_size, flags, &output_size = 0);
static cell AMX_NATIVE_CALL is_string_category(AMX *amx, cell *params)
{
	enum args { arg_count, arg_input, arg_inputsize, arg_flags, arg_outputsize };

	auto inputLength = 0;
	auto input = get_amxstring(amx, params[arg_input], 0, inputLength);

	auto inputMaxLength = ke::Min(params[arg_inputsize], inputLength);
	auto outputSize = get_amxaddr(amx, params[arg_outputsize]);

	// User wants to check only one character whatever its size.
	if (inputMaxLength <= 1)
	{
		// Gets the character length.
		inputMaxLength = utf8seek(input, inputLength, input, 1, SEEK_CUR) - input;

		// Truncated character.
		if (inputMaxLength > inputLength)
		{
			*outputSize = 0;
			return FALSE;
		}
	}

	// Checks input with the given flags.
	*outputSize = utf8iscategory(input, inputMaxLength, params[arg_flags]);

	// If function consumed input, then it's a success.
	return static_cast<cell>(*outputSize == inputMaxLength);
}

AMX_NATIVE_INFO string_Natives[] =
{
	{"add",				add},
	{"contain",			contain},
	{"containi",		containi},
	{"copy",			copy},
	{"copyc",			copyc},
	{"equal",			equal},
	{"equali",			equali},
	{"fmt",				fmt},
	{"format",			format},
	{"formatex",		formatex},
	{"format_args",		format_args},
	{"isdigit",			is_digit},
	{"isalnum",			is_alnum},
	{"isspace",			is_space},
	{"isalpha",			is_alpha},
	{"is_char_upper",	is_char_upper},
	{"is_char_lower",	is_char_lower},
	{"is_char_mb",		is_char_mb},
	{"is_string_category", is_string_category },
	{"get_char_bytes",	get_char_bytes},
	{"mb_strtotitle",	mb_strtotitle},
	{"mb_strtolower",	mb_strtolower},
	{"mb_strtoupper",	mb_strtoupper},
	{"mb_ucfirst",		mb_ucfirst},
	{"num_to_str",		numtostr},
	{"numtostr",		numtostr},
	{"parse",			parse},
	{"replace",			replace},
	{"replace_string",	replace_string},
	{"replace_stringex",replace_stringex},
	{"setc",			setc},
	{"strbreak",		strbreak},
	{"argparse",		argparse},
	{"split_string",	split_string},
	{"strtolower",		strtolower},
	{"strtoupper",		strtoupper},
	{"str_to_num",		strtonum},
	{"strtonum",		strtonum},
	{"strtol",			amx_strtol},
	{"strtof",			amx_strtof},
	{"trim",			amx_trim},
	{"ucfirst",			amx_ucfirst},
	{"strtok",			amx_strtok},
	{"strtok2",			amx_strtok2},
	{"strlen",			amx_strlen},
	{"strcat",			n_strcat},
	{"strfind",			n_strfind},
	{"strcmp",			n_strcmp},
	{"strncmp",			n_strncmp},
	{"str_to_float",	str_to_float},
	{"float_to_str",	float_to_str},
	{"vformat",			vformat},
	{NULL,				NULL}
};
