/* 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: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-10-0a/engines/agi/agi.h $
 * $Id: agi.h 27807 2007-07-01 00:57:39Z agent-q $
 *
 */

#ifndef AGI_H
#define AGI_H

#include "common/stdafx.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 "engines/engine.h"

namespace Agi {

typedef signed int Err;

/*
 * Version and other definitions
 */

#define USE_IIGS_SOUND

#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 STATUS_FG	0x00		/* Black */
#define	STATUS_BG	0x0f		/* White */

#define ADD_PIC 1
#define ADD_VIEW 2

} // 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_V2 = 1,
	GType_V3
};

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

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		// TODO: Should this be extended to include all fanmade games?
};

struct AGIGameDescription;

enum {
	NO_GAMEDIR = 0,
	GAMEDIR
};

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

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

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

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

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

	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 quitProgNow;		/**< quit now */
	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;
	uint8 *sbuf;			/**< 160x168 AGI screen buffer */

	/* 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];		/**< AGI sound resources */

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

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

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

class AgiLoader {
private:
	int intVersion;
	AgiEngine *_vm;

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 AgiEngine : public ::Engine {
	int _gameId;

protected:
	int init();
	int go();
	void shutdown();
	void initialize();

	bool initGame();

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

	const AGIGameDescription *_gameDescription;
	uint32 getGameID() const;
	uint32 getFeatures() const;
	uint16 getVersion() const;
	Common::Platform getPlatform() const;

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:
	AgiGame _game;
	AgiObject *_objects;	/* objects in the game */

	StringData _stringdata;

	AgiLoader *_loader;	/* loader */

	Common::RandomSource *_rnd;

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

	volatile uint32 _clockCount;

	uint8 *_intobj;
	int _oldMode;

	Menu* _menu;

	char _lastSentence[40];

	SpritesMgr *_sprites;
	GfxMgr *_gfx;
	SoundMgr *_sound;
	PictureMgr *_picture;

	#define INITIAL_IMAGE_STACK_SIZE 32

	int _stackSize;
	ImageStackElement *_imageStack;
	int _imageStackPointer;

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

	AgiDebug _debug;
	Common::RenderMode _renderMode;
	int _soundemu;

	int _keyControl;
	int _keyAlt;

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

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

	void newInputMode(int);
	void oldInputMode();

	int getflag(int);
	void setflag(int, int);
	void flipflag(int);
	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();

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

	static uint8* getWordsData();
	static uint32 getWordsDataSize();

	// 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(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
	char *_predictiveDictText;
	char **_predictiveDictLine;
	int32 _predictiveDictLines;
	char *_predictiveDictActLine;
	String _currentCode;
	String _currentWord;
	int _wordNumber;
	bool _predictiveDialogRunning;
public:
	char _predictiveResult[40];
};

} // End of namespace Agi

#endif /* AGI_H */
