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

#include "common/str.h"
#include "draci/script.h"
#include "draci/walking.h"

namespace Common {
class Serializer;
}

namespace Draci {

class BArchive;
class DraciEngine;

enum {
	kDragonObject = 0
};

enum {
	kDialogueLines = 4
};

enum {
	kBlackPalette = -1
};

enum {
	kMouseEnableSwitching = -1,
	kMouseDoNotSwitch = -2
};

// Constants tuned such that with ScummVM's default talkspeed kStandardSpeed, the speed
// computed by equation (kBaseSpeechDuration + kSpeechTimeUnit * #characters) /
// talkspeed is equal to the original game.
enum SpeechConstants {
	kBaseSpeechDuration = 12000,
	kSpeechTimeUnit = 2640,
	kStandardSpeed = 60
};

enum FadeConstants {
	// One fading phase called from the game scripts is 50ms.
	kFadingTimeUnit = 50,
	// Fading in/out when entering/leaving a location takes 15 iterations of (at least) 7ms each.
	kBlackFadingIterations = 15,
	kBlackFadingTimeUnit = 7
};

enum AnimationConstants {
	kTimeUnit = 20
};

/** Inventory related magical constants */
enum InventoryConstants {
  kInventoryItemWidth = 25,
  kInventoryItemHeight = 25,
  kInventoryColumns = 7,
  kInventoryLines = 5,
  kInventoryX = 70, ///< Used for positioning of the inventory sprite on the X axis
  kInventoryY = 30, ///< Used for positioning of the inventory sprite on the Y axis
  kInventorySlots = kInventoryLines * kInventoryColumns,
  kStatusChangeTimeout = 500
};

class GameObject {
public:
	int _absNum;
	uint _init, _look, _use, _canUse;
	bool _imInit, _imLook, _imUse;
	int _walkDir;
	byte _z;
	uint _lookX, _lookY, _useX, _useY;
	SightDirection _lookDir, _useDir;
	GPL2Program _program;
	Common::String _title;
	int _location;
	bool _visible;

	Common::Array<Animation *> _anim;
	int _playingAnim;

	int getAnim(int animID) const;
	int addAnim(Animation *anim);
	int playingAnim() const { return _playingAnim; }
	void playAnim(int i);
	void stopAnim();
	void deleteAnims();
	void deleteAnimsFrom(int index);
	void load(uint objNum, BArchive *archive);
};

struct GameInfo {
	int _startRoom;
	int _mapRoom;
	uint _numObjects;
	uint _numItems;
	byte _numVariables;
	byte _numPersons;
	byte _numDialogues;
	uint _maxItemWidth, _maxItemHeight;
	uint _musicLength;
	uint _crc[4];
	uint _numDialogueBlocks;
};

class GameItem {
public:
	int _absNum;
	uint _init, _look, _use, _canUse;
	bool _imInit, _imLook, _imUse;
	GPL2Program _program;
	Common::String _title;

	Animation *_anim;

	void load(int itemID, BArchive *archive);
};

struct Person {
	uint _x, _y;
	byte _fontColor;
};

struct Dialogue {
	int _canLen;
	byte *_canBlock;
	Common::String _title;
	GPL2Program _program;
};

class Room {
public:
	int _roomNum;
	byte _music;
	int _mapID;
	int _palette;
	int _numOverlays;
	int _init, _look, _use, _canUse;
	bool _imInit, _imLook, _imUse;
	bool _mouseOn, _heroOn;
	double _pers0, _persStep;
	int _escRoom;
	byte _numGates;
	Common::Array<int> _gates;
	GPL2Program _program;

	void load(int roomNum, BArchive *archive);
};

enum LoopStatus {
	kStatusOrdinary,	// normal game-play: everything allowed
	kStatusGate,		// during running init-scripts when entering a room: disable interactivity
	kStatusInventory,	// inventory is open: cannot change the room or go to map
	kStatusDialogue		// during a dialogue: cannot change the room, go to inventory
};

enum LoopSubstatus {
	kOuterLoop,		// outer loop: everything is allowed
	kInnerWhileTalk,	// playing a voice: inner loop will exit afterwards
	kInnerWhileFade,	// fading a palette: inner loop will exit when done
	kInnerDuringDialogue,	// selecting continuation block: inner block will exit afterwards
	kInnerUntilExit		// other inner loop: either immediately exiting or waiting for an animation to end (whose callback ends the loop)
};

class Game {
public:
	Game(DraciEngine *vm);
	~Game();

	void init();
	void start();
	void loop(LoopSubstatus substatus, bool shouldExit);

	// HACK: this is only for testing
	int nextRoomNum() const {
		int n = _currentRoom._roomNum;
		n = n < 37 ? n+1 : n;
		return n;
	}

	// HACK: same as above
	int prevRoomNum() const {
		int n = _currentRoom._roomNum;
		n = n > 0 ? n-1 : n;
		return n;
	}

	Common::Point findNearestWalkable(int x, int y) const { return _walkingMap.findNearestWalkable(x, y); }
	void heroAnimationFinished() { _walkingState.heroAnimationFinished(); }
	void stopWalking() { _walkingState.stopWalking(); }	// and clear callback
	void walkHero(int x, int y, SightDirection dir);	// start walking and leave callback as is
	void setHeroPosition(const Common::Point &p);
	const Common::Point &getHeroPosition() const { return _hero; }
	const Common::Point &getHeroLoadingPosition() const { return _heroLoading; }
	void positionAnimAsHero(Animation *anim);
	void positionHeroAsAnim(Animation *anim);

	// Makes sure animation anim_index plays on the hero.  If the hero's
	// position has changed, it updates the animation position.  If the new
	// animation is different, it stops the old one and starts the new one,
	// otherwise it just marks dirty rectangles for moving the position.
	// Returns the current animation phase of the new animation (usually 0
	// unless the animation hasn't changed).
	int playHeroAnimation(int anim_index);

	void loadOverlays();
	void loadWalkingMap(int mapID);		// but leaves _currentRoom._mapID untouched
	void switchWalkingAnimations(bool enabled);

	uint getNumObjects() const { return _info._numObjects; }
	GameObject *getObject(uint objNum) { return _objects + objNum; }
	const GameObject *getObjectWithAnimation(const Animation *anim) const;
	void deleteObjectAnimations();
	void deleteAnimationsAfterIndex(int lastAnimIndex);

	int getVariable(int varNum) const { return _variables[varNum]; }
	void setVariable(int varNum, int value) { _variables[varNum] = value; }

	const Person *getPerson(int personID) const { return &_persons[personID]; }

	int getRoomNum() const { return _currentRoom._roomNum; }
	void setRoomNum(int num) { _currentRoom._roomNum = num; }
	int getPreviousRoomNum() const { return _previousRoom; }
	void rememberRoomNumAsPrevious() { _previousRoom = getRoomNum(); }
	void scheduleEnteringRoomUsingGate(int room, int gate) { _newRoom = room; _newGate = gate; }
	void pushNewRoom();
	void popNewRoom();

	double getPers0() const { return _currentRoom._pers0; }
	double getPersStep() const { return _currentRoom._persStep; }
	int getMusicTrack() const { return _currentRoom._music; }
	void setMusicTrack(int num) { _currentRoom._music = num; }

	int getItemStatus(int itemID) const { return _itemStatus[itemID]; }
	void setItemStatus(int itemID, int status) { _itemStatus[itemID] = status; }
	GameItem *getItem(int id) { return id >= 0 && id < (int) _info._numItems ? &_items[id] : NULL; }
	GameItem *getCurrentItem() const { return _currentItem; }
	void setCurrentItem(GameItem *item) { _currentItem = item; }
	int getPreviousItemPosition() const { return _previousItemPosition; }
	void setPreviousItemPosition(int pos) { _previousItemPosition = pos; }
	void removeItem(GameItem *item);
	void loadItemAnimation(GameItem *item);
	void putItem(GameItem *item, int position);
	void addItem(int itemID);

	int getEscRoom() const { return _currentRoom._escRoom; }
	int getMapRoom() const { return _info._mapRoom; }
	int getMapID() const { return _currentRoom._mapID; }

	/**
	 * The GPL command Mark sets the animation index (which specifies the
	 * order in which animations were loaded in) which is then used by the
	 * Release command to delete all animations that have an index greater
	 * than the one marked.
	 */
	int getMarkedAnimationIndex() const { return _markedAnimationIndex; }
	void setMarkedAnimationIndex(int index) { _markedAnimationIndex = index; }

	void setLoopStatus(LoopStatus status) { _loopStatus = status; }
	void setLoopSubstatus(LoopSubstatus status) { _loopSubstatus = status; }
	LoopStatus getLoopStatus() const { return _loopStatus; }
	LoopSubstatus getLoopSubstatus() const { return _loopSubstatus; }

	bool gameShouldQuit() const { return _shouldQuit; }
	void setQuit(bool quit) { _shouldQuit = quit; }
	bool shouldExitLoop() const { return _shouldExitLoop; }
	void setExitLoop(bool exit) { _shouldExitLoop = exit; }
	bool isReloaded() const { return _isReloaded; }
	void setIsReloaded(bool value) { _isReloaded = value; }
	bool isPositionLoaded() { return _isPositionLoaded; }
	void setPositionLoaded(bool value) { _isPositionLoaded = value; }

	void setSpeechTiming(uint tick, uint duration);
	void shiftSpeechAndFadeTick(int delta);

	void inventoryInit();
	void inventoryDraw();
	void inventoryDone();
	void inventoryReload();
	void inventorySwitch(int keycode);

	void dialogueMenu(int dialogueID);
	int dialogueDraw();
	void dialogueInit(int dialogID);
	void dialogueDone();

	bool isDialogueBegin() const { return _dialogueBegin; }
	bool shouldExitDialogue() const { return _dialogueExit; }
	void setDialogueExit(bool exit) { _dialogueExit = exit; }
	int getDialogueBlockNum() const { return _blockNum; }
	int getDialogueVar(int dialogueID) const { return _dialogueVars[dialogueID]; }
	void setDialogueVar(int dialogueID, int value) { _dialogueVars[dialogueID] = value; }
	int getCurrentDialogue() const { return _currentDialogue; }
	int getDialogueCurrentBlock() const { return _currentBlock; }
	int getDialogueLastBlock() const { return _lastBlock; }
	int getDialogueLinesNum() const { return _dialogueLinesNum; }
	int getCurrentDialogueOffset() const { return _dialogueOffsets[_currentDialogue]; }

	void schedulePalette(int paletteID) { _scheduledPalette = paletteID; }
	int getScheduledPalette() const { return _scheduledPalette; }
	void initializeFading(int phases);
	void setEnableQuickHero(bool value) { _enableQuickHero = value; }
	bool getEnableQuickHero() const { return _enableQuickHero; }
	void setWantQuickHero(bool value) { _wantQuickHero = value; }
	bool getWantQuickHero() const { return _wantQuickHero; }
	void setEnableSpeedText(bool value) { _enableSpeedText = value; }
	bool getEnableSpeedText() const { return _enableSpeedText; }

	void DoSync(Common::Serializer &s, uint8 saveVersion);

private:
	void updateOrdinaryCursor();
	void updateInventoryCursor();
	int inventoryPositionFromMouse() const;
	void handleOrdinaryLoop(int x, int y);
	void handleInventoryLoop();
	void handleDialogueLoop();
	void updateTitle(int x, int y);
	void updateCursor();
	void fadePalette(bool fading_out);
	void advanceAnimationsAndTestLoopExit();
	void handleStatusChangeByMouse();

	void enterNewRoom();
	void initWalkingOverlays();
	void loadRoomObjects();
	void redrawWalkingPath(Animation *anim, byte color, const WalkingPath &path);

	DraciEngine *_vm;

	GameInfo _info;

	Common::Point _hero;
	Common::Point _heroLoading;
	Common::Point _lastTarget;

	int *_variables;
	Person *_persons;
	GameObject *_objects;

	byte *_itemStatus;
	GameItem *_items;
	GameItem *_currentItem;
	GameItem *_itemUnderCursor;

	// Last position in the inventory of the item currently in the hands, resp. of the item that
	// was last in our hands.
	int _previousItemPosition;

	GameItem *_inventory[kInventorySlots];

	Room _currentRoom;
	int _newRoom;
	int _newGate;
	int _previousRoom;
	int _pushedNewRoom;	// used in GPL programs
	int _pushedNewGate;

	uint *_dialogueOffsets;
	int _currentDialogue;
	int *_dialogueVars;
	BArchive *_dialogueArchive;
	Dialogue *_dialogueBlocks;
	bool _dialogueBegin;
	bool _dialogueExit;
	int _currentBlock;
	int _lastBlock;
	int _dialogueLinesNum;
	int _blockNum;
	int _lines[kDialogueLines];
	Animation *_dialogueAnims[kDialogueLines];

	LoopStatus _loopStatus;
	LoopSubstatus _loopSubstatus;

	bool _shouldQuit;
	bool _shouldExitLoop;
	bool _isReloaded;
	bool _isPositionLoaded;

	uint _speechTick;
	uint _speechDuration;

	const GameObject *_objUnderCursor;
	const Animation *_animUnderCursor;

	int _markedAnimationIndex; ///< Used by the Mark GPL command

	int _scheduledPalette;
	int _fadePhases;
	int _fadePhase;
	uint _fadeTick;
	bool _isFadeOut;
	int _mouseChangeTick;

	bool _enableQuickHero;
	bool _wantQuickHero;
	bool _enableSpeedText;

	WalkingMap _walkingMap;
	WalkingState _walkingState;

	Animation *_titleAnim;
	Animation *_inventoryAnim;
	Animation *_walkingMapOverlay;
	Animation *_walkingShortestPathOverlay;
	Animation *_walkingObliquePathOverlay;
};

} // End of namespace Draci

#endif // DRACI_GAME_H
