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

#include "common/array.h"
#include "common/events.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/random.h"
#include "common/savefile.h"
#include "common/serializer.h"
#include "common/str.h"
#include "common/substream.h"
#include "common/system.h"
#include "common/winexe.h"
#include "common/winexe_pe.h"
#include "engines/engine.h"
#include "graphics/pixelformat.h"
#include "graphics/wincursor.h"
#include "graphics/fontman.h"
#include "graphics/font.h"
#include "graphics/fonts/ttf.h"

#include "gnap/debugger.h"
#include "gnap/resource.h"
#include "gnap/scenes/scenecore.h"
#include "gnap/character.h"
#include "gnap/music.h"

struct ADGameDescription;

namespace Gnap {

class DatManager;
class SequenceResource;
class SpriteResource;
class GameSys;
class SoundMan;
class MusicPlayer;

#define GNAP_SAVEGAME_VERSION 2

struct MouseButtonState {
	bool _left;
	bool _right;
	MouseButtonState() : _left(false), _right(false) {
	}
};

struct Hotspot {
	Common::Rect _rect;
	uint16 _flags;

	bool isPointInside(Common::Point pos) const {
		return _rect.contains(pos);
	}

	bool isFlag(uint16 flag) const {
		return (_flags & flag) != 0;
	}

	void clearRect() {
		_rect = Common::Rect(0, 0, 0, 0);
	}
};

const int kMaxTimers = 10;

enum GnapDebugChannels {
	kDebugBasic	= 1 << 0,
	kDebugMusic = 1 << 1
};

enum {
	SF_NONE				= 0x0000,
	SF_LOOK_CURSOR		= 0x0001,
	SF_GRAB_CURSOR		= 0x0002,
	SF_TALK_CURSOR		= 0x0004,
	SF_PLAT_CURSOR		= 0x0008,
	SF_DISABLED			= 0x0010,
	SF_WALKABLE			= 0x0020,
	SF_EXIT_L_CURSOR	= 0x0040,
	SF_EXIT_R_CURSOR	= 0x0080,
	SF_EXIT_U_CURSOR	= 0x0100,
	SF_EXIT_D_CURSOR	= 0x0200,
	SF_EXIT_NW_CURSOR	= 0x0400,
	SF_EXIT_NE_CURSOR	= 0x0800,
	SF_EXIT_SW_CURSOR	= 0x1000,
	SF_EXIT_SE_CURSOR	= 0x2000
};

enum {
	LOOK_CURSOR		= 0,
	GRAB_CURSOR		= 1,
	TALK_CURSOR		= 2,
	PLAT_CURSOR		= 3,
	NOLOOK_CURSOR	= 4,
	NOGRAB_CURSOR	= 5,
	NOTALK_CURSOR	= 6,
	NOPLAT_CURSOR	= 7,
	EXIT_L_CURSOR	= 8,
	EXIT_R_CURSOR	= 9,
	EXIT_U_CURSOR	= 10,
	EXIT_D_CURSOR	= 11,
	EXIT_NE_CURSOR	= 12,
	EXIT_NW_CURSOR	= 13,
	EXIT_SE_CURSOR	= 14,
	EXIT_SW_CURSOR	= 15,
	WAIT_CURSOR		= 16
};

enum {
	kGSPullOutDevice			= 0,
	kGSPullOutDeviceNonWorking	= 1,
	kGSIdle						= 2,
	kGSBrainPulsating			= 3,
	kGSImpossible				= 4,
	kGSScratchingHead			= 5,
	kGSDeflect					= 6,
	kGSUseDevice				= 7,
	kGSMoan1					= 8,
	kGSMoan2					= 9
};

enum {
	kItemMagazine			= 0,
	kItemMud				= 1,
	kItemGrass				= 2,
	kItemDisguise			= 3,
	kItemNeedle				= 4,
	kItemTwig				= 5,
	kItemGas				= 6,
	kItemKeys				= 7,
	kItemDice				= 8,
	kItemTongs				= 9,
	kItemQuarter			= 10,
	kItemQuarterWithHole	= 11,
	kItemDiceQuarterHole	= 12,
	kItemWrench				= 13,
	kItemCowboyHat			= 14,
	kItemGroceryStoreHat	= 15,
	kItemBanana				= 16,
	kItemTickets			= 17,
	kItemPicture			= 18,
	kItemEmptyBucket		= 19,
	kItemBucketWithBeer		= 20,
	kItemBucketWithPill		= 21,
	kItemPill				= 22,
	kItemHorn				= 23,
	kItemJoint				= 24,
	kItemChickenBucket		= 25,
	kItemGum				= 26,
	kItemSpring				= 27,
	kItemLightbulb			= 28,
	kItemCereals			= 29
};

enum {
	kGFPlatypus				= 0,
	kGFMudTaken				= 1,
	kGFNeedleTaken			= 2,
	kGFTwigTaken			= 3,
	kGFUnk04				= 4,
	kGFKeysTaken			= 5,
	kGFGrassTaken			= 6,
	kGFBarnPadlockOpen		= 7,
	kGFTruckFilledWithGas	= 8,
	kGFTruckKeysUsed		= 9,
	kGFPlatypusDisguised	= 10,
	kGFSceneFlag1			= 11,
	kGFGnapControlsToyUFO	= 12,
	kGFUnk13				= 13, // Tongue Fight Won?
	kGFUnk14				= 14,
	kGFSpringTaken			= 15,
	kGFUnk16				= 16,
	kGFJointTaken			= 17,
	kGFUnk18				= 18,
	kGFGroceryStoreHatTaken	= 19,
	kGFPictureTaken			= 20,
	kGFUnk21				= 21,
	kGFUnk22				= 22,
	kGFUnk23				= 23,
	kGFUnk24				= 24,
	kGFUnk25				= 25,
	kGFPlatypusTalkingToAssistant = 26,
	kGFUnk27				= 27,
	kGFUnk28				= 28,
	kGFGasTaken				= 29,
	kGFUnk30				= 30,
	kGFUnk31				= 31
};

struct GnapSavegameHeader {
	uint8 _version;
	Common::String _saveName;
	Graphics::Surface *_thumbnail;
	int _year, _month, _day;
	int _hour, _minute;
};

class GnapEngine : public Engine {
protected:
	Common::Error run();
	virtual bool hasFeature(EngineFeature f) const;
public:
	GnapEngine(OSystem *syst, const ADGameDescription *gd);
	~GnapEngine();
private:
	const ADGameDescription *_gameDescription;
	Graphics::PixelFormat _pixelFormat;
	int _loadGameSlot;

public:
	Common::RandomSource *_random;
	Common::PEResources *_exe;

	DatManager *_dat;
	SpriteCache *_spriteCache;
	SoundCache *_soundCache;
	SequenceCache *_sequenceCache;
	GameSys *_gameSys;
	SoundMan *_soundMan;
	Debugger *_debugger;
	Scene *_scene;
	PlayerGnap *_gnap;
	PlayerPlat *_plat;
	MusicPlayer *_music;
	Graphics::Font *_font;

	Common::MemoryWriteStreamDynamic *_tempThumbnail;

	int _lastUpdateClock;
	bool _gameDone;

	bool _keyPressState[512];
	bool _keyDownState[512];

	bool _isPaused;
	Graphics::Surface *_pauseSprite;
	int _timers[kMaxTimers], _savedTimers[kMaxTimers];

	MouseButtonState _mouseButtonState;
	MouseButtonState _mouseClickState;

	bool _sceneSavegameLoaded, _wasSavegameLoaded;

	Graphics::Surface *_backgroundSurface;
	int _prevSceneNum, _currentSceneNum, _newSceneNum;
	bool _sceneDone, _sceneWaiting;

	uint32 _inventory, _gameFlags;

	Hotspot _hotspots[20];
	Common::Point _hotspotsWalkPos[20];
	int _hotspotsCount;
	int _sceneClickedHotspot;

	bool _isWaiting;
	bool _isLeavingScene;

	bool _isStockDatLoaded;

	int _newCursorValue, _cursorValue;

	int _verbCursor, _cursorIndex;
	Common::Point _mousePos;
	int _leftClickMouseX, _leftClickMouseY;

	Graphics::Surface *_grabCursorSprite;
	int _currGrabCursorX, _currGrabCursorY;
	int _grabCursorSpriteIndex, _newGrabCursorSpriteIndex;

	Graphics::Surface *_fullScreenSprite;
	int _fullScreenSpriteId;

	int _deviceX1, _deviceY1;

	int _soundTimerIndexA;
	int _soundTimerIndexB;
	int _soundTimerIndexC;
	int _idleTimerIndex;

	void updateEvents();
	void gameUpdateTick();
	void saveTimers();
	void restoreTimers();

	void pauseGame();
	void resumeGame();
	void updatePause();

	int getRandom(int max);

	int readSavegameDescription(int savegameNum, Common::String &description);
	int loadSavegame(int savegameNum);
	Common::Error saveGameState(int slot, const Common::String &desc);
	Common::Error loadGameState(int slot);
	Common::String generateSaveName(int slot);
	void synchronize(Common::Serializer &s);
	void writeSavegameHeader(Common::OutSaveFile *out, GnapSavegameHeader &header);
	static bool readSavegameHeader(Common::InSaveFile *in, GnapSavegameHeader &header);

	void delayTicks(int val, int idx, bool updateCursor);
	void delayTicksA(int val, int idx);
	void delayTicksCursor(int val);

	void setHotspot(int index, int16 x1, int16 y1, int16 x2, int16 y2, uint16 flags = SF_NONE,
		int16 walkX = -1, int16 walkY = -1);
	int getHotspotIndexAtPos(Common::Point pos);
	void updateCursorByHotspot();
	int getClickedHotspotId();

	int getInventoryItemSpriteNum(int index);

	void updateMouseCursor();
	void setVerbCursor(int verbCursor);
	void setCursor(int cursorIndex);
	void showCursor();
	void hideCursor();

	void setGrabCursorSprite(int index);
	void createGrabCursorSprite(int spriteId);
	void freeGrabCursorSprite();
	void updateGrabCursorSprite(int x, int y);

	void invClear();
	void invAdd(int itemId);
	void invRemove(int itemId);
	bool invHas(int itemId);

	void clearFlags();
	void setFlag(int num);
	void clearFlag(int num);
	bool isFlag(int num);

	Graphics::Surface *addFullScreenSprite(int resourceId, int id);
	void removeFullScreenSprite();
	void showFullScreenSprite(int resourceId);

	void queueInsertDeviceIcon();
	void insertDeviceIconActive();
	void removeDeviceIconActive();
	void setDeviceHotspot(int hotspotIndex, int x1, int y1, int x2, int y2);

	int getSequenceTotalDuration(int resourceId);

	bool isSoundPlaying(int resourceId);
	void playSound(int resourceId, bool looping);
	void stopSound(int resourceId);
	void setSoundVolume(int resourceId, int volume);

	void updateTimers();

	void initGameFlags(int num);
	void loadStockDat();

	void mainLoop();
	void initScene();
	void endSceneInit();
	void afterScene();

	int initSceneLogic();
	void runSceneLogic();

	void checkGameKeys();

	void startSoundTimerA(int timerIndex);
	int playSoundA();
	void startSoundTimerB(int timerIndex);
	int playSoundB();
	void startSoundTimerC(int timerIndex);
	int playSoundC();
	void startIdleTimer(int timerIndex);
	void updateIdleTimer();

	void screenEffect(int dir, byte r, byte g, byte b);

	bool isKeyStatus1(int key);
	bool isKeyStatus2(int key);
	void clearKeyStatus1(int key);
	void clearAllKeyStatus1();

	void deleteSurface(Graphics::Surface **surface);

	// Menu
	int _menuStatus;
	int _menuSpritesIndex;
	bool _menuDone;
	Graphics::Surface *_menuBackgroundSurface;
	Graphics::Surface *_menuQuitQuerySprite;
	Graphics::Surface *_largeSprite;
	Graphics::Surface *_menuSaveLoadSprite;
	Graphics::Surface *_menuSprite2;
	Graphics::Surface *_menuSprite1;
	char _savegameFilenames[7][30];
	Graphics::Surface *_savegameSprites[7];
	Graphics::Surface *_spriteHandle;
	Graphics::Surface *_cursorSprite;
	int _menuInventoryIndices[30];
	Graphics::Surface *_menuInventorySprites[30];
	int _savegameIndex;
	void createMenuSprite();
	void freeMenuSprite();
	void initMenuHotspots1();
	void initMenuHotspots2();
	void initMenuQuitQueryHotspots();
	void initSaveLoadHotspots();
	void drawInventoryFrames();
	void insertInventorySprites();
	void removeInventorySprites();
	void runMenu();
	void updateMenuStatusInventory();
	void updateMenuStatusMainMenu();
	void updateMenuStatusSaveGame();
	void updateMenuStatusLoadGame();
	void updateMenuStatusQueryQuit();

	// Grid common
	int _gridMinX, _gridMinY;
	int _gridMaxX, _gridMaxY;
	bool isPointBlocked(int gridX, int gridY);
	bool isPointBlocked(Common::Point gridPos);
	void initSceneGrid(int gridMinX, int gridMinY, int gridMaxX, int gridMaxY);
	bool testWalk(int animationIndex, int someStatus, int gridX1, int gridY1, int gridX2, int gridY2);

	// Gnap
	void doCallback(int callback);

	// Scenes
	int _toyUfoNextSequenceId, _toyUfoSequenceId;
	int _toyUfoId;
	int _toyUfoActionStatus;
	int _toyUfoX;
	int _toyUfoY;

	void initGlobalSceneVars();
	void playSequences(int fullScreenSpriteId, int sequenceId1, int sequenceId2, int sequenceId3);

	// Shared by scenes 17 & 18
	int _s18GarbageCanPos;

	// Scene 4x
	void toyUfoSetStatus(int flagNum);
	int toyUfoGetSequenceId();
	bool toyUfoCheckTimer();
	void toyUfoFlyTo(int destX, int destY, int minX, int maxX, int minY, int maxY, int animationIndex);

	void playMidi(const char *name);
	void stopMidi();
};

} // End of namespace Gnap

#endif // GNAP_GNAP_H