/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 *
 * Based on the original sources
 *   Faery Tale II -- The Halls of the Dead
 *   (c) 1993-1996 The Wyrmkeep Entertainment Co.
 */

#ifndef SAGA2_SCRIPT_H
#define SAGA2_SCRIPT_H

#include "saga2/objects.h"
#include "saga2/calendar.h"

namespace Saga2 {

typedef int16   ThreadID;

//  Various result codes returned from runScript

enum scriptResult {

	//  Code returned when attempt to run a non-existent script
	kScriptResultNoScript = 0,

	//  Code returned when script was aborted before completion
	kScriptResultAborted,

	//  Code returned when script finished
	kScriptResultFinished,

	//  Script spun off as async thread; no answer available.
	kScriptResultAsync
};

//  Variables specific to a thread

struct scriptCallFrame {

	//  ID of object who's method is being called (which can be the same
	//  as one of the other objects below).
	ObjectID        invokedObject = Nothing;
	ActiveItemID    invokedTAI;

	//  ID of the objects in the interaction.
	ObjectID        enactor = Nothing,        // actor who caused interaction
	                directObject = Nothing,   // the object being acted on
	                indirectObject = Nothing; // the object being used
	// with the other one
	ActiveItemID    directTAI,              // the tile activity instance
	                // being used
	                indirectTAI;            // the tile activity instance
	// upon which the object is being
	// used

	int16           responseType = -1;      // used with knowledge package

	int16           methodNum = -1;         // which method being invoked

	//  Misc fields used in passing parameters to scripts.
	int16           idNum = 0;              // a misc. id number
	int16           value = 0;              // a misc. parameter value
	TilePoint       coords;                 // a misc. tilepoint

	int16           returnVal = 0;          // return value of script
};

//  Standard return codes from scripts in the "returnVal" field

enum {
	//  Code returned by script when script decides requested
	//  action is not possible, and the calling C-code should
	//  take action to inform user
	kActionResultFailure = 0,

	//  Code returned by script when script completes the action
	//  successfully and C-code should not complete the action
	kActionResultSuccess,

	//  Code returned by script when requested action should complete
	//  the action
	kActionResultNotDone
};

//  Method used to refer to a SAGA object

struct SegmentRef {
	uint16              segment;            // segment / resource number
	uint16              offset;             // offset within segment
};

//  Segment numbers of "builtin" SAGA data structures,
//  such as actors and TAGS

enum builtinTypes {
	kBuiltinTypeObject = -1,
	kBuiltinTypeTAG = -2,
	kBuiltinAbstract = -3,
	kBuiltinTypeMission = -4
};

/* ===================================================================== *
   SAGA management functions
 * ===================================================================== */

//  Load the SAGA data segment from the resource file
void initSAGADataSeg();

void saveSAGADataSeg(Common::OutSaveFile *outS);
void loadSAGADataSeg(Common::InSaveFile *in);

//  Dispose of the SAGA data segment -- do nothing
inline void cleanupSAGADataSeg() {}

/* ===================================================================== *
   Thread management functions
 * ===================================================================== */

class Thread;

//  Initialize the SAGA thread list
void initSAGAThreads();

void saveSAGAThreads(Common::OutSaveFile *outS);
void loadSAGAThreads(Common::InSaveFile *in, int32 chunkSize);

//  Dispose of the active SAGA threads
void cleanupSAGAThreads();

//  Dispose of an active SAGA thread
void deleteThread(Thread *p);

void newThread(Thread *p, ThreadID id);

void newThread(Thread *p);

//  Return the ID of the specified SAGA thread
ThreadID getThreadID(Thread *thread);

//  Return a pointer to a SAGA thread, given a thread ID
Thread *getThreadAddress(ThreadID id);

/* ===================================================================== *
   Class Thread: An execution context of a script
 * ===================================================================== */

//  A script task is called a thread

scriptResult runMethod(
	uint16          scriptClassID,      // which script class
	int16           bType,              // builtin type
	uint16          index,              // object index
	uint16          methodNum,          // method number to call
	scriptCallFrame &args);

class Thread {

	friend char *STRING(int strNum);

	friend scriptResult runScript(uint16 exportEntryNum, scriptCallFrame &args);

	friend void wakeUpThread(ThreadID, int16);

public:
	SegmentRef      _programCounter;         // current PC location

	uint8           *_stackPtr;              // current stack location
	byte            *_codeSeg;                // base of current data segment
//					*_stringBase;         // base of string resource

	uint8           *_stackBase;             // base of module stack

	enum threadFlags {
		kTFWaiting     = (1 << 0),             // thread waiting for event
		kTFFinished    = (1 << 1),             // thread finished normally
		kTFAborted     = (1 << 2),             // thread is aborted
		kTFExtended    = (1 << 3),             // this is an extended sequence
		kTFExpectResult = (1 << 4),            // script is expecting result on stack
		kTFSynchronous = (1 << 5),             // when this bit is set this thread will
		// run until it is finished or this bit
		// is cleared

		kTFAsleep      = (kTFWaiting | kTFFinished | kTFAborted)
	};

	int16           _stackSize,              // allocated size of stack
	                _flags,                  // execution flags
	                _framePtr,               // pointer to call frame
	                _returnVal;              // return value from ccalls

	bool            _valid;

	//  Various signals that a script can wait upon
	enum WaitTypes {
		kWaitNone = 0,                       // waiting for nothing
		kWaitDelay,                          // waiting for a timer
		kWaitFrameDelay,                     // waiting for frame count
		kWaitOther,                          // waiting for to be awoken
		kWaitTagSemaphore                    // waiting for a tag semaphore

//		kWaitSpeech,                         // waiting for speech to finish
//		kWaitDialogEnd,                      // waiting for my dialog to finish
//		kWaitDialogBegin,                    // waiting for other dialog to finish
//		kWaitWalk,                           // waiting to finish walking
//		kWaitRequest,                        // a request is up
	};

	WaitTypes  _waitType;               // what we're waiting for
	union {
		Alarm       _waitAlarm;              // for time-delay
		FrameAlarm  _waitFrameAlarm;         // for frame count delay
		ActiveItem *_waitParam;              // for other waiting
	};

	scriptCallFrame _threadArgs;             // arguments from C to thread

	//  For 'cfunc' member functions, the address of the object who's
	//  member function is being invoked.
	void            *_thisObject;
	uint16          _argCount;               // number of args to cfunc

	//  Constructor
	Thread(uint16 segNum, uint16 segOff, scriptCallFrame &args);

	//  Constructor -- reconstruct from archive buffer
	Thread(void **buf);

	Thread(Common::SeekableReadStream *stream, ThreadID id);

	//  Destructor
	~Thread();

	//  Return the number of bytes need to archive this thread in an
	//  arhive buffer
	int32 archiveSize();

	//  Create an archive of this thread in an archive buffer
	void *archive(void *buf);

	void write(Common::MemoryWriteStreamDynamic *out);

	//  Dispatch all asynchronous threads
	static void dispatch();

	//  Intepret a single thread
	scriptResult run();

	//  Tells thread to wait for an event
	void waitForEvent(WaitTypes wt, ActiveItem *param) {
		_flags |= kTFWaiting;
		_waitType = wt;
		_waitParam = param;
	}

	//  Convert to extended script, and back to synchonous script
	void setExtended();
	void clearExtended();

	bool interpret();
private:
	uint8 *strAddress(int strNum);

};

const int               maxTimeSlice = 16,  // max instructions per call
                        kStackSize = 512;    // thread stack size

/* ============================================================================ *
   C-Function dispatch table
 * ============================================================================ */

typedef int16       C_Call(int16 *);

struct CallTable {
	C_Call          **table;
	uint16          numEntries;
	uint16          classID;
};

extern CallTable    globalCFuncs,
       actorCFuncs,
       tagCFuncs,
       missionCFuncs;

//extern C_Call     *ccall_table[];
//extern int16      ccall_count;

/* ===================================================================== *
   Externals
 * ===================================================================== */

extern Thread           *thisThread;        // task queue

//  Thread control
//void killThread( Thread *th );

/*
void wakeUpActorThread(WaitTypes wakeupType, void *obj);
void wakeUpThreads(WaitTypes wakeupType);
void wakeUpThreadsDelayed(WaitTypes wakeupType, int newdelay);
void abortObjectThreads(Thread *keep, uint16 id);
bool abortAllThreads(void);
*/

//  Run a script function
scriptResult runScript(uint16 exportEntryNum, scriptCallFrame &args);

//  Run a script class method.
scriptResult runObjectMethod(
    ObjectID id, uint16 methodNum, scriptCallFrame &args);

scriptResult runTagMethod(
    uint16 tagNum, uint16 methodNum, scriptCallFrame &args);


struct ResImportTable {
	int16           deadActorProto,
	                reserved[2];
	int16           EXP_spellEffect_CreateFireWisp,
	                EXP_spellEffect_CreateWindWisp,
	                EXP_spellEffect_CreateWraith,
	                EXP_spellEffect_TeleportToShrine,
	                EXP_spellEffect_Rejoin,
	                EXP_spellEffect_Timequake,
	                EXP_spellEffect_CreateFood;
};

extern ResImportTable       *resImports;

} // end of namespace Saga2

#endif
