/* ScummVM - Scumm Interpreter
 * Copyright (C) 2004-2005 The ScummVM project
 *
 * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $Header: /cvsroot/scummvm/scummvm/saga/script.h,v 1.112.2.1 2005/10/18 02:11:20 sev Exp $
 *
 */

// Scripting module private header

#ifndef SAGA_SCRIPT_H
#define SAGA_SCRIPT_H

#include "saga/font.h"
#include "saga/list.h"

namespace Saga {

#define COMMON_BUFFER_SIZE 1024   // Why 1024?

#define S_LUT_ENTRYLEN_ITECD 22
#define S_LUT_ENTRYLEN_ITEDISK 16

#define SCRIPT_TBLENTRY_LEN 4

#define SCRIPT_MAX 5000

#define ITE_SCRIPT_FUNCTION_MAX 78
#define IHNM_SCRIPT_FUNCTION_MAX 105
#define DEFAULT_THREAD_STACK_SIZE 256

enum AddressTypes {
	kAddressCommon     = 0,	// offset from global variables
	kAddressStatic     = 1,	// offset from global variables
	kAddressModule     = 2,	// offset from start of module
	kAddressStack      = 3,	// offset from stack
	kAddressThread     = 4	// offset from thread structure
/*	kAddressId         = 5,	// offset from const id object
	kAddressIdIndirect = 6,	// offset from stack id object
	kAddressIndex      = 7	// index from id*/
};

enum VerbTypes {
	kVerbNone = 0,
	kVerbPickUp = 1,
	kVerbLookAt = 2,
	kVerbWalkTo = 3,
	kVerbTalkTo = 4,
	kVerbOpen = 5,
	kVerbClose = 6,
	kVerbGive = 7,
	kVerbUse = 8,
	kVerbOptions = 9,
	kVerbEnter = 10,
	kVerbLeave = 11,
	kVerbBegin = 12,
	kVerbWalkOnly = 13,
	kVerbLookOnly = 14,

//additional IHNM
	kVerbTake = 15,
	kVerbSwallow = 16,
	kVerbPush = 17,
/*
	kVerbIHNMNone = 0,
	kVerbIHNMWalkTo = 1,
	kVerbIHNMLookAt = 2,
	kVerbIHNMTake = 3,
	kVerbIHNMUse = 4,
	kVerbIHNMTalkTo = 5,
	kVerbIHNMSwallow = 6,
	kVerbIHNMGive = 7,
	kVerbIHNMPush = 8,
*/
	kVerbTypesMax = kVerbPush + 1
};

#define STHREAD_TIMESLICE 8

enum ThreadVarTypes {
	kThreadVarObject = 0,
	kThreadVarWithObject = 1,
	kThreadVarAction = 2,
	kThreadVarActor = 3,

	kThreadVarMax = kThreadVarActor + 1
};

enum ThreadFlags {
	kTFlagNone = 0,
	kTFlagWaiting = 1,	// wait for even denoted in waitType
	kTFlagFinished = 2,
	kTFlagAborted = 4,
	kTFlagAsleep = kTFlagWaiting | kTFlagFinished | kTFlagAborted	// Combination of all flags which can halt a thread
};

enum ThreadWaitTypes {
	kWaitTypeNone = 0,			// waiting for nothing
	kWaitTypeDelay = 1,			// waiting for a timer
	kWaitTypeSpeech = 2,		// waiting for speech to finish
	kWaitTypeDialogEnd = 3,		// waiting for my dialog to finish
	kWaitTypeDialogBegin = 4,	// waiting for other dialog to finish
	kWaitTypeWalk = 5,			// waiting to finish walking
	kWaitTypeRequest = 6,		// a request is up
	kWaitTypePause = 7,
	kWaitTypePlacard = 8,
	kWaitTypeStatusTextInput = 9,
	kWaitTypeWaitFrames = 10    // IHNM. waiting for a frame count
};

enum OpCodes {
	opNextBlock = 0x01,
	opDup = 0x02,
	opDrop = 0x03,
	opZero = 0x04,
	opOne = 0x05,
	opConstint = 0x06,
//...
	opStrlit = 0x08,
//...
	opGetFlag = 0x0B,
	opGetInt = 0x0C,
//...
	opPutFlag = 0x0F,
	opPutInt = 0x10,
	//...
	opPutFlagV = 0x13,
	opPutIntV = 0x14,
//...
	opCall = 0x17,
	opCcall = 0x18,
	opCcallV = 0x19,
	opEnter = 0x1A,
	opReturn = 0x1B,
	opReturnV = 0x1C,
	opJmp = 0x1D,
	opJmpTrueV = 0x1E,
	opJmpFalseV = 0x1F,
	opJmpTrue = 0x20,
	opJmpFalse = 0x21,
	opJmpSwitch = 0x22,
//...
	opJmpRandom = 0x24,
	opNegate = 0x25,
	opNot = 0x26,
	opCompl = 0x27,
	opIncV = 0x28,
	opDecV = 0x29,
	opPostInc = 0x2A,
	opPostDec = 0x2B,
	opAdd = 0x2C,
	opSub = 0x2D,
	opMul = 0x2E,
	opDiv = 0x2F,
	opMod = 0x30,
//...
	opEq = 0x33,
	opNe = 0x34,
	opGt = 0x35,
	opLt = 0x36,
	opGe = 0x37,
	opLe = 0x38,
//...
	opRsh = 0x3F,
	opLsh = 0x40,
	opAnd = 0x41,
	opOr = 0x42,
	opXor = 0x43,
	opLAnd = 0x44,
	opLOr = 0x45,
	opLXor = 0x46,
//...
	opSpeak = 0x53,
	opDialogBegin = 0x54,
	opDialogEnd = 0x55,
	opReply = 0x56,
	opAnimate = 0x57
};

enum CycleFlags {
	kCyclePong    = 1 << 0,
	kCycleOnce    = 1 << 1,
	kCycleRandom  = 1 << 2,
	kCycleReverse = 1 << 3
};

enum WalkFlags {
	kWalkBackPedal = 1 << 0,
	kWalkAsync     = 1 << 1,
	kWalkUseAngle  = 1 << 2,
	kWalkFace      = 1 << 5
};

enum ReplyFlags {
	kReplyOnce      = 1 << 0,
	kReplySummary   = 1 << 1,
	kReplyCondition = 1 << 2
};

struct EntryPoint {
	uint16 nameOffset;
	uint16 offset;
};

struct VoiceLUT {
	uint16 voicesCount;
	uint16 *voices;
	void freeMem() {
		voicesCount = 0;
		free(voices);
	}
	VoiceLUT() {
		memset(this, 0, sizeof(*this));
	}
};

struct ModuleData {
	bool loaded;			// is it loaded or not?
	int scriptResourceId;
	int stringsResourceId;
	int voicesResourceId;

	byte *moduleBase;				// all base module
	uint16 moduleBaseSize;			// base module size
	uint16 staticSize;				// size of static data
	uint staticOffset;				// offset of static data begining in _commonBuffer

	uint16 entryPointsTableOffset;	// offset of entrypoint table in moduleBase
	uint16 entryPointsCount;
	EntryPoint *entryPoints;

	StringsTable strings;
	VoiceLUT voiceLUT;
	void freeMem() {
		strings.freeMem();
		voiceLUT.freeMem();
		free(moduleBase);
		free(entryPoints);
		memset(this, 0x0, sizeof(*this));
	}
};

class ScriptThread {
public:
	uint16 *_stackBuf;
	uint16 _stackSize;					// stack size in uint16

	uint16 _stackTopIndex;
	uint16 _frameIndex;

	uint16 _threadVars[kThreadVarMax];

	byte *_moduleBase;					//
	uint16 _moduleBaseSize;

	byte *_commonBase;					//
	byte *_staticBase;					//
	VoiceLUT *_voiceLUT;				//
	StringsTable *_strings;				//

	int _flags;							// ThreadFlags
	int _waitType;						// ThreadWaitTypes
	uint _sleepTime;
	void *_threadObj;					// which object we're handling

	int16 _returnValue;

	uint16 _instructionOffset;			// Instruction offset

	int32 _frameWait;

public:
	byte *baseAddress(byte addrMode) {
		switch (addrMode) {
		case kAddressCommon:
			return _commonBase;
		case kAddressStatic:
			return _staticBase;
		case kAddressModule:
			return _moduleBase;
		case kAddressStack:
			return (byte*)&_stackBuf[_frameIndex];
		case kAddressThread:
			return (byte*)_threadVars;
		default:
			return _commonBase;
		}
	}

	int16 stackTop() {
		return (int16)_stackBuf[_stackTopIndex];
	}

	uint pushedSize() {
		return _stackSize - _stackTopIndex - 2;
	}

	void push(int16 value) {
		if (_stackTopIndex <= 0) {
			error("ScriptThread::push() stack overflow");
		}
		_stackBuf[--_stackTopIndex] = (uint16)value;
	}

	int16 pop() {
		if (_stackTopIndex >= _stackSize) {
			error("ScriptThread::pop() stack underflow");
		}
		return (int16)_stackBuf[_stackTopIndex++];
	}


// wait stuff
	void wait(int waitType) {
		_waitType = waitType;
		_flags |= kTFlagWaiting;
	}

	void waitWalk(void *threadObj) {
		wait(kWaitTypeWalk);
		_threadObj = threadObj;
	}

	void waitDelay(int sleepTime) {
		wait(kWaitTypeDelay);
		_sleepTime = sleepTime;
	}

	ScriptThread() {
		memset(this, 0xFE, sizeof(*this));
		_stackBuf = NULL;
	}
	~ScriptThread() {
		free(_stackBuf);
	}
};

typedef SortedList<ScriptThread> ScriptThreadList;


#define SCRIPTFUNC_PARAMS ScriptThread *thread, int nArgs, bool &disContinue

class Script {
public:
	StringsTable _mainStrings;

	Script(SagaEngine *vm);
	~Script();

	void loadModule(int scriptModuleNumber);
	void freeModules();

	void doVerb();
	void showVerb(int statusColor = -1);
	void setVerb(int verb);
	int getCurrentVerb() const { return _currentVerb; }
	void setPointerVerb();
	void whichObject(const Point& mousePoint);
	void hitObject(bool leftButton);
	void playfieldClick(const Point& mousePoint, bool leftButton);

	void setLeftButtonVerb(int verb);
	int getLeftButtonVerb() const { return _leftButtonVerb; }
	void setRightButtonVerb(int verb);
	int getRightButtonVerb() const { return _rightButtonVerb; }
	void setNonPlayfieldVerb() {
		setRightButtonVerb(kVerbNone);
		_pointerObject = ID_NOTHING;
		_currentObject[_firstObjectSet ? 1 : 0] = ID_NOTHING;
	}
	void setNoPendingVerb() {
		_pendingVerb = kVerbNone;
		_currentObject[0] = _currentObject[0] = ID_NOTHING;
		setPointerVerb();
	}

private:
	// When reading or writing data to the common buffer, we have to use a
	// well-defined byte order since it's stored in savegames. Otherwise,
	// we use native byte ordering since that data may be accessed in other
	// ways than through these functions.
	//
	// The "module" area is a special case, which possibly never ever
	// happens. But if it does, we need well-defined byte ordering.

	uint16 readUint16(byte *addr, byte addrMode) {
		switch (addrMode) {
		case kAddressCommon:
		case kAddressStatic:
		case kAddressModule:
			return READ_LE_UINT16(addr);
		default:
			return READ_UINT16(addr);
		}
	}

	void writeUint16(byte *addr, uint16 value, byte addrMode) {
		switch (addrMode) {
		case kAddressCommon:
		case kAddressStatic:
		case kAddressModule:
			WRITE_LE_UINT16(addr, value);
			break;
		default:
			WRITE_UINT16(addr, value);
			break;
		}
	}

	SagaEngine *_vm;
	ResourceContext *_scriptContext;

	uint16 _modulesLUTEntryLen;
	ModuleData *_modules;
	int _modulesCount;
	TextListEntry *_placardTextEntry;

protected:
	friend class SagaEngine;
	byte *_commonBuffer;
	uint _commonBufferSize;

private:
	uint _staticSize;

	ScriptThreadList _threadList;

	ScriptThread *_conversingThread;

//verb
	bool _firstObjectSet;
	bool _secondObjectNeeded;
	uint16 _currentObject[2];
	int16 _currentObjectFlags[2];
	int _currentVerb;
	int _stickyVerb;
	int _leftButtonVerb;
	int _rightButtonVerb;

public:
	uint16 _pendingObject[2];
	int _pendingVerb;
	uint16 _pointerObject;

	bool _skipSpeeches;
	bool _abortEnabled;

	VoiceLUT _globalVoiceLUT;

public:
	ScriptThread *createThread(uint16 scriptModuleNumber, uint16 scriptEntryPointNumber);
	int executeThread(ScriptThread *thread, int entrypointNumber);
	void executeThreads(uint msec);
	void completeThread(void);
	void abortAllThreads(void);

	void wakeUpActorThread(int waitType, void *threadObj);
	void wakeUpThreads(int waitType);
	void wakeUpThreadsDelayed(int waitType, int sleepTime);

	void loadVoiceLUT(VoiceLUT &voiceLUT, const byte *resourcePointer, size_t resourceLength);

private:
	void loadModuleBase(ModuleData &module, const byte *resourcePointer, size_t resourceLength);

	// runThread returns true if we should break running of other threads
	bool runThread(ScriptThread *thread, uint instructionLimit);
	void setThreadEntrypoint(ScriptThread *thread, int entrypointNumber);

public:
	void finishDialog(int replyID, int flags, int bitOffset);

private:

	typedef void (Script::*ScriptFunctionType)(SCRIPTFUNC_PARAMS);

	struct ScriptFunctionDescription {
		ScriptFunctionType scriptFunction;
		const char *scriptFunctionName;
	};
	const ScriptFunctionDescription *_scriptFunctionsList;

	void setupScriptFuncList(void);

	void sfPutString(SCRIPTFUNC_PARAMS);
	void sfWait(SCRIPTFUNC_PARAMS);
	void sfTakeObject(SCRIPTFUNC_PARAMS);
	void sfIsCarried(SCRIPTFUNC_PARAMS);
	void sfStatusBar(SCRIPTFUNC_PARAMS);
	void sfMainMode(SCRIPTFUNC_PARAMS);
	void sfScriptWalkTo(SCRIPTFUNC_PARAMS);
	void sfScriptDoAction(SCRIPTFUNC_PARAMS);
	void sfSetActorFacing(SCRIPTFUNC_PARAMS);
	void sfStartBgdAnim(SCRIPTFUNC_PARAMS);
	void sfStopBgdAnim(SCRIPTFUNC_PARAMS);
	void sfLockUser(SCRIPTFUNC_PARAMS);
	void sfPreDialog(SCRIPTFUNC_PARAMS);
	void sfKillActorThreads(SCRIPTFUNC_PARAMS);
	void sfFaceTowards(SCRIPTFUNC_PARAMS);
	void sfSetFollower(SCRIPTFUNC_PARAMS);
	void sfScriptGotoScene(SCRIPTFUNC_PARAMS);

	void sfSetObjImage(SCRIPTFUNC_PARAMS);
	void sfSetObjName(SCRIPTFUNC_PARAMS);
	void sfGetObjImage(SCRIPTFUNC_PARAMS);
	void sfGetNumber(SCRIPTFUNC_PARAMS);
	void sfScriptOpenDoor(SCRIPTFUNC_PARAMS);
	void sfScriptCloseDoor(SCRIPTFUNC_PARAMS);
	void sfSetBgdAnimSpeed(SCRIPTFUNC_PARAMS);
	void SF_cycleColors(SCRIPTFUNC_PARAMS);
	void sfDoCenterActor(SCRIPTFUNC_PARAMS);
	void sfStartBgdAnimSpeed(SCRIPTFUNC_PARAMS);
	void sfScriptWalkToAsync(SCRIPTFUNC_PARAMS);
	void sfEnableZone(SCRIPTFUNC_PARAMS);
	void sfSetActorState(SCRIPTFUNC_PARAMS);
	void sfScriptMoveTo(SCRIPTFUNC_PARAMS);
	void sfSceneEq(SCRIPTFUNC_PARAMS);
	void sfDropObject(SCRIPTFUNC_PARAMS);
	void sfFinishBgdAnim(SCRIPTFUNC_PARAMS);
	void sfSwapActors(SCRIPTFUNC_PARAMS);
	void sfSimulSpeech(SCRIPTFUNC_PARAMS);
	void sfScriptWalk(SCRIPTFUNC_PARAMS);
	void sfCycleFrames(SCRIPTFUNC_PARAMS);
	void sfSetFrame(SCRIPTFUNC_PARAMS);
	void sfSetPortrait(SCRIPTFUNC_PARAMS);
	void sfSetProtagPortrait(SCRIPTFUNC_PARAMS);
	void sfChainBgdAnim(SCRIPTFUNC_PARAMS);
	void sfScriptSpecialWalk(SCRIPTFUNC_PARAMS);
	void sfPlaceActor(SCRIPTFUNC_PARAMS);
	void sfCheckUserInterrupt(SCRIPTFUNC_PARAMS);
	void sfScriptWalkRelative(SCRIPTFUNC_PARAMS);
	void sfScriptMoveRelative(SCRIPTFUNC_PARAMS);
	void sfSimulSpeech2(SCRIPTFUNC_PARAMS);
	void sfPlacard(SCRIPTFUNC_PARAMS);
	void sfPlacardOff(SCRIPTFUNC_PARAMS);
	void sfSetProtagState(SCRIPTFUNC_PARAMS);
	void sfResumeBgdAnim(SCRIPTFUNC_PARAMS);
	void sfThrowActor(SCRIPTFUNC_PARAMS);
	void sfWaitWalk(SCRIPTFUNC_PARAMS);
	void sfScriptSceneID(SCRIPTFUNC_PARAMS);
	void sfChangeActorScene(SCRIPTFUNC_PARAMS);
	void sfScriptClimb(SCRIPTFUNC_PARAMS);
	void sfSetDoorState(SCRIPTFUNC_PARAMS);
	void sfSetActorZ(SCRIPTFUNC_PARAMS);
	void sfScriptText(SCRIPTFUNC_PARAMS);
	void sfGetActorX(SCRIPTFUNC_PARAMS);
	void sfGetActorY(SCRIPTFUNC_PARAMS);
	void sfEraseDelta(SCRIPTFUNC_PARAMS);
	void sfPlayMusic(SCRIPTFUNC_PARAMS);
	void sfPickClimbOutPos(SCRIPTFUNC_PARAMS);
	void sfTossRif(SCRIPTFUNC_PARAMS);
	void sfShowControls(SCRIPTFUNC_PARAMS);
	void sfShowMap(SCRIPTFUNC_PARAMS);
	void sfPuzzleWon(SCRIPTFUNC_PARAMS);
	void sfEnableEscape(SCRIPTFUNC_PARAMS);
	void sfPlaySound(SCRIPTFUNC_PARAMS);
	void sfPlayLoopedSound(SCRIPTFUNC_PARAMS);
	void sfGetDeltaFrame(SCRIPTFUNC_PARAMS);
	void sfShowProtect(SCRIPTFUNC_PARAMS);
	void sfProtectResult(SCRIPTFUNC_PARAMS);
	void sfRand(SCRIPTFUNC_PARAMS);
	void sfFadeMusic(SCRIPTFUNC_PARAMS);
	void sfScriptStartCutAway(SCRIPTFUNC_PARAMS);
	void sfReturnFromCutAway(SCRIPTFUNC_PARAMS);
	void sfEndCutAway(SCRIPTFUNC_PARAMS);
	void sfGetMouseClicks(SCRIPTFUNC_PARAMS);
	void sfResetMouseClicks(SCRIPTFUNC_PARAMS);
	void sfWaitFrames(SCRIPTFUNC_PARAMS);
	void sfScriptFade(SCRIPTFUNC_PARAMS);
	void sfPlayVoice(SCRIPTFUNC_PARAMS);
	void sfVstopFX(SCRIPTFUNC_PARAMS);
	void sfVstopLoopedFX(SCRIPTFUNC_PARAMS);
	void sfDemoIsInteractive(SCRIPTFUNC_PARAMS);
	void sfVsetTrack(SCRIPTFUNC_PARAMS);
	void sfDebugShowData(SCRIPTFUNC_PARAMS);
	void sfNull(SCRIPTFUNC_PARAMS);
	void sfWaitFramesEsc(SCRIPTFUNC_PARAMS);
	void sfPsychicProfile(SCRIPTFUNC_PARAMS);
	void sfPsychicProfileOff(SCRIPTFUNC_PARAMS);
	void sfSetSpeechBox(SCRIPTFUNC_PARAMS);
	void sfSetChapterPoints(SCRIPTFUNC_PARAMS);
	void sfSetPortraitBgColor(SCRIPTFUNC_PARAMS);
	void sfScriptStartVideo(SCRIPTFUNC_PARAMS);
	void sfScriptReturnFromVideo(SCRIPTFUNC_PARAMS);
	void sfScriptEndVideo(SCRIPTFUNC_PARAMS);
	void sf87(SCRIPTFUNC_PARAMS);
	void sf88(SCRIPTFUNC_PARAMS);
	void sf89(SCRIPTFUNC_PARAMS);
	void sfGetPoints(SCRIPTFUNC_PARAMS);
	void sfSetGlobalFlag(SCRIPTFUNC_PARAMS);
	void sfClearGlobalFlag(SCRIPTFUNC_PARAMS);
	void sfTestGlobalFlag(SCRIPTFUNC_PARAMS);
	void sfSetPoints(SCRIPTFUNC_PARAMS);
	void sf103(SCRIPTFUNC_PARAMS);
	void sfDisableAbortSpeeches(SCRIPTFUNC_PARAMS);

	void SF_stub(const char *name, ScriptThread *thread, int nArgs);
};

} // End of namespace Saga

#endif
