#include "Common.h"
#include "Scripting.h"

#define nop opcode(0)
#define _if opcode(0xD6)
#define jump_if_false opcode(0x4D)
#define jump opcode(2)

namespace GTASA
{

/*
 * Note: this is the name gta_sa uses for unnamed threads.
 */
static const char myScriptThreadName[] = "noname\0";

Script theScript;

void Script::commonInit()
{
	dwScriptLength = 0;
	memset(bScriptBuffer, 0, sizeof(bScriptBuffer));
	/*
	 * Terminate the script with a 'wait 0' ("\1\0\4\0")
	 *   This is needed because ProcessOneCommand actually
	 *   executes commands in a loop until it encounters
	 *   a wait.
	 */
	dwScriptMargin = 0x40001U;
	iScriptRepeatCount = 1;
	dwUnresolvedRefs = 0;
	dwErrors = 0;
	iBlockStackPtr = -1;

	gst.dwScriptIP = (DWORD) bScriptBuffer;
	gst.bStartNewScript = 1;
}

void Script::initialize()
{
	memset(&gst, 0, sizeof(GTASA_SCRIPT_THREAD));
	commonInit();

	memcpy(gst.strName, myScriptThreadName, sizeof(myScriptThreadName));
	/*
	 * Note: setting the dwBaseIP is optional,
	 *   but it allows jumps relative to
	 *   pData->script in the script.  Such
	 *   jumps must have a negative target.
	 */
	gst.dwBaseIP = (DWORD) bScriptBuffer;
	gst.bUnknown = 0xFFU;
	/*
	 * Note: dwWakeTime must be set to a value less
	 *   then or equal to the timer at GLOBAL_TIMER.
	 *   A value of zero will do
	 */
}

void Script::clear()
{
	commonInit();

	memset(&gst.dwReturnStack, 0, 9 * sizeof(DWORD));
	gst.bJumpFlag = 0;
	gst.dwWakeTime = 0;
	gst.wIfParam = 0;
	gst.bWastedBustedFlag = 0;
	gst.dwSceneSkipIP = 0;
}

void Script::add(const BYTE* b, DWORD count)
{
	if (!b ||
		!count ||
		!gst.bStartNewScript)	// not initialized ?
		return;
	if (dwScriptLength + count > sizeof(bScriptBuffer)) {
		dwErrors |= ERROR_OVERFLOW;
		return;
	}
	memcpy(bScriptBuffer + dwScriptLength, b, count);
	dwScriptLength += count;
}

void Script::complete()
{
	if (!gst.bStartNewScript)	// not initialized?
		return;
	/*
	 * See comment above about termination
	 */
	*(PDWORD) (bScriptBuffer + dwScriptLength) = 0x40001U;
	gst.dwScriptIP = (DWORD) bScriptBuffer;
}

void Script::invalidate()
{
	gst.bStartNewScript = 0;
}

bool Script::hasWorkToDo() const
{
	return gst.bStartNewScript && dwScriptLength && iScriptRepeatCount;
}

void Script::execute()
{
	if (!hasWorkToDo() ||
		*GA(GLOBAL_TIMER) < gst.dwWakeTime)	// note: ProcessOneCommand also does this check, but we preempt it
		return;
	if ((PBYTE) gst.dwScriptIP >= bScriptBuffer + dwScriptLength) {
		if (iScriptRepeatCount != -1 && !(--iScriptRepeatCount))
				return;
		gst.dwScriptIP = (DWORD) bScriptBuffer;
	}
	PVOID processOneCommand = GA(PROCESS_ONE_COMMAND);
	PGTASA_SCRIPT_THREAD pGst = &gst;
	__asm {
		push ecx
		mov ecx, dword ptr pGst
		call processOneCommand
		pop ecx
	}
}

void Script::setMission(BYTE isMission)
{
	gst.bMissionThread = isMission;
	gst.bIsMissionThread = isMission;
}

void Script::setRepeatCount(int repeatCount)
{
	iScriptRepeatCount = repeatCount;
}

void Script::setWastedBustedCheck(BYTE wbc)
{
	gst.bWastedBustedCheck = wbc;
}

const DWORD* Script::findLocalVar(int index) const
{
	if (index < 0)
		return NULL;
	if (gst.bMissionThread) {
		if (index > 1023)
			return NULL;
		return GA(MISSION_LOCAL_VARS) + index;
	} else {
		if (index > 33)
			return NULL;
		return gst.dwLocalVar + index;
	}
}

DWORD Script::getLocalVar(int index) const
{
	const DWORD* p = findLocalVar(index);
	if (p)
		return *p;
	return 0;
}

float Script::getLocalVarF(int index) const
{
	const DWORD* p = findLocalVar(index);
	if (p)
		return *(const float*) p;
	return 0.0F;
}

void Script::setLocalVar(int index, DWORD v)
{
	PDWORD p = (PDWORD) findLocalVar(index);
	if (!p)
		return;
	*p = v;
}

void Script::setLocalVarF(int index, float f)
{
	PDWORD p = (PDWORD) findLocalVar(index);
	if (!p)
		return;
	*(PFLOAT) p = f;
}

void Script::adjustLocalTimers(DWORD delta)
{
	if (gst.bMissionThread)
		return;
	gst.dwLocalVar[32] += delta;
	gst.dwLocalVar[33] += delta;
}

Block* Script::push()
{
	if (iBlockStackPtr == MAX_BLOCK_NESTING_LEVEL - 1)
		return NULL;
	Block* b = &blockStack[++iBlockStackPtr];
	b->reset();
	return b;
}

void Script::pop()
{
	if (iBlockStackPtr < 0)
		return;
	iBlockStackPtr--;
}

Block* Script::peek(BYTE blockType)
{
	if (iBlockStackPtr < 0)
		return NULL;
	Block* b = &blockStack[iBlockStackPtr];
	if (b->blockType != blockType)
		return NULL;
	return b;
}

Script& Script::operator<<(int v)
{
	BYTE b[1 + sizeof(int)];
	if (v == (int)(char) v) {
		b[0] = 4;
		b[1] = (char) v;
		add(b, 2);
		return *this;
	}
	if (v == (int)(short) v) {
		b[0] = 5;
		*(PSHORT) (b + 1) = (short) v;
		add(b, 3);
		return *this;
	}
	b[0] = 1;
	*(PINT) (b + 1) = v;
	add(b, 5);
	return *this;
}

Script& Script::operator<<(unsigned int v)
{
	BYTE b[1 + sizeof(unsigned int)];
	if (!(v & ~0x7FU)) {
		b[0] = 4;
		b[1] = (BYTE) v;
		add(b, 2);
		return *this;
	}
	if (!(v & ~0x7FFFU)) {
		b[0] = 5;
		*(PWORD) (b + 1) = (WORD) v;
		add(b, 3);
		return *this;
	}
	b[0] = 1;
	*(PDWORD) (b + 1) = v;
	add(b, 5);
	return *this;
}

Script& Script::operator<<(SCRIPT_COMMAND o)
{
	add((const BYTE*) &o.OpCode, 2);
	return *this;
}

Script& Script::operator<<(opcode o)
{
	add((const BYTE*) &o.op, 2);
	return *this;
}

Script& Script::operator<<(G v)
{
	BYTE b[3];
	b[0] = 2;
	*(PWORD) (b + 1) = v.var * 4U;
	add(b, 3);
	return *this;
}

Script& Script::operator<<(L v)
{
	BYTE b[3];
	b[0] = 3;
	*(PWORD) (b + 1) = v.var;
	add(b, 3);
	return *this;
}

Script& Script::operator<<(float f)
{
	BYTE b[5];
	b[0] = 6;
	*(PFLOAT) (b + 1) = f;
	add(b, 5);
	return *this;
}

Script& Script::operator<<(const char* str)
{
	BYTE b[9];
	if (!str)
		return *this;
	memset(b, 0, 9);
	b[0] = 9;
	strncpy((char*) b + 1, str, 8);
	add(b, 9);
	return *this;
}

Script& Script::operator<<(const A& a)
{
	BYTE b[7];
	b[0] = 7 + (a.t >> 1);
	*(PWORD) (b + 1) = a.v1;
	*(PWORD) (b + 3) = a.v2;
	*(PWORD) (b + 5) = ((a.t & 1) ^ 1) << 15;
	add(b, 7);
	return *this;
}

Script& Script::operator<<(manip0 m)
{
	m(*this);
	return *this;
}

Script& Script::operator<<(const smanip& sm)
{
	sm.f(sm.args, *this);
	return *this;
}

Script& Script::operator<<(Label& l)
{
	BYTE b[1 + sizeof(int)];

	/*
	 * Reference a label
	 */
	if (l.target < 0) {
		/*
		 * label already found
		 */
		operator<<(l.target);
		return *this;
	}
	/*
	 * label, not found, add reference
	 *   to reference stack
	 */
#ifndef ASSUME_FORWARD_REFS_16BIT
	b[0] = 1;
	if (l.lastBackRef > 0)
		dwErrors |= ERROR_FORWARD_REF_TOO_FAR;
	*(PINT) (b + 1) = l.lastBackRef;
	l.lastBackRef = -(int) (dwScriptLength + 1);
	add(b, 5);
#else
	b[0] = 5;
	if (l.lastBackRef > 0 || l.lastBackRef < -32768)
		dwErrors |= ERROR_FORWARD_REF_TOO_FAR;
	*(PSHORT) (b + 1) = (SHORT) l.lastBackRef;
	l.lastBackRef = -(int) (dwScriptLength + 1);
	add(b, 3);
#endif
	dwUnresolvedRefs++;
	return *this;
}

Script& Script::operator>>(Label& l)
{
	/*
	 * Set a label
	 */
	if (!dwScriptLength) {
		/*
		 * Can't set a label at position 0 due to
		 *   limitation in GTASA script engine,
		 *   so insert a nop
		 */
		operator<<(nop);
	}
	l.target = -(int) dwScriptLength;
	if (l.target >= 0
#ifdef ASSUME_FORWARD_REFS_16BIT
		|| l.target < -32768
#endif
		) {
		dwErrors |= ERROR_FORWARD_REF_TOO_FAR;
		l.target = 0;
		return *this;
	}
	while (l.lastBackRef < 0) {
#ifndef ASSUME_FORWARD_REFS_16BIT
		PINT p = (PINT) (bScriptBuffer - l.lastBackRef);
		l.lastBackRef = *p;
		*p = l.target;
#else
		PSHORT p = (PSHORT) (bScriptBuffer - l.lastBackRef);
		l.lastBackRef = *p;
		*p = (SHORT) l.target;
#endif
		dwUnresolvedRefs--;
	}
	if (l.lastBackRef > 0)
		dwErrors |= ERROR_FORWARD_REF_TOO_FAR;
	return *this;
}

void endparams(Script& s)
{
	BYTE b = 0;
	s.add(&b, 1);
}

void beginscript(Script& s)
{
	s.clear();
}

void endscript(Script& s)
{
	s.complete();
}

void If(Script& s)
{
	Block* b = s.push();
	if (!b) {
		s.dwErrors |= ERROR_NESTING;
		return;
	}
	b->blockType = 1;
	b->addr = s.dwScriptLength + 3;
	s << _if << 0;
}

void And(Script& s)
{
	Block* b = s.peek(1);
	if (!b) {
		s.dwErrors |= ERROR_NESTING;
		return;
	}
	if (!b->addr) {
		s.dwErrors |= ERROR_CONDITIONAL;
		return;
	}
	BYTE& c = s.bScriptBuffer[b->addr];
	if (c >= 8U) {
		s.dwErrors |= ERROR_CONDITIONAL;
		return;
	}
	c++;
}

void Or(Script& s)
{
	Block* b = s.peek(1);
	if (!b) {
		s.dwErrors |= ERROR_NESTING;
		return;
	}
	if (!b->addr) {
		s.dwErrors |= ERROR_CONDITIONAL;
		return;
	}
	BYTE& c = s.bScriptBuffer[b->addr];
	if (!c) {
		c = 20U;
		return;
	}
	if (c < 20U || c >= 28) {
		s.dwErrors |= ERROR_CONDITIONAL;
		return;
	}
	c++;
}

void Then(Script& s)
{
	Block* b = s.peek(1);
	if (!b) {
		s.dwErrors |= ERROR_NESTING;
		return;
	}
	if (!b->label1.isEmpty()) {
		s.dwErrors |= ERROR_CONDITIONAL;
		return;
	}
	b->addr = 0;
	s << jump_if_false << b->label1;
}

void BareThen(Script& s)
{
	Block* b = s.peek(1);
	if (!b) {
		s.dwErrors |= ERROR_NESTING;
		return;
	}
	b->addr = 0;
}

void Else(Script& s)
{
	Block* b = s.peek(1);
	if (!b) {
		s.dwErrors |= ERROR_NESTING;
		return;
	}
	if (b->addr) {
		s.dwErrors |= ERROR_CONDITIONAL;
		return;
	}
	s << jump << b->label2;
	s >> b->label1;
	b->label1.reset();
}

void BareElse(Script& s)
{
	Block* b = s.peek(1);
	if (!b) {
		s.dwErrors |= ERROR_NESTING;
		return;
	}
	if (b->addr) {
		s.dwErrors |= ERROR_CONDITIONAL;
		return;
	}
	s >> b->label1;
	b->label1.reset();
}

void ElseIf(Script& s)
{
	Block* b = s.peek(1);
	if (!b) {
		s.dwErrors |= ERROR_NESTING;
		return;
	}
	if (b->addr) {
		s.dwErrors |= ERROR_CONDITIONAL;
		return;
	}
	s << jump << b->label2;
	s >> b->label1;
	b->label1.reset();
	b->addr = s.dwScriptLength + 3;
	s << _if << 0;
}

void BareElseIf(Script& s)
{
	Block* b = s.peek(1);
	if (!b) {
		s.dwErrors |= ERROR_NESTING;
		return;
	}
	if (b->addr) {
		s.dwErrors |= ERROR_CONDITIONAL;
		return;
	}
	s >> b->label1;
	b->label1.reset();
	b->addr = s.dwScriptLength + 3;
	s << _if << 0;
}

void EndIf(Script& s)
{
	Block* b = s.peek(1);
	if (!b) {
		s.dwErrors |= ERROR_NESTING;
		return;
	}
	if (!b->label1.isResolved())
		s >> b->label1;
	if (!b->label2.isResolved())
		s >> b->label2;
	s.pop();
}

opcode operator!(SCRIPT_COMMAND o)
{
	return opcode(o.OpCode | 0x8000);
}

opcode operator!(opcode o)
{
	return opcode(o.op | 0x8000);
}

A::A(G _v1, G _v2)
{
	v1 = _v1.var * 4U;
	v2 = _v2.var * 4U;
	t = 0;
}

A::A(G _v1, L _v2)
{
	v1 = _v1.var * 4U;
	v2 = _v2.var;
	t = 1;
}

A::A(L _v1, G _v2)
{
	v1 = _v1.var;
	v2 = _v2.var * 4U;
	t = 2;
}

A::A(L _v1, L _v2)
{
	v1 = _v1.var;
	v2 = _v2.var;
	t = 3;
}

Vector Vector::operator+(const Vector& v)
{
	Vector nv;
	nv.X = X + v.X;
	nv.Y = Y + v.Y;
	nv.Z = Z + v.Z;
	return nv;
}

Vector& Vector::operator+=(const Vector& v)
{
	X += v.X;
	Y += v.Y;
	Z += v.Z;
	return *this;
}

};