/*  
 *  Version: MPL 1.1
 *  
 *  The contents of this file are subject to the Mozilla Public License Version 
 *  1.1 (the "License"); you may not use this file except in compliance with 
 *  the License. You may obtain a copy of the License at 
 *  http://www.mozilla.org/MPL/
 *  
 *  Software distributed under the License is distributed on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 *  for the specific language governing rights and limitations under the
 *  License.
 *  
 *  The Original Code is the sscanf 2.0 SA:MP plugin.
 *  
 *  The Initial Developer of the Original Code is Alex "Y_Less" Cole.
 *  Portions created by the Initial Developer are Copyright (C) 2010
 *  the Initial Developer. All Rights Reserved.
 *  
 *  Contributor(s):
 *  
 *  Special Thanks to:
 *  
 *  SA:MP Team past, present and future
 */

#include "SDK/amx/amx.h"

#include "sscanf.h"
#include "utils.h"

extern unsigned int
	g_iTrueMax;

extern logprintf_t
	logprintf,
	real_logprintf;

void
	qlog(char *, ...);

// Options:
//  
//  1 = OLD_DEFAULT_NAME
//  
//    Parse values in "U(5)" as if they were a name that must be connected,
//    instead of just any number.
//  
//  2 = MATCH_NAME_PARTIAL
//  
//    When searching for players, match any part of their name not just the
//    start.
//  
//  4 = CELLMIN_ON_MATCHES
//  
//    If multiple player name matches are found, return 0x80000000.
//  
//  8 = SSCANF_QUIET
//  
//    Disable all prints.
//  
int
	gOptions = 0;

void
	RestoreOpts(int opt)
{
	gOptions = opt;
	if (gOptions & 8) logprintf = qlog;
	else logprintf = real_logprintf;
}

void
	DoOptions(char * name, cell value)
{
	// Not the most flexible code I've ever written...
	if (!strincmp(name, "OLD_DEFAULT_NAME", 16))
	{
		switch (value)
		{
			case 1:
				gOptions |= 1;
				break;
			case 0:
				gOptions &= ~1;
				break;
			case -1:
				if (*(name + 16) == '=')
				{
					if (*(name + 17) == '0') gOptions &= ~1;
					else gOptions |= 1;
				}
				else
				{
					logprintf("sscanf error: No option value.");
				}
		}
	}
	else if (!strincmp(name, "MATCH_NAME_PARTIAL", 18))
	{
		switch (value)
		{
			case 1:
				gOptions |= 2;
				break;
			case 0:
				gOptions &= ~2;
				break;
			case -1:
				if (*(name + 18) == '=')
				{
					if (*(name + 19) == '0') gOptions &= ~2;
					else gOptions |= 2;
				}
				else
				{
					logprintf("sscanf error: No option value.");
				}
		}
	}
	else if (!strincmp(name, "CELLMIN_ON_MATCHES", 18))
	{
		switch (value)
		{
			case 1:
				gOptions |= 4;
				break;
			case 0:
				gOptions &= ~4;
				break;
			case -1:
				if (*(name + 18) == '=')
				{
					if (*(name + 19) == '0') gOptions &= ~4;
					else gOptions |= 4;
				}
				else
				{
					logprintf("sscanf error: No option value.");
				}
		}
	}
	else if (!strincmp(name, "SSCANF_QUIET", 12))
	{
		switch (value)
		{
			case 1:
				logprintf = qlog;
				gOptions |= 8;
				break;
			case 0:
				logprintf = real_logprintf;
				gOptions &= ~8;
				break;
			case -1:
				if (*(name + 12) == '=')
				{
					if (*(name + 13) == '0')
					{
						logprintf = real_logprintf;
						gOptions &= ~8;
					}
					else
					{
						logprintf = qlog;
						gOptions |= 8;
					}
				}
				else
				{
					logprintf("sscanf error: No option value.");
				}
		}
	}
	else if (!strincmp(name, "OLD_DEFAULT_KUSTOM", 18) || !strincmp(name, "OLD_DEFAULT_CUSTOM", 18))
	{
		switch (value)
		{
			case 1:
				gOptions |= 16;
				break;
			case 0:
				gOptions &= ~16;
				break;
			case -1:
				if (*(name + 18) == '=')
				{
					if (*(name + 19) == '0') gOptions &= ~16;
					else gOptions |= 16;
				}
				else
				{
					logprintf("sscanf error: No option value.");
				}
		}
	}
	else
	{
		logprintf("sscanf error: Unknown option name.");
	}
}

char
	GetSingleType(char ** format)
{
	char
		* cur = *format,
		tmp = *cur;
	if (tmp == '<')
	{
		++cur;
		char
			ret = *cur;
		if (ret)
		{
			++cur;
			if (*cur == '>')
			{
				*format += 3;
				return ret;
			}
			else
			{
				logprintf("sscanf warning: Unclosed specifier parameter, assuming '<', consider using something like p<<>.");
			}
		}
		else
		{
			logprintf("sscanf warning: Unenclosed specifier parameters are deprecated, consider using something like p<<>.");
		}
		++(*format);
		return '<';
	}
	else if (tmp)
	{
		// Legacy support.
		logprintf("sscanf warning: Unenclosed specifier parameters are deprecated, consider using something like p<%c>.", tmp);
		++(*format);
		return tmp;
	}
	else
	{
		logprintf("sscanf warning: No specified parameter found.");
		return ' ';
	}
}

char *
	GetMultiType(char ** format)
{
	char
		* cur = *format,
		* ret = cur;
	if (*cur == '<')
	{
		++ret;
		bool
			reop = false,
			escape = false;
		do
		{
			if (*cur == '\\')
			{
				escape = !escape;
			}
			else
			{
				escape = false;
			}
			++cur;
			if (!escape)
			{
				if (reop)
				{
					if (*cur == '>')
					{
						reop = false;
						++cur;
					}
				}
				else
				{
					if (*cur == '<')
					{
						reop = true;
						++cur;
					}
				}
			}
		}
		while (*cur && (reop || escape || *cur != '>'));
		if (*cur)
		{
			// Only gets here if there is a closing '>'.
			*cur = '\0';
			*format = cur + 1;
			return ret;
		}
		else
		{
			logprintf("sscanf error: Unclosed specifier parameters.");
		}
	}
	else
	{
		logprintf("sscanf error: No specified parameters found.");
	}
	*format = cur;
	return 0;
}

unsigned int
	GetUserString(char * string, char ** end)
{
	unsigned int
		id = 0;
	char
		cur;
	// Get the full name, they can't contain spaces in this code.  If a
	// player's name manages to contain spaces (e.g. via SetPlayerName), then
	// you can only do the part of the name up to the space.
	while ((cur = *string) && !IsSpacer(cur))
	{
		// Valid string item.
		++string;
		if ((unsigned char)(cur - '0') >= 10)
		{
			// Invalid number, so not an ID.
			break;
		}
		// Still a valid ID, continue collecting.
		id = (id * 10) + (cur - '0');
	}
	*end = string;
	// Don't use extra checks every loop any more, just one will do.
	if (!IsSpacer(cur))
	{
		// Invalid ID, just find the end of the name.
		FindSpacer(end);
		// Save the ID as too large for detection later.
		id = g_iTrueMax;
	}
	return id;
}

int
	GetDecValue(char ** const input)
{
	char *
		str = *input;
	int
		val = 0;
	unsigned char
		cur;
	// Convert to a number and test it.
	while ((cur = (unsigned char)(*str - '0')) < 10)
	{
		// Update the current value.
		val = (val * 10) + cur;
		// Update the current pointer.
		++str;
	}
	// Save the pointer.
	*input = str;
	// Covert the sign and return without an if.
	return val;
}

int
	GetDec(char ** const input)
{
	char *
		str = *input;
	int
		neg = 1;
	switch (*str)
	{
		case '-':
			neg = -1;
			// FALLTHROUGH
		case '+':
			// Check there is valid data after
			if (((unsigned char)(*(++str) - '0')) >= 10)
			{
				// Just return now, the caller will recognise this as bad.
				return 0;
			}
	}
	*input = str;
	return GetDecValue(input) * neg;
}

int
	GetOctValue(char ** const input)
{
	char *
		str = *input;
	int
		val = 0;
	unsigned char
		cur;
	// Convert to a number and test it.
	while ((cur = (unsigned char)(*str - '0')) < 8)
	{
		// Update the current value.
		val = (val * 8) + cur;
		// Update the current pointer.
		++str;
	}
	// Save the pointer.
	*input = str;
	// Covert the sign and return without an if.
	return val;
}

int
	GetOct(char ** const input)
{
	char *
		str = *input;
	int
		neg = 1;
	switch (*str)
	{
		case '-':
			neg = -1;
			// FALLTHROUGH
		case '+':
			// Check there is valid data after
			if (((unsigned char)(*(++str) - '0')) >= 8)
			{
				// Just return now, the caller will recognise this as bad.
				return 0;
			}
	}
	*input = str;
	return GetOctValue(input) * neg;
}

int
	GetHexValue(char ** const input)
{
	char *
		str = *input;
	int
		val = 0;
	// Rewrote it using a big switch.
	for ( ; ; )
	{
		switch (*str)
		{
			case '0':
				val = (val * 16) + 0x00;
				break;
			case '1':
				val = (val * 16) + 0x01;
				break;
			case '2':
				val = (val * 16) + 0x02;
				break;
			case '3':
				val = (val * 16) + 0x03;
				break;
			case '4':
				val = (val * 16) + 0x04;
				break;
			case '5':
				val = (val * 16) + 0x05;
				break;
			case '6':
				val = (val * 16) + 0x06;
				break;
			case '7':
				val = (val * 16) + 0x07;
				break;
			case '8':
				val = (val * 16) + 0x08;
				break;
			case '9':
				val = (val * 16) + 0x09;
				break;
			case 'a':
			case 'A':
				val = (val * 16) + 0x0A;
				break;
			case 'b':
			case 'B':
				val = (val * 16) + 0x0B;
				break;
			case 'c':
			case 'C':
				val = (val * 16) + 0x0C;
				break;
			case 'd':
			case 'D':
				val = (val * 16) + 0x0D;
				break;
			case 'e':
			case 'E':
				val = (val * 16) + 0x0E;
				break;
			case 'f':
			case 'F':
				val = (val * 16) + 0x0F;
				break;
			default:
				// UGLY UGLY UGLY!
				goto sscanf_hex_switch;
		}
		++str;
	}
	// UGLY UGLY UGLY - Needed for the double level break, which isn't native
	// in C.
	sscanf_hex_switch:
	// Save the pointer.
	*input = str;
	// Covert the sign and return without an if.
	return val;
}

int
	GetHex(char ** const input)
{
	char *
		str = *input;
	int
		neg = 1;
	switch (*str)
	{
		case '-':
			neg = -1;
			// FALLTHROUGH
		case '+':
			// Check there is valid data after
			if (((unsigned char)(*(++str) - '0')) >= 10)
			{
				// Just return now, the caller will recognise this as bad.
				return 0;
			}
	}
	if (*str == '0')
	{
		if (*(str + 1) == 'x' || *(str + 1) == 'X')
		{
			// Check there is real data, otherwise it's bad.
			str += 2;
			if ((*str < '0') || ((*str > '9') && ((*str | 0x20) < 'a')) || ((*str | 0x20) > 'f'))
			{
				*input = str - 1;
				return 0;
			}
		}
	}
	else if ((*str < '0') || ((*str > '9') && ((*str | 0x20) < 'a')) || ((*str | 0x20) > 'f'))
	{
		*input = str;
		return 0;
	}
	*input = str;
	return GetHexValue(input) * neg;
	// Convert to a number and test it.  Horribly manually optimised - one of
	// these days I'll try write an ASM hex reader and see how well that works.
	// Actually I think I have written one before, but I don't know where it is
	// and I'm not sure how optimised it is.  Anyway, this version works and
	// avoids loads of big switches (although a single large switch may be more
	// efficient thinking about it).
	/*while ((cur = (unsigned char)((*str | 0x20) - '0')))
	{
		if (cur < 10)
		{
			// 0 - 9
			val = (val * 16) + cur;
		}
		else
		{
			cur -= ('a' - '0');
			if (cur < 6)
			{
				// A - F, a - f
				val = (val * 16) + cur + 10;
			}
			else
			{
				// End of the number.
				// Save the pointer.
				*input = str;
				// Covert the sign and return without an if.
				return val * neg;
			}
		}
	}*/
}

unsigned int
	GetBoolValue(char ** const input)
{
	char *
		str = *input;
	unsigned int
		val = 0;
	for ( ; ; ++str)
	{
		if (*str == '0')
		{
			val <<= 1;
		}
		else if (*str == '1')
		{
			val = (val << 1) | 1;
		}
		else
		{
			break;
		}
	}
	// Save the pointer.
	*input = str;
	return val;
}

int
	GetBool(char ** const input)
{
	char *
		str = *input;
	if (*str == '0')
	{
		if (*(str + 1) == 'b' || *(str + 1) == 'B')
		{
			// Check there is real data, otherwise it's bad.
			str += 2;
			if (*str != '0' && *str != '1')
			{
				*input = str - 1;
				return 0;
			}
		}
	}
	else if (*str != '1')
	{
		*input = str;
		return 0;
	}
	*input = str;
	return (int)GetBoolValue(input);
}

int
	GetNumber(char ** const input)
{
	char *
		str = *input;
	int
		neg = 1;
	switch (*str)
	{
		case '-':
			neg = -1;
			// FALLTHROUGH
		case '+':
			// Check there is valid data after
			if (((unsigned char)(*(++str) - '0')) >= 10)
			{
				// Just return now, the caller will recognise this as bad.
				return 0;
			}
	}
	if (*str == '0')
	{
		++str;
		switch (*str)
		{
			case 'x':
			case 'X':
				// Hex.
				++str;
				if (((*str >= '0') && (*str <= '9')) || (((*str | 0x20) >= 'a') && ((*str | 0x20) <= 'f')))
				{
					*input = str;
					return GetHexValue(input) * neg;
				}
				else
				{
					*input = str - 1;
					return 0;
				}
			case 'b':
			case 'B':
				// Bool.
				if (neg == -1)
				{
					// Can't have negative booleans.
					*input = str;
					return 0;
				}
				else
				{
					++str;
					if ((*str == '0') || (*str == '1'))
					{
						*input = str;
						return (int)GetBoolValue(input);
					}
					else
					{
						*input = str - 1;
						return 0;
					}
				}
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
				// Octal.
				*input = str;
				return GetOctValue(input) * neg;
			case '8':
			case '9':
				// Decimal.
				break;
			default:
				*input = str;
				return 0;
		}
	}
	else if ((*str < '0') || (*str > '9'))
	{
		*input = str;
		return 0;
	}
	*input = str;
	return GetDecValue(input) * neg;
}

bool
	GetLogical(char ** const input)
{
	// Boolean values - if it's not '0' or 'false' then it's  true.  I could be
	// strict and make them enter '1' or 'true' for true, but I'm not going to.
	char *
		string = *input;
	bool
		ret;
	switch (*string++)
	{
		case '0':
			// Set the value to false till we decide otherwise.
			ret = false;
			while (!IsSpacer(*string))
			{
				if (*string++ != '0')
				{
					// Value is not false, thus is true.
					ret = true;
					break;
				}
			}
			// Let the cleanup code below handle excess chars.
			break;
		case 'f':
		case 'F':
			// Default to true unless we find "false" exactly.
			ret = true;
			if ((*string | 0x20) == 'a')
			{
				++string;
				if ((*string | 0x20) == 'l')
				{
					++string;
					if ((*string | 0x20) == 's')
					{
						++string;
						if ((*string | 0x20) == 'e')
						{
							++string;
							// We have the whole word, check that it's on its
							// own, i.e. is followed by a space or delimiter
							// Note that if your delimiter is in the word
							// "false", you may have a bug here but they seem
							// like odd delimiter characters tbh.
							if (IsSpacer(*string))
							{
								// Exact word found on its own so save the fact
								// that it's false.
								ret = false;
							}
						}
					}
				}
			}
			break;
		default:
			ret = true;
		break;
	}
	// Skip the rest of the data.
	*input = string;
	FindSpacer(input);
	return ret;
}

bool
	FindDefaultStart(char ** const str)
{
	// Skip the default value passed for optional parameters.
	if (**str == '(')
	{
		++(*str);
		SkipWhitespace(str);
		return true;
	}
	else
	{
		logprintf("sscanf warning: No default value found.");
	}
	return false;
}

void
	SkipDefault(char ** const str)
{
	if (FindDefaultStart(str))
	{
		// Default value found - skip it.
		do
		{
			++(*str);
		}
		while (**str && **str != ')');
		if (**str)
		{
			// Current pointer points to the close bracket, skip it.
			++(*str);
		}
		else
		{
			logprintf("sscanf warning: Unclosed default value.");
		}
	}
}

void
	SkipDefaultEx(char ** const data)
{
	// Skip the default value passed for optional parameters.
	char *
		str = *data;
	if (*str == '(')
	{
		// Default value found - skip it.
		bool
			escape = false;
		while (*str && (escape || *str != ')'))
		{
			if (*str == '\\')
			{
				// Invert the escape, they may do:
				// S(a\)
				// Where the default string is "a\", in which case we need a
				// way to tell the system not to include the close bracket,
				// which would be:
				// S(a\\)
				// Or, in real PAWN terms:
				// S(a\\\\)
				// Of course, they can also do:
				// S(\\\\\\\\\\))
				// To get a final string of "\\)".  Yes, you need FOUR slashes
				// in a PAWN string to get just one in the output string.
				escape = !escape;
			}
			else
			{
				escape = false;
			}
			++str;
		}
		if (*str)
		{
			// Current pointer points to the close bracket, skip it.
			++str;
		}
		else
		{
			logprintf("sscanf warning: Unclosed default value.");
		}
	}
	else
	{
		logprintf("sscanf warning: No default value found.");
	}
	*data = str;
}

void
	SkipLength(char ** const input)
{
	// Get an easy pointer to the data to manipulate.
	char *
		str = *input;
	if (*str == '[')
	{
		while (*(++str))
		{
			// Don't check the length is valid, that's effort and slow.
			if (*str == ']')
			{
				*input = str + 1;
				return;
			}
		}
		// If we get here then the end of the string was reached before the
		// valid end of the length.
		*input = str;
		logprintf("sscanf warning: Missing string length end.");
	}
	else
	{
		logprintf("sscanf warning: Arrays without a length are deprecated, please add a destination size.");
	}
}

int
	GetLength(char ** const input, bool error)
{
	if (**input == '[')
	{
		++(*input);
		int
			length = GetDec(input);
		char *
			str = *input;
		if (length <= 0)
		{
			if (error)
			{
				length = 0;
				logprintf("sscanf error: Invalid data length.");
			}
			else
			{
				length = SSCANF_MAX_LENGTH;
				logprintf("sscanf warning: Invalid data length.");
			}
		}
		if (*str == ']')
		{
			// Valid end: [number]
			*input = str + 1;
			return length;
		}
		else if (*str)
		{
			// Invalid character: [numberX]
			logprintf("sscanf warning: Invalid character in data length.");
			// Loop through the string till we find an end to the size.
			while (*(++str))
			{
				if (*str == ']')
				{
					// Valid end: [numberX]
					*input = str + 1;
					return length;
				}
			}
		}
		// Invalid end: [number
		logprintf("sscanf warning: Missing length end.");
		*input = str;
		return length;
	}
	else if (error)
	{
		logprintf("sscanf error: String/array must include a length, please add a destination size.");
		return 0;
	}
	else
	{
		logprintf("sscanf warning: Strings without a length are deprecated, please add a destination size.");
		return SSCANF_MAX_LENGTH;
	}
}

void
	GetJaggedSlot(cell * start, int count, int size, int slot, cell ** rs, int * rl)
{
	// Get the start of the real data after the header.
	cell
		* cur,
		* dstart = start + count,
		* dend = dstart + (count * size),
		* sstart = (cell *)((char *)(start + slot) + *(start + slot));
	while (start < dstart)
	{
		cur = (cell *)((char *)start + *start);
		// Use "<" not "<=" to avoid assigning 0-length slots.
		if (sstart < cur && cur < dend)
		{
			dend = cur;
		}
		// else if (sstart == cur && slot--)
		//{
		//	// Do something about the fact that we just found a 0 length slot.
		//}
		++start;
	}
	*rs = sstart;
	*rl = (dend - sstart);
}
