/*  
 *  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 <stdio.h>

#include "SDK/amx/amx.h"

#include "utils.h"
#include "specifiers.h"
#include "data.h"
#include "sscanf.h"

extern logprintf_t
	logprintf;

#define SAVE_VALUE(m)            \
	if (cptr)                    \
		*cptr++ = m

#define SAVE_VALUE_F(m)          \
	if (cptr) {                  \
		float f = (float)m;      \
		*cptr++ = amx_ftoc(f); }

#define UPDATE_VALUES(m)         \
	if (defaults) {              \
		if (count) {             \
			diff.c = m - last.c; \
			last.c = m;          \
		} else {                 \
			last.c = m;          \
			diff.c = 0; } }      \
	SkipOneSpacer(&string);      \
	++count

#define UPDATE_VALUES_F(m)       \
	if (defaults) {              \
		if (count) {             \
			diff.d = m - last.d; \
			last.d = m;          \
		} else {                 \
			last.d = m;          \
			diff.d = 0; } }      \
	SkipOneSpacer(&string);      \
	++count

#define DO_LOOP(n)                                 \
	count < length && *string && Do##n(&string, &b)

// Macros for the regular values.
#define DO(m,n)                     \
	{m b;                           \
	while (DO_LOOP(n)) {            \
		SAVE_VALUE((cell)b);        \
		UPDATE_VALUES((cell)b); } } \
	break;

#define DOV(m,n)                        \
	{m b;                               \
	while (count < length && *string) { \
		Do##n(&string, &b);             \
		SAVE_VALUE((cell)b);            \
		UPDATE_VALUES((cell)b); } }     \
	break;

#define DOF(m,n)                \
	{m b;                       \
	while (DO_LOOP(n)) {        \
		SAVE_VALUE_F(b)         \
		UPDATE_VALUES_F(b); } } \
	break;

#define OPTIONAL_INVALID \
	logprintf("sscanf warning: Optional types invalid in array specifiers, consider using 'A'.")

#define DX(m,n) \
	OPTIONAL_INVALID;

#define DXF(m,n) \
	OPTIONAL_INVALID;

void
	GetJaggedSlot(cell *, int, int, int, cell **, int *);

bool
	DoK(AMX * amx, char ** defaults, char ** input, cell * cptr, bool optional);

extern AMX *
	g_aCurAMX;

extern int
	gOptions;

union update_u
{
	cell
		c;
	double
		d;
};

int
	DoArrayValues(char * type, char ** input, cell * cptr, int length, bool defaults, bool wholeString)
{
	int
		count = 0;
	union update_u
		last,
		diff;
	last.d = 0;
	diff.d = 0;
	char *
		string = *input;
	switch (*type++)
	{
		// Copied directly from the main loop, just with different macros.
		case 'L':
			DX(bool, L)
			// FALLTHROUGH
		case 'l':
			{
				// The only custom code here.  If the result is either "true"
				// or "false" it makes no sense to alternate in defaults as we
				// will just end up with larger values of true.
				bool
					b;
				while (count < length && *string)
				{
					DoL(&string, &b);
					SAVE_VALUE((cell)b);
					if (defaults)
					{
						last.c = (cell)b;
					}
					SkipOneSpacer(&string);
					++count;
				}
			}
			break;
		case 'B':
			DX(int, B)
			// FALLTHROUGH
		case 'b':
			DO(int, B)
		case 'N':
			DX(int, N)
			// FALLTHROUGH
		case 'n':
			DO(int, N)
		case 'C':
			DX(char, C)
			// FALLTHROUGH
		case 'c':
			DO(char, C)
		case 'I':
		case 'D':
			DX(int, I)
			// FALLTHROUGH
		case 'i':
		case 'd':
			DO(int, I)
		case 'H':
		case 'X':
			DX(int, H)
			// FALLTHROUGH
		case 'h':
		case 'x':
			DO(int, H)
		case 'O':
			DX(int, O)
			// FALLTHROUGH
		case 'o':
			DO(int, O)
		case 'F':
			DXF(double, F)
			// FALLTHROUGH
		case 'f':
			DOF(double, F)
		case 'G':
			DXF(double, G)
			// FALLTHROUGH
		case 'g':
			{
				double
					b;
				while (DO_LOOP(G))
				{
					float
						f = (float)b;
					if (cptr)
					{
						*cptr++ = amx_ftoc(f);
					}
					if (defaults)
					{
						switch (amx_ftoc(f))
						{
							default:
								if (count)
								{
									diff.d = b - last.d;
									last.d = b;
									break;
								}
								// FALLTHROUGH
							case FLOAT_NEG_INFINITY:
							case FLOAT_INFINITY:
							case FLOAT_NAN:
							case FLOAT_NAN_E:
								last.d = b;
								diff.d = 0;
								break;
						}
					}
					SkipOneSpacer(&string);
					++count;
				}
			}
			break;
		case 'U':
			DX(int, U)
			// FALLTHROUGH
		case 'u':
			if (*type == '[')
			{
				logprintf("sscanf warning: User arrays are not supported in arrays.");
			}
			if (defaults)
			{
				if (gOptions & 1)
				{
					int
						b;
					while (count < length && *string)
					{
						DoU(&string, &b, 0);
						SAVE_VALUE((cell)b);
						last.c = b;
						diff.c = 0;
						SkipOneSpacer(&string);
						++count;
					}
				}
				else
				{
					int
						b;
					while (count < length && *string)
					{
						DoI(&string, &b);
						SAVE_VALUE((cell)b);
						last.c = b;
						diff.c = 0;
						SkipOneSpacer(&string);
						++count;
					}
				}
			}
			else
			#define DoU(m,n) DoU(m,n,0)
			{
				DOV(int, U)
			}
			#undef DoU
			break;
		case 'Q':
			DX(int, Q)
			// FALLTHROUGH
		case 'q':
			if (*type == '[')
			{
				logprintf("sscanf warning: User arrays are not supported in arrays.");
			}
			if (defaults)
			{
				if (gOptions & 1)
				{
					int
						b;
					while (count < length && *string)
					{
						DoQ(&string, &b, 0);
						SAVE_VALUE((cell)b);
						last.c = b;
						diff.c = 0;
						SkipOneSpacer(&string);
						++count;
					}
				}
				else
				{
					int
						b;
					while (count < length && *string)
					{
						DoI(&string, &b);
						SAVE_VALUE((cell)b);
						last.c = b;
						diff.c = 0;
						SkipOneSpacer(&string);
						++count;
					}
				}
			}
			else
			#define DoQ(m,n) DoQ(m,n,0)
			{
				DOV(int, Q)
			}
			#undef DoQ
			break;
		case 'R':
			DX(int, R)
			// FALLTHROUGH
		case 'r':
			if (*type == '[')
			{
				logprintf("sscanf warning: User arrays are not supported in arrays.");
			}
			if (defaults)
			{
				if (gOptions & 1)
				{
					int
						b;
					while (count < length && *string)
					{
						DoR(&string, &b, 0);
						SAVE_VALUE((cell)b);
						last.c = b;
						diff.c = 0;
						SkipOneSpacer(&string);
						++count;
					}
				}
				else
				{
					int
						b;
					while (count < length && *string)
					{
						DoI(&string, &b);
						SAVE_VALUE((cell)b);
						last.c = b;
						diff.c = 0;
						SkipOneSpacer(&string);
						++count;
					}
				}
			}
			else
			#define DoR(m,n) DoR(m,n,0)
			{
				DOV(int, R)
			}
			#undef DoR
			break;
		case 'A':
		case 'a':
			logprintf("sscanf error: Multi-dimensional arrays are not supported.");
			return SSCANF_FAIL_RETURN;
		case '\'':
			logprintf("sscanf error: Search strings are not supported in arrays.");
			return SSCANF_FAIL_RETURN;
		case 'P':
		case 'p':
			logprintf("sscanf error: Delimiters are not supported in arrays.");
			return SSCANF_FAIL_RETURN;
		case '?':
			logprintf("sscanf error: Options are not supported in arrays.");
			return SSCANF_FAIL_RETURN;
		case 'K':
			DX(double, K)
			// FALLTHROUGH
		case 'k':
			//DOF(double, K)
			if (defaults)
			{
				if (DoK(g_aCurAMX, &type, &string, cptr, false) && cptr)
				{
					while (++count < length)
					{
						*(cptr + 1) = *cptr;
						++cptr;
					}
				}
			}
			else
			{
				char *
					f = type;
				while (count < length && *string && DoK(g_aCurAMX, &f, &string, cptr, false))
				{
					if (cptr) ++cptr;
					SkipOneSpacer(&string);
					++count;
					*(f - 1) = '>';
					f = type;
				}
			}
			break;
		case 's':
			//logprintf("sscanf error: Strings are not supported in arrays.");
			// Now they are (or rather, now I would like them to be, I've not
			// actually WRITTEN the code yet...).
			// This code has to read the memory pointed to by "cptr", which for
			// a multi-dimensional array points to the array header and from
			// which we can actually read the array lengths.  Note that this
			// actually means that we don't need to include the string length in
			// the array specifier, and can thus support jagged arrays!
			if (defaults)
			{
				//char *
				//	lt = type + 1;
				int
					nl = GetLength(&type, false),
					sl;
				char *
					dest;
				// Parse the default string.
				DoS(&string, &dest, 0x7FFFFFFF, true);
				if (cptr)
				{
					cell *
						sptr;
					// Send the string to PAWN.
					while (count < length)
					{
						// Get the true address as offset from the array
						// base address, and its length.
						GetJaggedSlot(cptr, length, nl, count, &sptr, &sl);
						amx_SetString(sptr, dest, 0, 0, sl);
						++count;
					}
				}
				break;
			}
			else
			{
				// Get the length.
				//char *
				//	lt = type;
				int
					nl = GetLength(&type, false),
					sl;
				char *
					dest;
				while (count < length && *string)
				{
					if (cptr)
					{
						cell *
							sptr;
						// Get the true address as offset from the array
						// base address, and its length.
						GetJaggedSlot(cptr, length, nl, count, &sptr, &sl);
						//printf("%d %d\n", nl, sl);
						//printf("%d %d %d", cptr, sptr, sl);
						DoS(&string, &dest, sl, wholeString && count == length - 1);
						amx_SetString(sptr, dest, 0, 0, sl);
					}
					else
					{
						DoS(&string, &dest, 0x7FFFFFFF, wholeString && count == length - 1);
					}
					SkipOneSpacer(&string);
					++count;
				}
				break;
			}
			//return SSCANF_FAIL_RETURN;
		case '{':
		case '}':
			logprintf("sscanf error: Quiet sections are not supported in arrays.");
			return SSCANF_FAIL_RETURN;
		default:
			logprintf("sscanf error: Unknown format specifier '%c'.", *(type - 1));
			return SSCANF_FAIL_RETURN;
	}
	// Save the end of the string.
	*input = string;
	if (defaults)
	{
		if (count < length)
		{
			if (*string)
			{
				// Error in format specifier.
				logprintf("sscanf warning: Invalid values in array defaults.");
			}
			else
			{
				switch (*(type - 1))
				{
					case 'F':
					case 'f':
					case 'G':
					case 'g':
						// Float type.
						do
						{
							last.d += diff.d;
							SAVE_VALUE_F(last.d);
							++count;
						}
						while (count < length);
						break;
					case 'K':
					case 'k':
					case 's':
						// There is no "progression" in optional strings - you
						// specify one and JUST one!
						break;
					default:
						do
						{
							// Other type.
							while (count < length)
							{
								last.c += diff.c;
								SAVE_VALUE(last.c);
								++count;
							}
						}
						while (count < length);
						break;
				}
			}
		}
		else if (*string)
		{
			logprintf("sscanf warning: Excess array defaults found.");
		}
	}
	else
	{
		if (count < length)
		{
			if (*string)
			{
				// Invalid value entered.
				return SSCANF_FAIL_RETURN;
			}
			else
			{
				// Ran out of values - check if this is an optional array at a
				// higher level.
				return SSCANF_CONT_RETURN;
			}
		}
	}
	return SSCANF_TRUE_RETURN;
}

bool
	DoA(char ** defaults, char ** input, cell * cptr, bool optional)
{
	// First, get the type of the array.
	bool
		escape = false;
	char
		* type = GetMultiType(defaults),
		* opts = 0;
	if (!type) return false;
	switch (*type)
	{
		case 'Z':
			// Don't even THINK about using "Z" - you will get THREE error
			// messages telling you off, "z" will give you 2, and "S" just 1.
			logprintf("sscanf warning: 'Z' doesn't exist - that would be an optional, deprecated optional string!.");
			// FALLTHROUGH
		case 'z':
			logprintf("sscanf warning: 'z' is deprecated, consider using 'S' instead.");
			// FALLTHROUGH
		case 'S':
			OPTIONAL_INVALID;
			*type = 's';
			break;
	}
	if (optional)
	{
		// Optional parameters - just collect the data for
		// now and use it later.
		if (**defaults == '(')
		{
			// Find the end of the options.  Based on
			// SkipDefaultEx, but saving the start point.
			++(*defaults);
			SkipWhitespace(defaults);
			// Got the start of the values.
			opts = *defaults;
			// Skip the defaults for now, we don't know the length yet.
			//while (**defaults) // && **defaults != ')')
			for ( ; ; )
			{
				switch (**defaults)
				{
					case '\0':
						goto DoA_after_loop;
					case '\\':
						escape = !escape;
						break;
					case ')':
						if (!escape)
						{
							// End the loop after ")" if it isn't escaped.
							goto DoA_after_loop;
						}
						// FALLTHROUGH.
					default:
						escape = false;
						break;
				}
				// Don't need to check for escaped ")"s here as you can't have
				// arrays of strings, which is the only place that would be.
				// Only now we do...
				++(*defaults);
			}
DoA_after_loop:
			if (**defaults)
			{
				if (opts == *defaults && *type != 's')
				{
					// No defaults found.
					logprintf("sscanf warning: Empty default values.");
					optional = false;
				}
				// Found a valid end.  Make it null for
				// later array getting (easy to detect a
				// null end and we'll never need to
				// backtrack to here in the specifiers).
				**defaults = '\0';
				++(*defaults);
			}
			else
			{
				logprintf("sscanf warning: Unclosed default value.");
			}
		}
		else
		{
			logprintf("sscanf warning: No default value found.");
			optional = false;
		}
	}
	// GetLength has "true" as arrays, being new, MUST have lengths.
	int
		length = GetLength(defaults, true);
	if (length)
	{
		// Got the length of the array, it's all good.
		if (optional)
		{
			// Optional parameters are always separated by commans, not
			// whatever the coder may choose.
			if (*type == 's') TempDelimiter(")");
			else TempDelimiter(",)");
			if (DoArrayValues(type, &opts, cptr, length, true, false) == SSCANF_FAIL_RETURN)
			{
				RestoreDelimiter();
				return false;
			}
			RestoreDelimiter();
		}
		if (input)
		{
			switch (DoArrayValues(type, input, cptr, length, false, *type == 's' && (IsEnd(**defaults) || (!cptr && **defaults == '}' && IsEnd(*(*defaults + 1))))))
			{
				case SSCANF_CONT_RETURN:
					if (optional)
					{
						return true;
					}
					else
					{
						return false;
					}
				case SSCANF_TRUE_RETURN:
					return true;
				default:
					return false;
			}
		}
		else
		{
			return true;
		}
	}
	// Default fail - only gets here if there is no length.
	return false;
}
