/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

// Scene management module private header file

#ifndef SAGA_SCENE_H
#define SAGA_SCENE_H

#include "saga/font.h"
#include "saga/actor.h"
#include "saga/interface.h"
#include "saga/puzzle.h"
#include "saga/events.h"

// Some defines used for detection.
#include "saga/shared_detection_defines.h"

namespace Saga {

//#define SCENE_DEBUG // for scene debugging

#define SCENE_DOORS_MAX 16
#define NO_CHAPTER_CHANGE -2

// Scenes
#define ITE_SCENE_INV -1
#define ITE_SCENE_PUZZLE 26
#define ITE_SCENE_LODGE 21
#define ITE_SCENE_ENDCREDIT1 295
#define ITE_SCENE_OVERMAP 226

class ObjectMap;

enum SceneFlags {
	kSceneFlagISO        = 1,
	kSceneFlagShowCursor = 2
};

struct BGInfo {
	Rect bounds;
	byte *buffer;
};

typedef int (SceneProc) (int, void *);


enum SCENE_PROC_PARAMS {
	SCENE_BEGIN = 0,
	SCENE_END
};

// Resource type numbers
enum SAGAResourceTypes {
	SAGA_UNKNOWN,
	SAGA_ACTOR,
	SAGA_OBJECT,
	SAGA_BG_IMAGE,
	SAGA_BG_MASK,
	SAGA_STRINGS,
	SAGA_OBJECT_MAP,
	SAGA_ACTION_MAP,
	SAGA_ISO_IMAGES,
	SAGA_ISO_MAP,
	SAGA_ISO_PLATFORMS,
	SAGA_ISO_METATILES,
	SAGA_ENTRY,
	SAGA_ANIM,
	SAGA_ISO_MULTI,
	SAGA_PAL_ANIM,
	SAGA_FACES,
	SAGA_PALETTE
};

#define SAGA_RESLIST_ENTRY_LEN 4

struct SceneResourceData {
	uint32 resourceId;
	int resourceType;
	bool invalid;

	SceneResourceData() : resourceId(0), resourceType(0),  invalid(false) {
	}
};

typedef Common::Array<SceneResourceData> SceneResourceDataArray;

#define SAGA_SCENE_DESC_LEN 16

struct SceneDescription {
	int16 flags;
	int16 resourceListResourceId;
	int16 endSlope;
	int16 beginSlope;
	uint16 scriptModuleNumber;
	uint16 sceneScriptEntrypointNumber;
	uint16 startScriptEntrypointNumber;
	int16 musicResourceId;

	void reset()  {
		flags = resourceListResourceId = endSlope = beginSlope = scriptModuleNumber = sceneScriptEntrypointNumber = startScriptEntrypointNumber = musicResourceId = 0;
	}
};

struct SceneEntry {
	Location location;
	uint16 facing;
};

typedef Common::Array<SceneEntry> SceneEntryList;

struct SceneImage {
	bool loaded;
	int w;
	int h;
	int p;
	ByteArray buffer;
	PalEntry pal[256];

	SceneImage() : loaded(false), w(0), h(0), p(0) {
		memset(pal, 0, sizeof(pal));
	}
};


enum SceneTransitionType {
	kTransitionNoFade,
	kTransitionFade
};

enum SceneLoadFlags {
	kLoadByResourceId = 0,
	kLoadBySceneNumber = 1,
	kLoadIdTypeMask = 1,
	kLoadBgMaskIsImage = 1 << 16,
};

struct LoadSceneParams {
	int32 sceneDescriptor;
	SceneLoadFlags loadFlag;
	SceneProc *sceneProc;
	bool sceneSkipTarget;
	SceneTransitionType transitionType;
	int actorsEntrance;
	int chapter;
};

typedef Common::List<LoadSceneParams> SceneQueueList;

///// IHNM-specific stuff
#define IHNM_PALFADE_TIME    1000
#define IHNM_INTRO_FRAMETIME 80
#define IHNM_DGLOGO_TIME     8000
#define IHNM_TITLE_TIME_GM   28750
#define IHNM_TITLE_TIME_FM   19500

#define CREDIT_DURATION1 4000

class Scene {
 public:
	Scene(SagaEngine *vm);
	~Scene();

// Console functions
	void cmdActionMapInfo();
	void cmdObjectMapInfo();

	void cmdSceneChange(int argc, const char **argv);

	void startScene();
	void creditsScene();
	void nextScene();
	void skipScene();
	void endScene();
	void restoreScene();
	void queueScene(const LoadSceneParams &sceneQueue) {
		_sceneQueue.push_back(sceneQueue);
	}

	void draw();
	int getFlags() const { return _sceneDescription.flags; }
	int getScriptModuleNumber() const { return _sceneDescription.scriptModuleNumber; }
	bool isInIntro() { return !_inGame; }
	const Rect& getSceneClip() const { return _sceneClip; }

	void getBGMaskInfo(int &width, int &height, byte *&buffer);
	int isBGMaskPresent() { return _bgMask.loaded; }

	int getBGMaskType(const Point &testPoint) {
		uint offset;
		if (!_bgMask.loaded) {
			return 0;
		}
		offset = testPoint.x + testPoint.y * _bgMask.w;

		#ifdef SCENE_DEBUG
		if (offset >= _bgMask.buf_len) {
			error("Scene::getBGMaskType offset 0x%X exceed bufferLength 0x%X", offset, (int)_bgMask.buf_len);
		}
		#endif

		return (_bgMask.buffer[offset] >> 4) & 0x0f;
	}

	bool validBGMaskPoint(const Point &testPoint) {
		#ifdef SCENE_DEBUG
		if (!_bgMask.loaded) {
			error("Scene::validBGMaskPoint _bgMask not loaded");
		}
		#endif

		return !((testPoint.x < 0) || (testPoint.x >= _bgMask.w) ||
			(testPoint.y < 0) || (testPoint.y >= _bgMask.h));
	}

	bool canWalk(const Point &testPoint);
	bool offscreenPath(Point &testPoint);

	void setDoorState(int doorNumber, int doorState) {
		#ifdef SCENE_DEBUG
		if ((doorNumber < 0) || (doorNumber >= SCENE_DOORS_MAX))
			error("Scene::setDoorState wrong doorNumber");
		#endif

		_sceneDoors[doorNumber] = doorState;
	}

	int getDoorState(int doorNumber) {
		#ifdef SCENE_DEBUG
		if ((doorNumber < 0) || (doorNumber >= SCENE_DOORS_MAX))
			error("Scene::getDoorState wrong doorNumber");
		#endif

		return _sceneDoors[doorNumber];
	}

	void initDoorsState();

	void getBGInfo(BGInfo &bgInfo);
	void getBGPal(PalEntry *&pal) {
		pal = (PalEntry *)_bg.pal;
	}

	void getSlopes(int &beginSlope, int &endSlope);

	void clearSceneQueue() {
		_sceneQueue.clear();
	}
	void changeScene(int16 sceneNumber, int actorsEntrance, SceneTransitionType transitionType, int chapter = NO_CHAPTER_CHANGE);

	bool isSceneLoaded() const { return _sceneLoaded; }

	uint16 getSceneResourceId(int sceneNumber) {
	#ifdef SCENE_DEBUG
		if ((sceneNumber < 0) || (sceneNumber >= _sceneCount)) {
			error("getSceneResourceId: wrong sceneNumber %i", sceneNumber);
		}
	#endif
		return _sceneLUT[sceneNumber];
	}
	int currentSceneNumber() const { return _sceneNumber; }
	int currentChapterNumber() const { return _chapterNumber; }
	void setChapterNumber(int ch) { _chapterNumber = ch; }
	int getOutsetSceneNumber() const { return _outsetSceneNumber; }
	int currentSceneResourceId() const { return _sceneResourceId; }
	int getCurrentMusicTrack() const { return _currentMusicTrack; }
	void setCurrentMusicTrack(int tr) { _currentMusicTrack = tr; }
	int getCurrentMusicRepeat() const { return _currentMusicRepeat; }
	void setCurrentMusicRepeat(int rp) { _currentMusicRepeat = rp; }
	bool haveChapterPointsChanged() const { return _chapterPointsChanged; }
	void setChapterPointsChanged(bool cp) { _chapterPointsChanged = cp; }

	void cutawaySkip() {
		_vm->_framesEsc = _vm->_scene->isInIntro() ? 2 : 1;
	}

	void drawTextList();

	int getHeight(bool speech = false) const {
		if (_vm->getGameId() == GID_IHNM && _vm->_scene->currentChapterNumber() == 8 && !speech)
			return _vm->getDisplayInfo().height;
		else
			return _vm->getDisplayInfo().sceneHeight;
	}

	void clearPlacard();
	void showPsychicProfile(const char *text);
	void clearPsychicProfile();
	void showIHNMDemoSpecialScreen();

	bool isNonInteractiveIHNMDemoPart() {
		return _vm->isIHNMDemo() && (_sceneNumber >= 144 && _sceneNumber <= 149);
	}

	bool isITEPuzzleScene() {
		return _vm->getGameId() == GID_ITE && _vm->_puzzle->isActive();
	}

 private:
	void loadScene(LoadSceneParams &loadSceneParams);
	void loadSceneDescriptor(uint32 resourceId);
	void loadSceneResourceList(uint32 resourceId, SceneResourceDataArray &resourceList);
	void loadSceneEntryList(const ByteArray &resourceData);
	void processSceneResources(SceneResourceDataArray &resourceList, SceneLoadFlags flags);
	void getResourceTypes(SAGAResourceTypes *&types, int &typesCount);


	SagaEngine *_vm;

	ResourceContext *_sceneContext;
	Common::Array<uint16> _sceneLUT;
	SceneQueueList _sceneQueue;
	bool _sceneLoaded;
	int _sceneNumber;
	int _chapterNumber;
	int _outsetSceneNumber;
	int _sceneResourceId;
	int _currentMusicTrack;
	int _currentMusicRepeat;
	bool _chapterPointsChanged;
	bool _inGame;
	SceneDescription _sceneDescription;
	SceneProc *_sceneProc;
	SceneImage _bg;
	SceneImage _bgMask;
	Common::Rect _sceneClip;

	int _sceneDoors[SCENE_DOORS_MAX];


 public:
	ObjectMap *_actionMap;
	ObjectMap *_objectMap;
	SceneEntryList _entryList;
	StringsTable _sceneStrings;
	TextList _textList;

 private:
	int ITEStartProc();
	int IHNMStartProc();
	int IHNMCreditsProc();

	void IHNMLoadCutaways();
	bool checkKey();
	void fadeMusic();

	bool playTitle(int title, int time, int mode = kPanelVideo);
	bool playLoopingTitle(int title, int seconds);

 public:
	static int SC_ITEIntroAnimProc(int param, void *refCon);
	static int SC_ITEIntroCave1Proc(int param, void *refCon);
	static int SC_ITEIntroCave2Proc(int param, void *refCon);
	static int SC_ITEIntroCave3Proc(int param, void *refCon);
	static int SC_ITEIntroCave4Proc(int param, void *refCon);
	static int SC_ITEIntroValleyProc(int param, void *refCon);
	static int SC_ITEIntroTreeHouseProc(int param, void *refCon);
	static int SC_ITEIntroFairePathProc(int param, void *refCon);
	static int SC_ITEIntroFaireTentProc(int param, void *refCon);
	static int SC_ITEIntroCaveDemoProc(int param, void *refCon);

 private:
	EventColumns *queueIntroDialogue(EventColumns *eventColumns, int n_dialogues, const IntroDialogue dialogue[]);
	EventColumns *queueCredits(int delta_time, int duration, int n_credits, const IntroCredit credits[]);
	int ITEIntroAnimProc(int param);
	int ITEIntroCaveCommonProc(int param, int caveScene);
	int ITEIntroCaveDemoProc(int param);
	int ITEIntroValleyProc(int param);
	int ITEIntroTreeHouseProc(int param);
	int ITEIntroFairePathProc(int param);
	int ITEIntroFaireTentProc(int param);
};

} // End of namespace Saga

#endif
