#pragma once

#define MAX_SCRIPT_SIZE 544
#define ASSUME_FORWARD_REFS_16BIT
#define MAX_BLOCK_NESTING_LEVEL 8

#define ERROR_OVERFLOW 1
#define ERROR_FORWARD_REF_TOO_FAR 2
#define ERROR_CONDITIONAL 4
#define ERROR_NESTING 8

struct GTASA_SCRIPT_THREAD	// 0xE0 bytes total
{
	void* pNext;			// 0x00
	void* pPrev;			// 0x04
	char strName[8];		// 0x08
	DWORD dwBaseIP;			// 0x10
	DWORD dwScriptIP;		// 0x14
	DWORD dwReturnStack[8];	// 0x18
	WORD dwStackPointer;	// 0x38
	DWORD dwLocalVar[34];	// 0x3C
	BYTE bStartNewScript;	// 0xC4
	BYTE bJumpFlag;			// 0xC5
	BYTE bIsMissionThread;	// 0xC6
	BYTE bIsExternalScript;	// 0xC7
	BYTE bInMenu;			// 0xC8
	BYTE bUnknown;			// 0xC9
	DWORD dwWakeTime;		// 0xCC
	WORD wIfParam;			// 0xD0
	BYTE bNotFlag;			// 0xD2
	BYTE bWastedBustedCheck;// 0xD3
	BYTE bWastedBustedFlag;	// 0xD4
	DWORD dwSceneSkipIP;	// 0xD8
	BYTE bMissionThread;	// 0xDC
};

typedef struct GTASA_SCRIPT_THREAD* PGTASA_SCRIPT_THREAD;

namespace GTASA
{

class Script;

struct G {	// used for global variables
	WORD var;
	G(WORD v) : var(v) {}
};

struct L {	// used for local variables
	WORD var;
	L(WORD v) : var(v) {}
};

struct A {	// used for array references
	WORD v1, v2, t;

	A(G, G);
	A(G, L);
	A(L, G);
	A(L, L);
};

struct SCRIPT_COMMAND {	// used for statically defined opcodes
	WORD OpCode;
#if 0
	char Params[13];	// this is only for compatibility with spookie's code, it's not used
#endif
};

struct opcode {	// used for dynamically constructed opcodes
	WORD op;
	opcode(WORD o) : op(o) {}
};

struct Label {	// used for jump targets
	int target;
	int lastBackRef;
	Label() : target(0), lastBackRef(0) {}
	void reset() { target = 0; lastBackRef = 0; }
	bool isResolved() const { return !lastBackRef; }
	bool isEmpty() const { return !target && !lastBackRef; }
};

struct Block {	// used for nesting blocks
	BYTE blockType;
	DWORD addr;
	Label label1, label2;
	Block() : blockType(0), addr(0) {}
	void reset() { blockType = 0; addr = 0; label1.reset(); label2.reset(); }
};

typedef void (*manip0)(Script&);	// used for manipulators with no parameters

struct smanip { // delegate for making parametered manipulators
	PVOID args;		// opaque pointer to container of manipulator args
	void (__stdcall *f)(PVOID, Script&);	// manipulating function
};

#define MANIP0_PROTOTYPE(name) void name(Script&)

struct Vector {
	float X, Y, Z;
	Vector operator+(const Vector&);
	Vector& operator+=(const Vector&);
};

class Script {
private:
	GTASA_SCRIPT_THREAD gst;
	DWORD dwScriptLength;
	BYTE bScriptBuffer[MAX_SCRIPT_SIZE];
	DWORD dwScriptMargin;		// used to terminate the script with 'wait 0'
	int iScriptRepeatCount;		// a value of -1 will repeat forever
	DWORD dwUnresolvedRefs;
	DWORD dwErrors;
	Block blockStack[MAX_BLOCK_NESTING_LEVEL];
	int iBlockStackPtr;

	void commonInit();
	const DWORD* findLocalVar(int index) const;
	Block* push();
	void pop();
	Block* peek(BYTE blockType);

	friend MANIP0_PROTOTYPE(If);
	friend MANIP0_PROTOTYPE(And);
	friend MANIP0_PROTOTYPE(Or);
	friend MANIP0_PROTOTYPE(Then);
	friend MANIP0_PROTOTYPE(BareThen);
	friend MANIP0_PROTOTYPE(Else);
	friend MANIP0_PROTOTYPE(BareElse);
	friend MANIP0_PROTOTYPE(ElseIf);
	friend MANIP0_PROTOTYPE(BareElseIf);
	friend MANIP0_PROTOTYPE(EndIf);

public:
	void initialize();						// 1st time initialization
	void clear();							// clear for reuse
	void add(const BYTE* b, DWORD count);	// add script chunk
	void complete();						// terminate script
	void invalidate();						// mark script so it won't be executed
	bool hasWorkToDo() const;				// is there anything to execute?
	void execute();							// execute script

	DWORD getUnresolvedRefs() const { return dwUnresolvedRefs; }
	DWORD getErrors() const { return dwErrors; }
	bool hasOverflowed() const { return (dwErrors & ERROR_OVERFLOW) != 0; }
	bool hasForwardRefTooFar() const { return (dwErrors & ERROR_FORWARD_REF_TOO_FAR) != 0; }
	bool hasConditionalError() const { return (dwErrors & ERROR_CONDITIONAL) != 0; }
	bool hasNestingError() const { return (dwErrors & ERROR_NESTING) != 0; }
	void setMission(BYTE isMission);
	void setRepeatCount(int repeatCount);
	int getRepeatCount() const { return iScriptRepeatCount; }
	BYTE getJumpFlag() const { return gst.bJumpFlag; }
	BYTE isMission() const { return gst.bMissionThread; }
	void setWastedBustedCheck(BYTE wbc);
	BYTE getWastedBustedCheck() const { return gst.bWastedBustedCheck; }
	BYTE getWastedBustedFlag() const { return gst.bWastedBustedFlag; }
	DWORD getLocalVar(int index) const;
	float getLocalVarF(int index) const;
	void setLocalVar(int index, DWORD v);
	void setLocalVarF(int index, float f);
	void adjustLocalTimers(DWORD delta);

	Script& operator<<(int);
	Script& operator<<(unsigned int);
	Script& operator<<(SCRIPT_COMMAND);
	Script& operator<<(opcode);
	Script& operator<<(G);
	Script& operator<<(L);
	Script& operator<<(float);
	Script& operator<<(double d) { return operator<<((float) d); }
	Script& operator<<(const Vector& v) { return *this << v.X << v.Y << v.Z; }
	Script& operator<<(const char*);
	Script& operator<<(const A&);
	Script& operator<<(manip0);
	Script& operator<<(const smanip&);
	Script& operator<<(Label&);
	Script& operator>>(Label&);
};

/*
 * Manipulators
 */
MANIP0_PROTOTYPE(endparams);		// terminates variable-length param list with 0
MANIP0_PROTOTYPE(beginscript);		// calls clear()
MANIP0_PROTOTYPE(endscript);		// calls complete()
MANIP0_PROTOTYPE(If);
MANIP0_PROTOTYPE(And);
MANIP0_PROTOTYPE(Or);
MANIP0_PROTOTYPE(Then);
MANIP0_PROTOTYPE(BareThen);
MANIP0_PROTOTYPE(Else);
MANIP0_PROTOTYPE(BareElse);
MANIP0_PROTOTYPE(ElseIf);
MANIP0_PROTOTYPE(BareElseIf);
MANIP0_PROTOTYPE(EndIf);

#undef MANIP0_PROTOTYPE

/*
 * Other functions
 */
opcode operator!(SCRIPT_COMMAND);
opcode operator!(opcode);			// for inverting conditional opcodes

extern Script theScript;

};