/* 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.
 *
 * $URL$
 * $Id$
 *
 */

#ifndef AGI_H
#define AGI_H


#include "common/scummsys.h"
#include "common/endian.h"
#include "common/util.h"
#include "common/file.h"
#include "common/savefile.h"
#include "common/system.h"
#include "common/hash-str.h"
#include "common/stack.h"

#include "engines/engine.h"

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_DIRS	256
#define	MAX_VARS	256
#define	MAX_FLAGS	(256 >> 3)
#define MAX_VIEWTABLE	255	/* KQ3 uses o255! */
#define MAX_WORDS	20
#define	MAX_STRINGS	24		/* MAX_STRINGS + 1 used for get.num */
#define MAX_STRINGLEN	40
#ifndef MAX_PATH
#define MAX_PATH	260
#endif

#define	_EMPTY		0xfffff
#define	EGO_OWNED	0xff

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

#ifndef INLINE
#define INLINE
#endif

#define	MSG_BOX_COLOUR	0x0f	/* White */
#define MSG_BOX_TEXT	0x00	/* Black */
#define MSG_BOX_LINE	0x04	/* Red */
#define BUTTON_BORDER	0x00	/* Black */
#define STATUS_FG	0x00		/* Black */
#define	STATUS_BG	0x0f		/* White */

#define ADD_PIC 1
#define ADD_VIEW 2

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,
	GID_MICKEY,			// PreAGI
	GID_WINNIE,			// PreAGI
	GID_TROLL			// PreAGI
};

} // End of namespace Agi

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

namespace Agi {

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

/*
 * 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),
	GF_AGDS =        (1 << 1),
	GF_AGI256 =      (1 << 2),
	GF_AGI256_2 =    (1 << 3),
	GF_AGIPAL =      (1 << 4),
	GF_MACGOLDRUSH = (1 << 5),
	GF_FANMADE =     (1 << 6),
	GF_MENUS =		 (1 << 7),
	GF_ESCPAUSE =	 (1 << 8),
	GF_OLDAMIGAV20 = (1 << 9),
	GF_CLIPCOORDS  = (1 << 10)
};

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 {
	rLOGIC = 1,
	rSOUND,
	rVIEW,
	rPICTURE
};

enum {
	RES_LOADED = 1,
	RES_COMPRESSED = 0x40
};

enum {
	lCOMMAND_MODE = 1,
	lTEST_MODE
};

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

struct Mouse {
	int button;
	unsigned int x;
	unsigned int y;
};

// 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
};

#define report printf

enum GameId {
	GID_AGI = 1
};

#define WIN_TO_PIC_X(x) ((x) / 2)
#define WIN_TO_PIC_Y(y) ((y) < 8 ? 999 : (y) >= (8 + _HEIGHT) ? 999 : (y) - 8)

/**
 * AGI variables.
 */
enum {
	vCurRoom = 0,		/* 0 */
	vPrevRoom,
	vBorderTouchEgo,
	vScore,
	vBorderCode,
	vBorderTouchObj,	/* 5 */
	vEgoDir,
	vMaxScore,
	vFreePages,
	vWordNotFound,
	vTimeDelay,		/* 10 */
	vSeconds,
	vMinutes,
	vHours,
	vDays,
	vJoystickSensitivity,	/* 15 */
	vEgoViewResource,
	vAgiErrCode,
	vAgiErrCodeInfo,
	vKey,
	vComputer,		/* 20 */
	vWindowReset,
	vSoundgen,
	vVolume,
	vMaxInputChars,
	vSelItem,		/* 25 */
	vMonitor
};

/**
 * 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)
};

/**
 * AGI flags
 */
enum {
	fEgoWater = 0,	/* 0 */
	fEgoInvisible,
	fEnteredCli,
	fEgoTouchedP2,
	fSaidAcceptedInput,
	fNewRoomExec,	/* 5 */
	fRestartGame,
	fScriptBlocked,
	fJoySensitivity,
	fSoundOn,
	fDebuggerOn,		/* 10 */
	fLogicZeroFirsttime,
	fRestoreJustRan,
	fStatusSelectsItems,
	fMenusWork,
	fOutputMode,		/* 15 */
	fAutoRestart
};

struct AgiEvent {
	uint16 data;
	uint8 occured;
};

struct AgiObject {
	int location;
	char *name;
};

struct AgiWord {
	int id;
	char *word;
};

struct AgiDir {
	uint8 volume;
	uint32 offset;
	uint32 len;
	uint32 clen;
	uint8 flags;
	/* 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
	 */
};

struct AgiBlock {
	int active;
	int x1, y1;
	int x2, y2;
	uint8 *buffer;		/* used for window background */
};

/** AGI text color (Background and foreground color). */
struct AgiTextColor {
	/** Creates an AGI text color. Uses black text on white background by default. */
	AgiTextColor(int fgColor = 0x00, int bgColor = 0x0F) : fg(fgColor), bg(bgColor) {}

	/** Get an AGI text color with swapped foreground and background color. */
	AgiTextColor swap() const { return AgiTextColor(bg, fg); }

	int fg; ///< Foreground color (Used for text).
	int bg; ///< Background color (Used for text's background).
};

/**
 * AGI button style (Amiga or PC).
 *
 * Supports positive and negative button types (Used with Amiga-style only):
 * Positive buttons do what the dialog was opened for.
 * Negative buttons cancel what the dialog was opened for.
 * Restart-dialog example: Restart-button is positive, Cancel-button negative.
 * Paused-dialog example: Continue-button is positive.
 */
struct AgiButtonStyle {
// Public constants etc
public:
	static const int
		// Amiga colors (Indexes into the Amiga-ish palette)
		amigaBlack  = 0x00, ///< Accurate,                   is          #000000 (24-bit RGB)
		amigaWhite  = 0x0F, ///< Practically accurate,       is close to #FFFFFF (24-bit RGB)
		amigaGreen  = 0x02, ///< Quite accurate,             should be   #008A00 (24-bit RGB)
		amigaOrange = 0x0C, ///< Inaccurate, too much blue,  should be   #FF7500 (24-bit RGB)
		amigaPurple = 0x0D, ///< Inaccurate, too much green, should be   #FF00FF (24-bit RGB)
		amigaRed    = 0x04, ///< Quite accurate,             should be   #BD0000 (24-bit RGB)
		amigaCyan   = 0x0B, ///< Inaccurate, too much red,   should be   #00FFDE (24-bit RGB)
		// PC colors (Indexes into the EGA-palette)
		pcBlack     = 0x00,
		pcWhite     = 0x0F;

// Public methods
public:
	/**
	 * Get the color of the button with the given state and type using current style.
	 *
	 * @param hasFocus True if button has focus, false otherwise.
	 * @param pressed True if button is being pressed, false otherwise.
	 * @param positive True if button is positive, false if button is negative. Only matters for Amiga-style buttons.
	 */
	AgiTextColor getColor(bool hasFocus, bool pressed, bool positive = true) const;

	/**
	 * Get the color of a button with the given base color and state ignoring current style.
	 * Swaps foreground and background color when the button has focus or is being pressed.
	 *
	 * @param hasFocus True if button has focus, false otherwise.
	 * @param pressed True if button is being pressed, false otherwise.
	 * @param baseFgColor Foreground color of the button when it has no focus and is not being pressed.
	 * @param baseBgColor Background color of the button when it has no focus and is not being pressed.
	 */
	AgiTextColor getColor(bool hasFocus, bool pressed, int baseFgColor, int baseBgColor) const;

	/**
	 * Get the color of a button with the given base color and state ignoring current style.
	 * Swaps foreground and background color when the button has focus or is being pressed.
	 *
	 * @param hasFocus True if button has focus, false otherwise.
	 * @param pressed True if button is being pressed, false otherwise.
	 * @param baseColor Color of the button when it has no focus and is not being pressed.
	 */
	AgiTextColor getColor(bool hasFocus, bool pressed, const AgiTextColor &baseColor) const;

	/**
	 * How many pixels to offset the shown text diagonally down and to the right.
	 * Currently only used for pressed PC-style buttons.
	 */
	int getTextOffset(bool hasFocus, bool pressed) const;

	/**
	 * Show border around the button?
	 * Currently border is only used for in focus or pressed Amiga-style buttons
	 * when in inauthentic Amiga-style mode.
	 */
	bool getBorder(bool hasFocus, bool pressed) const;

	/**
	 * Set Amiga-button style.
	 *
	 * @param amigaStyle Set Amiga-button style if true, otherwise set PC-button style.
	 * @param olderAgi If true then use older AGI style in Amiga-mode, otherwise use newer.
	 * @param authenticAmiga If true then don't use a border around buttons in Amiga-mode, otherwise use.
	 */
	void setAmigaStyle(bool amigaStyle = true, bool olderAgi = false, bool authenticAmiga = false);

	/**
	 * Set PC-button style.
	 * @param pcStyle Set PC-button style if true, otherwise set default Amiga-button style.
	 */
	void setPcStyle(bool pcStyle = true);

// Public constructors
public:
	/**
	 * Create a button style based on the given rendering mode.
	 * @param renderMode If Common::kRenderAmiga then creates default Amiga-button style, otherwise PC-style.
	 */
	AgiButtonStyle(Common::RenderMode renderMode = Common::kRenderDefault);

// Private member variables
private:
	bool _amigaStyle;     ///< Use Amiga-style buttons if true, otherwise use PC-style buttons.
	bool _olderAgi;       ///< Use older AGI style in Amiga-style mode.
	bool _authenticAmiga; ///< Don't use border around buttons in Amiga-style mode.
};

#define EGO_VIEW_TABLE	0
#define	HORIZON		36
#define _WIDTH		160
#define _HEIGHT		168

/**
 * AGI game structure.
 * This structure contains all global data of an AGI game executed
 * by the interpreter.
 */
struct AgiGame {
#define STATE_INIT	0x00
#define STATE_LOADED	0x01
#define STATE_RUNNING	0x02
	int state;		/**< state of the interpreter */

	// 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 */
	uint8 vars[MAX_VARS];   /**< 256 variables */

	/* internal variables */
	int horizon;			/**< horizon y coordinate */
	int lineStatus;		/**< line number to put status on */
	int lineUserInput;	/**< line to put user input on */
	int lineMinPrint;		/**< num lines to print on */
	int cursorPos;			/**< column where the input cursor is */
	uint8 inputBuffer[40]; /**< buffer for user input */
	uint8 echoBuffer[40];	/**< buffer for echo.line */
	int keypress;
#define INPUT_NORMAL	0x01
#define INPUT_GETSTRING	0x02
#define INPUT_MENU	0x03
#define INPUT_NONE	0x04
	int inputMode;			/**< keyboard input mode */
	int inputEnabled;		/**< keyboard input enabled */
	int lognum;				/**< current logic number */

	/* internal flags */
	int playerControl;		/**< player is in control */
	int statusLine;		/**< status line on/off */
	int clockEnabled;		/**< clock is on/off */
	int exitAllLogics;	/**< break cycle after new.room */
	int pictureShown;		/**< show.pic has been issued */
	int hasPrompt;			/**< input prompt has been printed */
#define ID_AGDS		0x00000001
#define ID_AMIGA	0x00000002
	int gameFlags;			/**< agi options flags */

	uint8 priTable[_HEIGHT];/**< priority table */

	/* windows */
	uint32 msgBoxTicks;	/**< timed message box tick counter */
	AgiBlock block;
	AgiBlock window;
	int hasWindow;

	/* graphics & text */
	int gfxMode;
	char cursorChar;
	unsigned int colorFg;
	unsigned int colorBg;
#define SBUF16_OFFSET 0
#define SBUF256_OFFSET ((_WIDTH) * (_HEIGHT))
#define FROM_SBUF16_TO_SBUF256_OFFSET ((SBUF256_OFFSET) - (SBUF16_OFFSET))
#define FROM_SBUF256_TO_SBUF16_OFFSET ((SBUF16_OFFSET) - (SBUF256_OFFSET))
	uint8 *sbufOrig;		/**< Pointer to the 160x336 AGI screen buffer that contains vertically two 160x168 screens (16 color and 256 color). */
	uint8 *sbuf16c;			/**< 160x168 16 color (+control line & priority information) AGI screen buffer. Points at sbufOrig + SBUF16_OFFSET. */
	uint8 *sbuf256c;		/**< 160x168 256 color AGI screen buffer (For AGI256 and AGI256-2 support). Points at sbufOrig + SBUF256_OFFSET. */
	uint8 *sbuf;			/**< Currently chosen AGI screen buffer (sbuf256c if AGI256 or AGI256-2 is used, otherwise sbuf16c). */

	/* player command line */
	AgiWord egoWords[MAX_WORDS];
	int numEgoWords;

	unsigned int numObjects;

	AgiEvent evKeyp[MAX_DIRS];  /**< keyboard keypress events */
	char strings[MAX_STRINGS + 1][MAX_STRINGLEN]; /**< strings */

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

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

	/* view table */
	VtEntry viewTable[MAX_VIEWTABLE];

	int32 ver;								/**< detected game version */

	int simpleSave;						/**< select simple savegames */
};

class AgiLoader {
public:

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

	virtual int init() = 0;
	virtual int deinit() = 0;
	virtual int detectGame() = 0;
	virtual int loadResource(int, int) = 0;
	virtual int unloadResource(int, int) = 0;
	virtual int loadObjects(const char *) = 0;
	virtual int loadWords(const char *) = 0;
	virtual int version() = 0;
	virtual void setIntVersion(int) = 0;
	virtual int getIntVersion() = 0;
};

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

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

public:

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

	virtual int init();
	virtual int deinit();
	virtual int detectGame();
	virtual int loadResource(int, int);
	virtual int unloadResource(int, int);
	virtual int loadObjects(const char *);
	virtual int loadWords(const char *);
	virtual int version();
	virtual void setIntVersion(int);
	virtual int getIntVersion();
};

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

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

public:

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

	virtual int init();
	virtual int deinit();
	virtual int detectGame();
	virtual int loadResource(int, int);
	virtual int unloadResource(int, int);
	virtual int loadObjects(const char *);
	virtual int loadWords(const char *);
	virtual int version();
	virtual void setIntVersion(int);
	virtual int getIntVersion();
};

class GfxMgr;
class SpritesMgr;
class Menu;
class SearchTree;

extern struct Mouse g_mouse;

/* 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 != Common::kNoError)
			return err;
		return go();
	}
	virtual bool hasFeature(EngineFeature f) const;

	virtual void initialize() = 0;

public:
	GfxMgr *_gfx;

	AgiButtonStyle _defaultButtonStyle;
	AgiButtonStyle _buttonStyle;
	Common::RenderMode _renderMode;
	volatile uint32 _clockCount;
	AgiDebug _debug;
	AgiGame _game;
	Common::RandomSource *_rnd;

	virtual void agiTimerLow() = 0;
	virtual int agiGetKeypressLow() = 0;
	virtual int agiIsKeypressLow() = 0;

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

	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;
	virtual	int saveGame(const char *fileName, const char *saveName) = 0;
	virtual int loadGame(const char *fileName, bool checkId = true) = 0;

	int _soundemu;

	int getflag(int);
	void setflag(int, int);
	void flipflag(int);

	const AGIGameDescription *_gameDescription;
	uint32 getGameID() const;
	uint32 getFeatures() const;
	uint16 getVersion() const;
	uint16 getGameType() const;
	Common::Language getLanguage() const;
	Common::Platform getPlatform() const;
	Common::Error loadGameState(int slot);
	Common::Error saveGameState(int slot, const char *desc);
	bool canLoadGameStateCurrently();
	bool canSaveGameStateCurrently();
};

class AgiEngine : public AgiBase {
	int _gameId;

protected:
	// Engine APIs
	virtual Common::Error go();
	virtual void syncSoundSettings();

	void initialize();

	uint32 _lastSaveTime;

public:
	AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc);
	virtual ~AgiEngine();
	int getGameId() {
		return _gameId;
	}


private:

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

	bool _allowSynthetic;

	int checkPriority(VtEntry *v);
	int checkCollision(VtEntry *v);
	int checkPosition(VtEntry *v);

	uint32 matchVersion(uint32 crc);

	int _firstSlot;

public:
	AgiObject *_objects;	/* objects in the game */

	StringData _stringdata;

	const char *getSavegameFilename(int num);
	void getSavegameDescription(int num, char *buf, bool showEmpty = true);
	int selectSlot();
	int saveGame(const char *fileName, const char *saveName);
	int saveGameDialog();
	int saveGameSimple();
	int loadGame(const char *fileName, bool checkId = true);
	int loadGameDialog();
	int loadGameSimple();

	uint8 *_intobj;
	int _oldMode;
	bool restartGame;

	Menu* _menu;

	char _lastSentence[40];

	SpritesMgr *_sprites;
	SoundMgr *_sound;
	PictureMgr *_picture;
	AgiLoader *_loader;	/* loader */

	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();

	Console *_console;

	int agiInit();
	int agiDeinit();
	int agiVersion();
	int agiGetRelease();
	void agiSetRelease(int);
	int agiDetectGame();
	int agiLoadResource(int, int);
	int agiUnloadResource(int, int);
	void agiUnloadResources();

	virtual void agiTimerLow();
	virtual int agiGetKeypressLow();
	virtual int agiIsKeypressLow();
	static void agiTimerFunctionLow(void *refCon);
	void initPriTable();

	void newInputMode(int);
	void oldInputMode();

	int getvar(int);
	void setvar(int, int);
	void decrypt(uint8 * mem, int len);
	void releaseSprites();
	int mainCycle();
	int viewPictures();
	int parseCli(int, char **);
	int runGame();
	void inventory();
	void listGames();
	uint32 matchCrc(uint32, char *, int);
	int v2IdGame();
	int v3IdGame();
	int v4IdGame(uint32 ver);
	void updateTimer();
	int getAppDir(char *appDir, unsigned int size);

	int setupV2Game(int ver, uint32 crc);
	int setupV3Game(int ver, uint32 crc);

	void newRoom(int n);
	void resetControllers();
	void interpretCycle();
	int playGame();

	void printItem(int n, int fg, int bg);
	int findItem();
	int showItems();
	void selectItems(int n);

	void allowSynthetic(bool);
	void processEvents();
	void checkQuickLoad();

	// Objects
	int showObjects();
	int decodeObjects(uint8 *mem, uint32 flen);
	int loadObjects(const char *fname);
	int allocObjects(int);
	void unloadObjects();
	const char *objectName(unsigned int);
	int objectGetLocation(unsigned int);
	void objectSetLocation(unsigned int, int);

	// Logic
	int decodeLogic(int);
	void unloadLogic(int);
	int runLogic(int);
	void debugConsole(int, int, const char *);
	int testIfCode(int);
	void executeAgiCommand(uint8, uint8 *);

	// View
private:

	void lSetCel(VtEntry *v, int n);
	void lSetLoop(VtEntry *v, int n);
	void updateView(VtEntry *v);

public:

	void setCel(VtEntry *, int);
	void clipViewCoordinates(VtEntry *v);
	void setLoop(VtEntry *, int);
	void setView(VtEntry *, int);
	void startUpdate(VtEntry *);
	void stopUpdate(VtEntry *);
	void updateViewtable();
	void unloadView(int);
	int decodeView(int);
	void addToPic(int, int, int, int, int, int, int);
	void drawObj(int);
	bool isEgoView(const VtEntry *v);

	// Words
	int showWords();
	int loadWords(const char *);
	void unloadWords();
	int findWord(char *word, int *flen);
	void dictionaryWords(char *);

	// Motion
private:
	int checkStep(int delta, int step);
	int checkBlock(int x, int y);
	void changePos(VtEntry *v);
	void motionWander(VtEntry *v);
	void motionFollowEgo(VtEntry *v);
	void motionMoveObj(VtEntry *v);
	void checkMotion(VtEntry *v);
public:
	void checkAllMotions();
	void moveObj(VtEntry *);
	void inDestination(VtEntry *);
	void fixPosition(int);
	void updatePosition();
	int getDirection(int x0, int y0, int x, int y, int s);

	// Keyboard
	void initWords();
	void cleanInput();
	int doPollKeyboard();
	void cleanKeyboard();
	void handleKeys(int);
	void handleGetstring(int);
	int handleController(int);
	void getString(int, int, int, int);
	uint16 agiGetKeypress();
	int waitKey();
	int waitAnyKey();

	// Text
public:
	#define MAXWORDLEN 24

	typedef Common::String String;

	int messageBox(const char *);
	int selectionBox(const char *, const char **);
	void closeWindow(void);
	void drawWindow(int, int, int, int);
	void printText(const char *, int, int, int, int, int, int, bool checkerboard = false);
	void printTextConsole(const char *, int, int, int, int, int);
	int print(const char *, int, int, int);
	char *wordWrapString(const char *, int *);
	char *agiSprintf(const char *);
	void writeStatus(void);
	void writePrompt(void);
	void clearLines(int, int, int);
	void flushLines(int, int);
	bool predictiveDialog(void);

private:
	void printStatus(const char *message, ...);
	void printText2(int l, const char *msg, int foff, int xoff, int yoff, int len, int fg, int bg, bool checkerboard = false);
	void blitTextbox(const char *p, int y, int x, int len);
	void eraseTextbox();
	char *safeStrcat(char *s, const char *t);
	void loadDict(void);
	bool matchWord(void);

	// Predictive dialog
	// TODO: Move this to a separate class
	char *_predictiveDictText;
	char **_predictiveDictLine;
	int32 _predictiveDictLineCount;
	char *_predictiveDictActLine;
	String _currentCode;
	String _currentWord;
	int _wordNumber;
	bool _predictiveDialogRunning;
public:
	char _predictiveResult[40];
};

} // End of namespace Agi

#endif /* AGI_H */