#pragma once

/*
	Expressions are evaluated according to a set of rules stored in lookup tables. Each type of operand can 
	be resolved to zero or more	"lower" types as defined by ConversionRule. Operators use OperationRules based
	on the types of each operand to	determine the result type. Types can be ambiguous at compile-time (array 
	elements, command results) but always resolve to a concrete type at run-time. Rules are applied in the order 
	they are defined until the first one matching the operands is encountered. At run-time this means the routines
	which perform the operations can know that the operands are of the expected type.
*/

struct Operator;
struct ScriptEventList;
class ExpressionEvaluator;

#include "Script.h"
#include "CommandTable.h"
#include <stack>
#include "ArrayVar.h"

#if OBLIVION
#include "StringVar.h"
#include "GameAPI.h"
#endif

extern ErrOutput g_ErrOut;

enum OperatorType : UInt8
{
	kOpType_Min		= 0,

	kOpType_Assignment	= 0,
	kOpType_LogicalOr,
	kOpType_LogicalAnd,
	kOpType_Slice,
	kOpType_Equals,
	kOpType_NotEqual,
	kOpType_GreaterThan,
	kOpType_LessThan,
	kOpType_GreaterOrEqual,
	kOpType_LessOrEqual,
	kOpType_BitwiseOr,
	kOpType_BitwiseAnd,
	kOpType_LeftShift,
	kOpType_RightShift,
	kOpType_Add,
	kOpType_Subtract,
	kOpType_Multiply,
	kOpType_Divide,
	kOpType_Modulo,
	kOpType_Exponent,
	kOpType_Negation,
	kOpType_LogicalNot,
	kOpType_LeftParen,
	kOpType_RightParen,
	kOpType_LeftBracket,
	kOpType_RightBracket,
	kOpType_In,
	kOpType_ToString,

	kOpType_Max
};

enum Token_Type : UInt8
{
	kTokenType_Number	= 0,
	kTokenType_Boolean,
	kTokenType_String,
	kTokenType_Form,
	kTokenType_Ref,
	kTokenType_Global,
	kTokenType_Array,
	kTokenType_ArrayElement,
	kTokenType_Slice,
	kTokenType_Command,
	kTokenType_Variable,
	kTokenType_NumericVar,
	kTokenType_RefVar,
	kTokenType_StringVar,
	kTokenType_ArrayVar,
	kTokenType_Ambiguous,
	kTokenType_Operator,
	kTokenType_ForEachContext,

	kTokenType_Invalid,
	kTokenType_Max = kTokenType_Invalid
};

// these are used in ParamInfo to specify expected Token_Type of args to commands taking OBSE expressions as args
enum {
	kOBSEParamType_Number =		(1 << kTokenType_Number) | (1 << kTokenType_Ambiguous),
	kOBSEParamType_Boolean =	(1 << kTokenType_Boolean) | (1 << kTokenType_Ambiguous),
	kOBSEParamType_String =		(1 << kTokenType_String) | (1 << kTokenType_Ambiguous),
	kOBSEParamType_Form =		(1 << kTokenType_Form) | (1 << kTokenType_Ambiguous),
	kOBSEParamType_Array =		(1 << kTokenType_Array) | (1 << kTokenType_Ambiguous),
	kOBSEParamType_ArrayElement = 1 << (kTokenType_ArrayElement) | (1 << kTokenType_Ambiguous),
	kOBSEParamType_Slice =		1 << kTokenType_Slice,
	kOBSEParamType_Command =	1 << kTokenType_Command,
	kOBSEParamType_Variable =	1 << kTokenType_Variable,
	kOBSEParamType_NumericVar =	1 << kTokenType_NumericVar,
	kOBSEParamType_RefVar =		1 << kTokenType_RefVar,
	kOBSEParamType_StringVar =	1 << kTokenType_StringVar,
	kOBSEParamType_ArrayVar =	1 << kTokenType_ArrayVar,
	kOBSEParamType_ForEachContext = 1 << kTokenType_ForEachContext,
	
	kOBSEParamType_Collection = kOBSEParamType_Array | kOBSEParamType_String,
	kOBSEParamType_ArrayVarOrElement = kOBSEParamType_ArrayVar | kOBSEParamType_ArrayElement,
	kOBSEParamType_ArrayIndex = kOBSEParamType_String | kOBSEParamType_Number,
	kOBSEParamType_BasicType = kOBSEParamType_Array | kOBSEParamType_String | kOBSEParamType_Number | kOBSEParamType_Form,
	kOBSEParamType_NoTypeCheck = 0,
};

struct Slice		// a range used for indexing into a string or array, expressed as arr[a:b]
{
	bool			bIsString;
	double			m_upper;
	double			m_lower;
	std::string		m_lowerStr;
	std::string		m_upperStr;

	void GetArrayBounds(ArrayKey& lo, ArrayKey& hi);
};

struct ForEachContext
{
	UInt32				sourceID;
	UInt32				iteratorID;
	UInt32				variableType;
};

// cheap, ugly polymorphism
struct ScriptToken
{
	ScriptToken();
	ScriptToken(bool boolean);
	ScriptToken(double num);
	ScriptToken(Script::RefVariable* refVar, UInt16 refIdx);
	ScriptToken(Script::VariableInfo* varInfo, UInt16 refIdx, UInt32 varType);
	ScriptToken(CommandInfo* cmdInfo, UInt16 refIdx);
	ScriptToken(std::string& str);
	ScriptToken(TESGlobal* global, UInt16 refIdx);
	ScriptToken(Operator* op);
	ScriptToken(UInt32 data, Token_Type asType);		// ArrayID or FormID
#if OBLIVION
	ScriptToken(ScriptEventList::Var* var);

	static ScriptToken* CreateForEachContext(UInt32 srcID, UInt32 iterID, UInt32 varType);
#endif

	static ScriptToken* ElementToken(ArrayID arr, ArrayKey* key);
	static ScriptToken* CreateSliceToken(Slice* slice);
	static ScriptToken	Bad() { return ScriptToken(); }		// returns an invalid script token

	Token_Type	type;
	UInt8		variableType;
	UInt16		refIdx;

	struct Value {
		std::string					str;
		union {
			Script::RefVariable		* refVar;
//			TESForm					* form;
			UInt32					formID;
			double					num;
			TESGlobal				* global;
			Operator				* op;
#if OBLIVION		// run-time only
			ArrayID					arrID;
			ScriptEventList::Var	* var;
#endif
			// compile-time only
			Script::VariableInfo	* varInfo;
			CommandInfo				* cmd;
		};
	} value;

	virtual Slice* GetSlice() { return NULL; }
	virtual ArrayKey* Key() { return NULL; }
	virtual ForEachContext* GetForEachContext() { return NULL; }

	Token_Type	Read(ExpressionEvaluator* context);	// reconstitute param from compiled data, return the type
	bool		Write(ScriptLineBuffer* buf);
	ScriptToken	ConvertTo(Token_Type to, ExpressionEvaluator* context);
	bool		IsGood()	{	return type != kTokenType_Invalid;	}
	TESForm*	GetForm() { return (type == kTokenType_Form) ? LookupFormByID(value.formID) : NULL; }
};

struct SliceToken : public ScriptToken
{
	Slice	slice;

	SliceToken(Slice* _slice) { if (_slice) { type = kTokenType_Slice; slice = *_slice; } }
	virtual Slice* GetSlice() { return &slice; }
};

#if OBLIVION
struct ArrayElementToken : public ScriptToken
{
	ArrayKey	key;

	ArrayElementToken(ArrayID arr, ArrayKey* _key);
	virtual ArrayKey* Key() { return type == kTokenType_ArrayElement ? &key : NULL; }
};

struct ForEachContextToken : public ScriptToken
{
	ForEachContext		context;

	ForEachContextToken(UInt32 srcID, UInt32 iterID, UInt32 varType);
	virtual ForEachContext* GetForEachContext() { return &context; }
};

struct ForEachToken : public ScriptToken	// a pair of arrayIDs, used for iterating over elements in an array
{
	UInt32						sourceID;
	UInt32						iteratorID;
	ScriptEventList::Var*	iterVar;
};

class ExpressionEvaluator
{
	enum { kMaxArgs = 10 };

	static UInt32			 s_numInstances;
	static bool				 s_bSuppressErrorMessages;
	static CommandReturnType s_expectedReturnType;

	UInt8			* m_scriptData;
	UInt32			* m_opcodeOffsetPtr;
	double			* m_result;
	TESObjectREFR	* m_thisObj;
	TESObjectREFR	* m_containingObj;
	UInt8			* m_data;
	ScriptToken		* m_args[kMaxArgs];
	ParamInfo		* m_params;
	UInt8			m_numArgsExtracted;
	
	UInt8*			&Data()	{ return m_data;	}
	ScriptToken*	Evaluate();
	CommandReturnType GetExpectedReturnType() { CommandReturnType type = s_expectedReturnType; s_expectedReturnType = kRetnType_Default; return type; }
public:
	static bool	Active()	{	return s_numInstances != 0;	}
	static void ToggleErrorSuppression(bool bDisable) { s_bSuppressErrorMessages = bDisable; }

	ExpressionEvaluator(COMMAND_ARGS);
	~ExpressionEvaluator();

	Script			* script;
	ScriptEventList	* eventList;

	void			Error(const char* fmt, ...);
	bool			ExtractArgs();
	ScriptToken*	Arg(UInt32 idx) { return idx < kMaxArgs ? m_args[idx] : NULL; }
	UInt8			NumArgs() { return m_numArgsExtracted; }
	static void		ExpectReturnType(CommandReturnType type) { s_expectedReturnType = type; }

	UInt8		ReadByte();
	UInt16		Read16();
	double		ReadFloat();
	std::string	ReadString();
};
#endif

// handlers for applying conversion/operation rules at run-time
typedef ScriptToken* (* Op_Eval)(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context);
typedef ScriptToken (* Token_Cast)(ScriptToken* token, ExpressionEvaluator* context);

struct ConversionRule
{
	Token_Type	to;
	Token_Cast	convert;
};

// defines an operand type and its conversion rules
struct Operand
{
	ConversionRule	* rules;
	UInt8			numRules;
};

struct OperationRule
{
	Token_Type	lhs;
	Token_Type	rhs;
	Token_Type	result;
	Op_Eval		eval;
	bool		bAsymmetric;	// does order matter? e.g. var := constant legal, constant := var illegal
};

struct Operator
{
	UInt8			precedence;
	char			symbol[3];
	UInt8			numOperands;
	OperatorType	type;
	UInt8			numRules;
	OperationRule	* rules;

	bool Precedes(Operator* op) {
		if (!IsRightAssociative())
			return op->precedence <= precedence;
		else
			return op->precedence < precedence;
	}
	bool IsRightAssociative()	{	return type == kOpType_Assignment || IsUnary();	}
	bool IsUnary()	{ return numOperands == 1;	}
	bool IsBinary() { return numOperands == 2;	}
	bool IsOpenBracket() { return (type == kOpType_LeftParen || type == kOpType_LeftBracket); }
	bool IsClosingBracket() { return (type == kOpType_RightParen || type == kOpType_RightBracket); }
	bool IsBracket() { return (IsOpenBracket() || IsClosingBracket()); }
	char GetMatchedBracket() {
		switch (type) {
			case kOpType_LeftBracket : return ']';
			case kOpType_RightBracket : return '[';
			case kOpType_LeftParen: return ')';
			case kOpType_RightParen: return '(';
			default: return 0;
		}
	}

	Token_Type GetResult(Token_Type lhs, Token_Type rhs);	// at compile-time determine type resulting from operation
	ScriptToken* Evaluate(ScriptToken* lhs, ScriptToken* rhs, ExpressionEvaluator* context);	// at run-time, operate on the operands and return result
};

class ExpressionParser
{
	enum { kMaxArgs = 10 };

	ScriptBuffer		* m_scriptBuf;
	ScriptLineBuffer	* m_lineBuf;
	UInt32				m_len;
	Token_Type			m_argTypes[kMaxArgs];
	UInt8				m_numArgsParsed;

	enum {								// varargs
		kError_CantParse,
		kError_TooManyOperators,
		kError_TooManyOperands,
		kError_MismatchedBrackets,
		kError_InvalidOperands,			// string:operator
		kError_MismatchedQuotes,
		kError_InvalidDotSyntax,
		kError_CantFindVariable,		// string:varName
		kError_ExpectedStringVariable,
		kError_UnscriptedObject,		// string:objName
		kError_TooManyArgs,				// string:commandName
		kError_RefRequired,				// string:commandName
		kError_MissingParam,			// string:paramName, int:paramIndex

		kWarning_UnquotedString,		// string:unquotedString

		kError_Max
	};

	static ErrOutput::Message	* s_Messages;

	char	Peek(UInt32 idx = -1) {
		if (idx == -1)	idx = m_lineBuf->lineOffset;
		return (idx < m_len) ? m_lineBuf->paramText[idx] : 0;
	}
	UInt32&	Offset()	{ return m_lineBuf->lineOffset; }
	const char * Text()	{ return m_lineBuf->paramText; }
	const char * CurText() { return Text() + Offset(); }

	void	Message(UInt32 errorCode, ...);

	Token_Type		Parse();
	Token_Type		ParseSubExpression(UInt32 exprLen);
	Operator *		ParseOperator(bool bExpectBinaryOperator);
	ScriptToken		ParseOperand();
	bool			ParseFunctionCall(CommandInfo* cmdInfo);
	Token_Type		PopOperator(std::stack<Operator*> & ops, std::stack<Token_Type> & operands);

	UInt32	MatchOpenBracket(Operator* openBracOp);
	std::string GetCurToken();
	Script::VariableInfo* LookupVariable(const char* varName, Script::RefVariable* refVar = NULL);

public:
	ExpressionParser(ScriptBuffer* scriptBuf, ScriptLineBuffer* lineBuf) : m_scriptBuf(scriptBuf),
		m_lineBuf(lineBuf), m_len(strlen(m_lineBuf->paramText)), m_numArgsParsed(0)
		{ memset(m_argTypes, kTokenType_Invalid, sizeof(m_argTypes)); }

	bool			ParseArgs(ParamInfo* params, UInt32 numParams);
	Token_Type		ArgType(UInt32 idx) { return idx < kMaxArgs ? m_argTypes[idx] : kTokenType_Invalid; }
};

bool CanConvertOperand(Token_Type from, Token_Type to);
void ShowRuntimeError(Script* script, const char* fmt, ...);
bool PrecompileScript(ScriptBuffer* buf);

// OBSE analogue for Cmd_Default_Parse, accepts expressions as args
bool Cmd_Expression_Parse(UInt32 numParams, ParamInfo* paramInfo, ScriptLineBuffer* lineBuf, ScriptBuffer* scriptBuf);
