/* 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 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.
 *
 */

#ifndef AGI_AGI_H
#define AGI_AGI_H

#include "common/scummsys.h"
#include "common/error.h"
#include "common/util.h"
#include "common/file.h"
#include "common/keyboard.h"
#include "common/rect.h"
#include "common/rendermode.h"
#include "common/stack.h"
#include "common/str.h"
#include "common/system.h"

#include "engines/engine.h"

#include "gui/debugger.h"

// AGI resources
#include "agi/console.h"
#include "agi/view.h"
#include "agi/picture.h"
#include "agi/logic.h"
#include "agi/sound.h"

namespace Common {
class RandomSource;
}

/**
 * This is the namespace of the AGI engine.
 *
 * Status of this engine: ???
 *
 * Games using this engine:
 * - Early Sierra adventure games
 * - many fan made games
 * - Mickey's Space Adventure (Pre-AGI)
 * - Winnie the Pooh in the Hundred Acre Wood (Pre-AGI)
 * - Troll's Tale (Pre-AGI)
 */
namespace Agi {

typedef signed int Err;

//
// Version and other definitions
//

#define TITLE       "AGI engine"

#define DIR_        "dir"
#define LOGDIR      "logdir"
#define PICDIR      "picdir"
#define VIEWDIR     "viewdir"
#define SNDDIR      "snddir"
#define OBJECTS     "object"
#define WORDS       "words.tok"

#define MAX_DIRECTORY_ENTRIES      256
#define MAX_CONTROLLERS            256
#define MAX_VARS                   256
#define MAX_FLAGS                  (256 >> 3)
#define SCREENOBJECTS_MAX          255        // KQ3 uses o255!
#define SCREENOBJECTS_EGO_ENTRY    0          // first entry is ego
#define MAX_WORDS                  20
#define MAX_STRINGS                24         // MAX_STRINGS + 1 used for get.num
#define MAX_STRINGLEN              40
#define MAX_CONTROLLER_KEYMAPPINGS 39

#define SAVEDGAME_DESCRIPTION_LEN 30

#define _EMPTY       0xfffff
#define EGO_OWNED    0xff
#define EGO_OWNED_V1 0xf9

#define CRYPT_KEY_SIERRA    "Avis Durgan"
#define CRYPT_KEY_AGDS      "Alex Simkin"

#define ADD_PIC  1
#define ADD_VIEW 2

#define CMD_BSIZE 12

enum AgiGameID {
	GID_AGIDEMO,
	GID_BC,
	GID_DDP,
	GID_GOLDRUSH,
	GID_KQ1,
	GID_KQ2,
	GID_KQ3,
	GID_KQ4,
	GID_LSL1,
	GID_MH1,
	GID_MH2,
	GID_MIXEDUP,
	GID_PQ1,
	GID_SQ1,
	GID_SQ2,
	GID_XMASCARD,
	GID_FANMADE,
	GID_GETOUTTASQ, // Fanmade
	GID_MICKEY,     // PreAGI
	GID_WINNIE,     // PreAGI
	GID_TROLL       // PreAGI
};

enum AgiGameType {
	GType_PreAGI = 0,
	GType_V1 = 1,
	GType_V2 = 2,
	GType_V3 = 3
};

enum BooterDisks {
	BooterDisk1 = 0,
	BooterDisk2 = 1
};

//
// GF_OLDAMIGAV20 means that the interpreter is an old Amiga AGI interpreter that
// uses value 20 for the computer type (v20 i.e. vComputer) rather than the usual value 5.
//
// GF_CLIPCOORDS means that views' coordinates must be clipped at least in commands
// position and position.v.
//
enum AgiGameFeatures {
	GF_AGIMOUSE    = (1 << 0), // this disables "Click-to-walk mouse interface"
	GF_AGDS        = (1 << 1),
	GF_AGI256      = (1 << 2), // marks fanmade AGI-256 games
	GF_AGI256_2    = (1 << 3), // marks fanmade AGI-256-2 games
	GF_AGIPAL      = (1 << 4), // marks game using fanmade AGIPAL extension
	GF_MACGOLDRUSH = (1 << 5), // use "grdir" instead of "dir" for volume loading
	GF_FANMADE     = (1 << 6), // marks fanmade games
	GF_MENUS       = (1 << 7), // not used anymore
	GF_ESCPAUSE    = (1 << 8), // not used anymore, we detect this internally
	GF_OLDAMIGAV20 = (1 << 9),
	GF_CLIPCOORDS  = (1 << 10), // not used atm
	GF_2GSOLDSOUND = (1 << 11)
};

struct AGIGameDescription;

enum {
	NO_GAMEDIR = 0,
	GAMEDIR
};

enum AGIErrors {
	errOK = 0,
	errDoNothing,
	errBadCLISwitch,
	errInvalidAGIFile,
	errBadFileOpen,
	errNotEnoughMemory,
	errBadResource,
	errUnknownAGIVersion,
	errNoLoopsInView,
	errViewDataError,
	errNoGameList,
	errIOError,

	errUnk = 127
};

enum kDebugLevels {
	kDebugLevelMain =      1 << 0,
	kDebugLevelResources = 1 << 1,
	kDebugLevelSprites =   1 << 2,
	kDebugLevelInventory = 1 << 3,
	kDebugLevelInput =     1 << 4,
	kDebugLevelMenu =      1 << 5,
	kDebugLevelScripts =   1 << 6,
	kDebugLevelSound =     1 << 7,
	kDebugLevelText =      1 << 8,
	kDebugLevelSavegame =  1 << 9
};

/**
 * AGI resources.
 */
enum {
	RESOURCETYPE_LOGIC = 1,
	RESOURCETYPE_SOUND,
	RESOURCETYPE_VIEW,
	RESOURCETYPE_PICTURE
};

enum {
	RES_LOADED                 = 0x01,
	RES_COMPRESSED             = 0x40,
	RES_PICTURE_V3_NIBBLE_PARM = 0x80  // Flag that gets set for picture resources,
	                                   // which use a nibble instead of a byte as F0+F2 parameters
};

enum {
	lCOMMAND_MODE = 1,
	lTEST_MODE
};

struct gameIdList {
	gameIdList *next;
	uint32 version;
	uint32 crc;
	char *gName;
	char *switches;
};

struct Mouse {
	int button;
	Common::Point pos;

	Mouse() : button(0) {}
};

// Used by AGI Mouse protocol 1.0 for v27 (i.e. button pressed -variable).
enum AgiMouseButton {
	kAgiMouseButtonUp,    // Mouse button is up (not pressed)
	kAgiMouseButtonLeft,  // Left mouse button
	kAgiMouseButtonRight, // Right mouse button
	kAgiMouseButtonMiddle // Middle mouse button
};

/**
 * AGI variables.
 */
enum {
	VM_VAR_CURRENT_ROOM = 0,        // 0
	VM_VAR_PREVIOUS_ROOM,           // 1
	VM_VAR_BORDER_TOUCH_EGO,        // 2
	VM_VAR_SCORE,                   // 3
	VM_VAR_BORDER_CODE,             // 4
	VM_VAR_BORDER_TOUCH_OBJECT,     // 5
	VM_VAR_EGO_DIRECTION,           // 6
	VM_VAR_MAX_SCORE,               // 7
	VM_VAR_FREE_PAGES,              // 8
	VM_VAR_WORD_NOT_FOUND,          // 9
	VM_VAR_TIME_DELAY,              // 10
	VM_VAR_SECONDS,                 // 11
	VM_VAR_MINUTES,                 // 12
	VM_VAR_HOURS,                   // 13
	VM_VAR_DAYS,                    // 14
	VM_VAR_JOYSTICK_SENSITIVITY,    // 15
	VM_VAR_EGO_VIEW_RESOURCE,       // 16
	VM_VAR_AGI_ERROR_CODE,          // 17
	VM_VAR_AGI_ERROR_INFO,          // 18
	VM_VAR_KEY,                     // 19
	VM_VAR_COMPUTER,                // 20
	VM_VAR_WINDOW_AUTO_CLOSE_TIMER, // 21
	VM_VAR_SOUNDGENERATOR,          // 22
	VM_VAR_VOLUME,                  // 23
	VM_VAR_MAX_INPUT_CHARACTERS,    // 24
	VM_VAR_SELECTED_INVENTORY_ITEM, // 25
	VM_VAR_MONITOR = 26,            // 26
	VM_VAR_MOUSE_BUTTONSTATE = 27,  // 27
	VM_VAR_MOUSE_X = 28,            // 28
	VM_VAR_MOUSE_Y = 29             // 29
};

/**
 * Different monitor types.
 * Used with AGI variable 26 i.e. vMonitor.
 */
enum AgiMonitorType {
	kAgiMonitorCga = 0,
	//kAgiMonitorTandy = 1, // Not sure about this
	kAgiMonitorHercules = 2,
	kAgiMonitorEga = 3
	//kAgiMonitorVga = 4 // Not sure about this
};

/**
 * Different computer types.
 * Used with AGI variable 20 i.e. vComputer.
 *
 * At least these Amiga AGI versions use value 5:
 * 2.082 (King's Quest I v1.0U 1986)
 * 2.090 (King's Quest III v1.01 1986-11-08)
 * x.yyy (Black Cauldron v2.00 1987-06-14)
 * x.yyy (Larry I v1.05 1987-06-26)
 * 2.107 (King's Quest II v2.0J. Date is probably 1987-01-29)
 * 2.202 (Space Quest II v2.0F)
 * 2.310 (Police Quest I v2.0B 1989-02-22)
 * 2.316 (Gold Rush! v2.05 1989-03-09)
 * 2.333 (King's Quest III v2.15 1989-11-15)
 *
 * At least these Amiga AGI versions use value 20:
 * 2.082 (Space Quest I v1.2 1986)
 * x.yyy (Manhunter NY 1.06 3/18/89)
 * 2.333 (Manhunter SF 3.06 8/17/89)
 *
 */
enum AgiComputerType {
	kAgiComputerPC = 0,
	kAgiComputerAtariST = 4,
	kAgiComputerAmiga = 5, // Newer Amiga AGI interpreters' value (Commonly used)
	kAgiComputerApple2GS = 7,
	kAgiComputerAmigaOld = 20 // Older Amiga AGI interpreters' value (Seldom used)
};

enum AgiSoundType {
	kAgiSoundPC = 1,
	kAgiSoundTandy = 3, // Tandy (This value is also used by the Amiga AGI and Apple IIGS AGI)
	kAgiSound2GSOld = 8 // Apple IIGS's Gold Rush! (Version 1.0M 1989-02-28 (CE), AGI 3.003) uses value 8
};

/**
 * AGI flags
 */
enum {
	VM_FLAG_EGO_WATER = 0,  // 0
	VM_FLAG_EGO_INVISIBLE,
	VM_FLAG_ENTERED_CLI,
	VM_FLAG_EGO_TOUCHED_P2,
	VM_FLAG_SAID_ACCEPTED_INPUT,
	VM_FLAG_NEW_ROOM_EXEC,  // 5
	VM_FLAG_RESTART_GAME,
	VM_FLAG_SCRIPT_BLOCKED,
	VM_FLAG_JOY_SENSITIVITY,
	VM_FLAG_SOUND_ON,
	VM_FLAG_DEBUGGER_ON,        // 10
	VM_FLAG_LOGIC_ZERO_FIRST_TIME,
	VM_FLAG_RESTORE_JUST_RAN,
	VM_FLAG_STATUS_SELECTS_ITEMS,
	VM_FLAG_MENUS_ACCESSIBLE,
	VM_FLAG_OUTPUT_MODE,        // 15
	VM_FLAG_AUTO_RESTART
};

struct AgiControllerKeyMapping {
	uint16 keycode;
	byte   controllerSlot;

	AgiControllerKeyMapping() : keycode(0), controllerSlot(0) {}
};

struct AgiObject {
	int location;
	Common::String name;
};

struct AgiDir {
	uint8 volume;
	uint32 offset;
	uint32 len;
	uint32 clen;

	// 0 = not in mem, can be freed
	// 1 = in mem, can be released
	// 2 = not in mem, cant be released
	// 3 = in mem, cant be released
	// 0x40 = was compressed
	uint8 flags;

	void reset() {
		volume = 0;
		offset = 0;
		len = 0;
		clen = 0;
		flags = 0;
	}

	AgiDir() { reset(); }
};

struct AgiBlock {
	bool active;
	int16 x1, y1;
	int16 x2, y2;

	AgiBlock() : active(false), x1(0), y1(0), x2(0), y2(0) {}
};

struct ScriptPos {
	int script;
	int curIP;
};

enum CycleInnerLoopType {
	CYCLE_INNERLOOP_GETSTRING = 0,
	CYCLE_INNERLOOP_GETNUMBER,
	CYCLE_INNERLOOP_INVENTORY,
	CYCLE_INNERLOOP_MENU_VIA_KEYBOARD,
	CYCLE_INNERLOOP_MENU_VIA_MOUSE,
	CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT,
	CYCLE_INNERLOOP_SYSTEMUI_VERIFICATION,
	CYCLE_INNERLOOP_MESSAGEBOX,
	CYCLE_INNERLOOP_HAVEKEY
};

typedef Common::Array<int16> SavedGameSlotIdArray;

/**
 * AGI game structure.
 * This structure contains all global data of an AGI game executed
 * by the interpreter.
 */
struct AgiGame {
	AgiEngine *_vm;

	// TODO: Check whether adjMouseX and adjMouseY must be saved and loaded when using savegames.
	//       If they must be then loading and saving is partially broken at the moment.
	int adjMouseX;  /**< last given adj.ego.move.to.x.y-command's 1st parameter */
	int adjMouseY;  /**< last given adj.ego.move.to.x.y-command's 2nd parameter */

	char name[8];   /**< lead in id (e.g. `GR' for goldrush) */
	char id[8];     /**< game id */
	uint32 crc;     /**< game CRC */

	// game flags and variables
	uint8 flags[MAX_FLAGS]; /**< 256 1-bit flags combined into a total of 32 bytes */
	uint8 vars[MAX_VARS];   /**< 256 variables */

	// internal variables
	int16 horizon;          /**< horizon y coordinate */

	bool  cycleInnerLoopActive;
	int16 cycleInnerLoopType;

	int16 curLogicNr;               /**< current logic number */
	Common::Array<ScriptPos> execStack;

	// internal flags
	bool playerControl; /**< player is in control */
	bool exitAllLogics; /**< break cycle after new.room */
	bool pictureShown;  /**< show.pic has been issued */
#define ID_AGDS     0x00000001
#define ID_AMIGA    0x00000002
	int gameFlags;      /**< agi options flags */

	// windows
	AgiBlock block;

	// graphics & text
	bool gfxMode;

	unsigned int numObjects;

	bool controllerOccured[MAX_CONTROLLERS];  /**< keyboard keypress events */
	AgiControllerKeyMapping controllerKeyMapping[MAX_CONTROLLER_KEYMAPPINGS];

	char strings[MAX_STRINGS + 1][MAX_STRINGLEN]; /**< strings */

	// directory entries for resources
	AgiDir dirLogic[MAX_DIRECTORY_ENTRIES];
	AgiDir dirPic[MAX_DIRECTORY_ENTRIES];
	AgiDir dirView[MAX_DIRECTORY_ENTRIES];
	AgiDir dirSound[MAX_DIRECTORY_ENTRIES];

	// resources
	AgiPicture pictures[MAX_DIRECTORY_ENTRIES]; /**< AGI picture resources */
	AgiLogic logics[MAX_DIRECTORY_ENTRIES];     /**< AGI logic resources */
	AgiView views[MAX_DIRECTORY_ENTRIES];       /**< AGI view resources */
	AgiSound *sounds[MAX_DIRECTORY_ENTRIES];    /**< Pointers to AGI sound resources */

	AgiLogic *_curLogic;

	// view table
	ScreenObjEntry screenObjTable[SCREENOBJECTS_MAX];

	ScreenObjEntry addToPicView;

	bool automaticSave;             /**< set by CmdSetSimple() */
	char automaticSaveDescription[SAVEDGAME_DESCRIPTION_LEN + 1];

	Common::Rect mouseFence;        /**< rectangle set by fence.mouse command */
	bool mouseEnabled;              /**< if mouse is supposed to be active */
	bool mouseHidden;               /**< if mouse is currently hidden */

	// IF condition handling
	int testResult;

	int max_logics;
	int logic_list[256];

	// used to detect situations, where the game shows some text and changes rooms right afterwards
	// for example Space Quest 2 intro right at the start
	// or Space Quest 2, when entering the vent also right at the start
	// The developers assumed that loading the new room would take a bit.
	// In ScummVM it's basically done in an instant, which means that
	// the text would only get shown for a split second.
	// We delay a bit as soon as such situations get detected.
	bool nonBlockingTextShown;
	int16 nonBlockingTextCyclesLeft;

	bool automaticRestoreGame;

	AgiGame() {
		_vm = nullptr;

		adjMouseX = 0;
		adjMouseY = 0;

		for (uint16 i = 0; i < ARRAYSIZE(name); i++) {
			name[i] = 0;
		}
		for (uint16 i = 0; i < ARRAYSIZE(id); i++) {
			id[i] = 0;
		}
		crc = 0;

		for (uint16 i = 0; i < ARRAYSIZE(flags); i++) {
			flags[i] = 0;
		}
		for (uint16 i = 0; i < ARRAYSIZE(vars); i++) {
			vars[i] = 0;
		}

		horizon = 0;

		cycleInnerLoopActive = false;
		cycleInnerLoopType = 0;

		curLogicNr = 0;

		// execStack is defaulted by Common::Array constructor

		playerControl = false;
		exitAllLogics = false;
		pictureShown = false;
		gameFlags = 0;

		// block defaulted by AgiBlock constructor

		gfxMode = false;

		numObjects = 0;

		for (uint16 i = 0; i < ARRAYSIZE(controllerOccured); i++) {
			controllerOccured[i] = false;
		}

		// controllerKeyMapping defaulted by AgiControllerKeyMapping constructor

		for (uint16 i = 0; i < MAX_STRINGS + 1; i++) {
			for (uint16 j = 0; j < MAX_STRINGLEN; j++) {
				strings[i][j] = 0;
			}
		}

		// dirLogic cleared by AgiDir constructor
		// dirPic cleared by AgiDir constructor
		// dirView cleared by AgiDir constructor
		// dirSound cleared by AgiDir constructor

		// pictures cleared by AgiPicture constructor
		// logics cleared by AgiLogic constructor
		// views cleared by AgiView constructor
		for (uint16 i = 0; i < ARRAYSIZE(sounds); i++) {
			sounds[i] = nullptr;
		}

		_curLogic = nullptr;

		// screenObjTable cleared by ScreenObjEntry constructor

		// addToPicView cleared by ScreenObjEntry constructor

		automaticSave = false;
		for (uint16 i = 0; i < ARRAYSIZE(automaticSaveDescription); i++) {
			automaticSaveDescription[i] = 0;
		}

		// mouseFence cleared by Common::Rect constructor
		mouseEnabled = false;
		mouseHidden = false;

		testResult = 0;

		max_logics = 0;
		for (uint16 i = 0; i < ARRAYSIZE(logic_list); i++) {
			logic_list[i] = 0;
		}

		nonBlockingTextShown = false;
		nonBlockingTextCyclesLeft = 0;

		automaticRestoreGame = false;
	}
};

class AgiLoader {
public:

	AgiLoader() {}
	virtual ~AgiLoader() {}

	virtual int init() = 0;
	virtual int deinit() = 0;
	virtual int detectGame() = 0;
	virtual int loadResource(int16 resourceType, int16 resourceNr) = 0;
	virtual int unloadResource(int16 resourceType, int16 resourceNr) = 0;
	virtual int loadObjects(const char *) = 0;
	virtual int loadWords(const char *) = 0;
};

class AgiLoader_v1 : public AgiLoader {
private:
	AgiEngine *_vm;
	Common::String _filenameDisk0;
	Common::String _filenameDisk1;

	int loadDir_DDP(AgiDir *agid, int offset, int max);
	int loadDir_BC(AgiDir *agid, int offset, int max);
	uint8 *loadVolRes(AgiDir *agid);

public:
	AgiLoader_v1(AgiEngine *vm);

	virtual int init();
	virtual int deinit();
	virtual int detectGame();
	virtual int loadResource(int16 resourceType, int16 resourceNr);
	virtual int unloadResource(int16 resourceType, int16 resourceNr);
	virtual int loadObjects(const char *);
	virtual int loadWords(const char *);
};

class AgiLoader_v2 : public AgiLoader {
private:
	AgiEngine *_vm;

	int loadDir(AgiDir *agid, const char *fname);
	uint8 *loadVolRes(AgiDir *agid);

public:

	AgiLoader_v2(AgiEngine *vm) {
		_vm = vm;
	}

	virtual int init();
	virtual int deinit();
	virtual int detectGame();
	virtual int loadResource(int16 resourceType, int16 resourceNr);
	virtual int unloadResource(int16 resourceType, int16 resourceNr);
	virtual int loadObjects(const char *);
	virtual int loadWords(const char *);
};

class AgiLoader_v3 : public AgiLoader {
private:
	AgiEngine *_vm;

	int loadDir(AgiDir *agid, Common::File *fp, uint32 offs, uint32 len);
	uint8 *loadVolRes(AgiDir *agid);

public:

	AgiLoader_v3(AgiEngine *vm) {
		_vm = vm;
	}

	virtual int init();
	virtual int deinit();
	virtual int detectGame();
	virtual int loadResource(int16 resourceType, int16 resourceNr);
	virtual int unloadResource(int16 resourceType, int16 resourceNr);
	virtual int loadObjects(const char *);
	virtual int loadWords(const char *);
};

class GfxFont;
class GfxMgr;
class SpritesMgr;
class InventoryMgr;
class TextMgr;
class GfxMenu;
class SystemUI;
class Words;

// Image stack support
struct ImageStackElement {
	uint8 type;
	uint8 pad;
	int16 parm1;
	int16 parm2;
	int16 parm3;
	int16 parm4;
	int16 parm5;
	int16 parm6;
	int16 parm7;
};

struct StringData {
	int x;
	int y;
	int len;
	int str;
};

#define TICK_SECONDS 20

#define KEY_QUEUE_SIZE 16

class AgiBase : public ::Engine {
protected:
	// Engine API
	Common::Error init();
	virtual Common::Error go() = 0;
	virtual Common::Error run() {
		Common::Error err;
		err = init();
		if (err.getCode() != Common::kNoError)
			return err;
		return go();
	}
	virtual bool hasFeature(EngineFeature f) const;

	virtual void initialize() = 0;

	void initRenderMode();

public:
	Words *_words;

	GfxFont *_font;
	GfxMgr  *_gfx;

	Common::RenderMode _renderMode;
	AgiDebug _debug;
	AgiGame _game;
	Common::RandomSource *_rnd;

	SoundMgr *_sound;

	Mouse _mouse;

	bool _noSaveLoadAllowed;

	virtual bool promptIsEnabled() {
		return false;
	}

	virtual int getKeypress() = 0;
	virtual bool isKeypress() = 0;
	virtual void clearKeyQueue() = 0;

	AgiBase(OSystem *syst, const AGIGameDescription *gameDesc);
	~AgiBase();

	virtual void clearImageStack() = 0;
	virtual void recordImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
	                                  int16 p4, int16 p5, int16 p6, int16 p7) = 0;
	virtual void replayImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
	                                  int16 p4, int16 p5, int16 p6, int16 p7) = 0;
	virtual void releaseImageStack() = 0;

	int _soundemu;

	bool getFlag(int16 flagNr);
	void setFlag(int16 flagNr, bool newState);
	void flipFlag(int16 flagNr);

	const AGIGameDescription *_gameDescription;

	uint32 _gameFeatures;
	uint16 _gameVersion;

	uint32 getGameID() const;
	uint32 getFeatures() const;
	uint16 getVersion() const;
	uint16 getGameType() const;
	Common::Language getLanguage() const;
	Common::Platform getPlatform() const;
	const char *getGameMD5() const;
	void initFeatures();
	void setFeature(uint32 feature);
	void initVersion();
	void setVersion(uint16 version);

	const char *getDiskName(uint16 id);

	bool canLoadGameStateCurrently();
	bool canSaveGameStateCurrently();

	const byte *getFontData();

	void cycleInnerLoopActive(int16 loopType) {
		_game.cycleInnerLoopActive = true;
		_game.cycleInnerLoopType = loopType;
	};
	void cycleInnerLoopInactive() {
		_game.cycleInnerLoopActive = false;
	};
	bool cycleInnerLoopIsActive() {
		return _game.cycleInnerLoopActive;
	}
};

enum AgiArtificialDelayTriggerType {
	ARTIFICIALDELAYTYPE_NEWROOM = 0,
	ARTIFICIALDELAYTYPE_NEWPICTURE = 1,
	ARTIFICIALDELAYTYPE_END = -1
};

struct AgiArtificialDelayEntry {
	uint32 gameId;
	Common::Platform platform;
	AgiArtificialDelayTriggerType triggerType;
	int16 orgNr;
	int16 newNr;
	uint16 millisecondsDelay;
};

typedef void (*AgiOpCodeFunction)(AgiGame *state, AgiEngine *vm, uint8 *p);

struct AgiOpCodeEntry {
	const char *name;
	const char *parameters;
	AgiOpCodeFunction functionPtr;
	uint16     parameterSize;
};

struct AgiOpCodeDefinitionEntry {
	const char *name;
	const char *parameters;
	AgiOpCodeFunction functionPtr;
};

class AgiEngine : public AgiBase {
protected:
	// Engine APIs
	virtual Common::Error go();

	void initialize();

	uint32 _lastSaveTime;

public:
	AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc);
	virtual ~AgiEngine();

	bool promptIsEnabled();

	Common::Error loadGameState(int slot);
	Common::Error saveGameState(int slot, const Common::String &description);

private:
	int _keyQueue[KEY_QUEUE_SIZE];
	int _keyQueueStart;
	int _keyQueueEnd;

	bool _allowSynthetic;

	bool checkPriority(ScreenObjEntry *v);
	bool checkCollision(ScreenObjEntry *v);
	bool checkPosition(ScreenObjEntry *v);

	int _firstSlot;

public:
	Common::Array<AgiObject> _objects;    // objects in the game

	StringData _stringdata;

	SavedGameSlotIdArray getSavegameSlotIds();
	Common::String getSavegameFilename(int16 slotId) const;
	bool getSavegameInformation(int16 slotId, Common::String &saveDescription, uint32 &saveDate, uint32 &saveTime, bool &saveIsValid);

	int saveGame(const Common::String &fileName, const Common::String &descriptionString);
	int loadGame(const Common::String &fileName, bool checkId = true);
	bool saveGameDialog();
	bool saveGameAutomatic();
	bool loadGameDialog();
	bool loadGameAutomatic();
	int doSave(int slot, const Common::String &desc);
	int doLoad(int slot, bool showMessages);
	int scummVMSaveLoadDialog(bool isSave);

	uint8 *_intobj;
	bool _restartGame;

	SpritesMgr *_sprites;
	TextMgr *_text;
	InventoryMgr *_inventory;
	PictureMgr *_picture;
	AgiLoader *_loader; // loader
	GfxMenu *_menu;
	SystemUI *_systemUI;

	Common::Stack<ImageStackElement> _imageStack;

	void clearImageStack();
	void recordImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
	                          int16 p4, int16 p5, int16 p6, int16 p7);
	void replayImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
	                          int16 p4, int16 p5, int16 p6, int16 p7);
	void releaseImageStack();

	void wait(uint32 msec, bool busy = false);

	Console *_console;
	GUI::Debugger *getDebugger() { return _console; }

	int agiInit();
	int agiDeinit();
	int agiDetectGame();
	int agiLoadResource(int16 resourceType, int16 resourceNr);
	int agiUnloadResource(int16 resourceType, int16 resourceNr);
	void agiUnloadResources();

	virtual int getKeypress();
	virtual bool isKeypress();
	virtual void clearKeyQueue();

	byte getVar(int16 varNr);
	void setVar(int16 varNr, byte newValue);

private:
	void setVolumeViaScripts(byte newVolume);
	void setVolumeViaSystemSetting();

public:
	void syncSoundSettings();

public:
	void decrypt(uint8 *mem, int len);
	void releaseSprites();
	uint16 processAGIEvents();
	int viewPictures();
	int runGame();
	int getAppDir(char *appDir, unsigned int size);

	int setupV2Game(int ver);
	int setupV3Game(int ver);

	void newRoom(int16 newRoomNr);
	void resetControllers();
	void interpretCycle();
	int playGame();

	void allowSynthetic(bool);
	void processScummVMEvents();
	void checkQuickLoad();

	// Objects
public:
	int showObjects();
	int loadObjects(const char *fname);
	int loadObjects(Common::File &fp);
	const char *objectName(uint16 objectNr);
	int objectGetLocation(uint16 objectNr);
	void objectSetLocation(uint16 objectNr, int);
private:
	int decodeObjects(uint8 *mem, uint32 flen);
	int readObjects(Common::File &fp, int flen);

	// Logic
public:
	int decodeLogic(int16 logicNr);
	void unloadLogic(int16 logicNr);
	int runLogic(int16 logicNr);
	void debugConsole(int, int, const char *);
	bool testIfCode(int16 logicNr);
	void executeAgiCommand(uint8, uint8 *);

private:
	bool _veryFirstInitialCycle; /**< signals, that currently the very first cycle is executed (restarts, etc. do not count!) */
	uint32 _instructionCounter; /**< counts every instruction, that got executed, can wrap around */

	bool _setVolumeBrokenFangame;

	void resetGetVarSecondsHeuristic();
	void getVarSecondsHeuristicTrigger();
	uint32 _getVarSecondsHeuristicLastInstructionCounter; /**< last time VM_VAR_SECONDS were read */
	uint16 _getVarSecondsHeuristicCounter; /**< how many times heuristic was triggered */

	uint32 _playTimeInSecondsAdjust; /**< milliseconds to adjust for calculating current play time in seconds, see setVarSecondsTrigger() */

	void setVarSecondsTrigger(byte newSeconds);

public:
	// Some submethods of testIfCode
	void skipInstruction(byte op);
	void skipInstructionsUntil(byte v);
	uint8 testObjRight(uint8, uint8, uint8, uint8, uint8);
	uint8 testObjCenter(uint8, uint8, uint8, uint8, uint8);
	uint8 testObjInBox(uint8, uint8, uint8, uint8, uint8);
	uint8 testPosn(uint8, uint8, uint8, uint8, uint8);
	uint8 testSaid(uint8, uint8 *);
	uint8 testController(uint8);
	uint8 testCompareStrings(uint8, uint8);

	// View
private:

	void lSetLoop(ScreenObjEntry *screenObj, int16 loopNr);
	void updateView(ScreenObjEntry *screenObj);

public:
	void setView(ScreenObjEntry *screenObj, int16 viewNr);
	void setLoop(ScreenObjEntry *screenObj, int16 loopNr);
	void setCel(ScreenObjEntry *screenObj, int16 celNr);

	void clipViewCoordinates(ScreenObjEntry *screenObj);

	void startUpdate(ScreenObjEntry *);
	void stopUpdate(ScreenObjEntry *);
	void updateScreenObjTable();
	void unloadView(int16 viewNr);
	int decodeView(byte *resourceData, uint16 resourceSize, int16 viewNr);

private:
	void unpackViewCelData(AgiViewCel *celData, byte *compressedData, uint16 compressedSize);
	void unpackViewCelDataAGI256(AgiViewCel *celData, byte *compressedData, uint16 compressedSize);

public:
	void addToPic(int, int, int, int, int, int, int);
	void drawObj(int);
	bool isEgoView(const ScreenObjEntry *screenObj);

	// Motion
private:
	int checkStep(int delta, int step);
	bool checkBlock(int16 x, int16 y);
	void changePos(ScreenObjEntry *screenObj);
	void motionWander(ScreenObjEntry *screenObj);
	void motionFollowEgo(ScreenObjEntry *screenObj);
	void motionMoveObj(ScreenObjEntry *screenObj);
	void motionMoveObjStop(ScreenObjEntry *screenObj);
	void checkMotion(ScreenObjEntry *screenObj);

public:
	void motionActivated(ScreenObjEntry *screenObj);
	void cyclerActivated(ScreenObjEntry *screenObj);
	void checkAllMotions();
	void moveObj(ScreenObjEntry *screenObj);
	void inDestination(ScreenObjEntry *screenObj);
	void fixPosition(int16 screenObjNr);
	void fixPosition(ScreenObjEntry *screenObj);
	void updatePosition();
	int getDirection(int16 objX, int16 objY, int16 destX, int16 destY, int16 stepSize);

	bool _keyHoldMode;
	Common::KeyCode _keyHoldModeLastKey;

	// Keyboard
	int doPollKeyboard();
	void cleanKeyboard();

	bool handleMouseClicks(uint16 &key);
	bool handleController(uint16 key);

	bool showPredictiveDialog();

	uint16 agiGetKeypress();
	int waitKey();
	int waitAnyKey();

	void nonBlockingText_IsShown();
	void nonBlockingText_Forget();

	void artificialDelay_Reset();
	void artificialDelay_CycleDone();

	uint16 artificialDelay_SearchTable(AgiArtificialDelayTriggerType triggerType, int16 orgNr, int16 newNr);

	void artificialDelayTrigger_NewRoom(int16 newRoomNr);
	void artificialDelayTrigger_DrawPicture(int16 newPictureNr);

private:
	int16 _artificialDelayCurrentRoom;
	int16 _artificialDelayCurrentPicture;

public:
	void redrawScreen();

	void inGameTimerReset(uint32 newPlayTime = 0);
	void inGameTimerResetPassedCycles();
	void inGameTimerPause();
	void inGameTimerResume();
	uint32 inGameTimerGet();
	uint32 inGameTimerGetPassedCycles();

	void inGameTimerUpdate();

private:
	uint32 _lastUsedPlayTimeInCycles; // 40 per second
	uint32 _lastUsedPlayTimeInSeconds; // actual seconds
	uint32 _passedPlayTimeCycles; // increased by 1 every time we passed a cycle

private:
	AgiOpCodeEntry _opCodes[256]; // always keep those at 256, so that there is no way for invalid memory access
	AgiOpCodeEntry _opCodesCond[256];

	void setupOpCodes(uint16 version);

public:
	const AgiOpCodeEntry *getOpCodesTable() { return _opCodes; }
};

} // End of namespace Agi

#endif /* AGI_AGI_H */