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

#include "lastexpress/shared.h"

#include "common/rect.h"
#include "common/serializer.h"
#include "common/system.h"
#include "common/textconsole.h"

namespace LastExpress {

class LastExpressEngine;

class Inventory;
class Objects;
class SavePoints;

class State {
public:
	struct GameProgress : public Common::Serializable {
		uint32 field_0;
		JacketType jacket;
		bool eventCorpseMovedFromFloor;
		uint32 field_C;
		bool eventCorpseFound;
		uint32 field_14;                  ///< EntityIndex (used in Gendarmes)
		uint32 field_18;
		uint32 portrait;
		bool eventCorpseThrown;
		uint32 field_24;
		uint32 field_28;
		ChapterIndex chapter;
		uint32 field_30;
		bool eventMetAugust;
		bool isNightTime;
		uint32 field_3C;
		uint32 field_40;
		uint32 field_44;
		uint32 field_48;
		uint32 field_4C;
		bool isTrainRunning;
		uint32 field_54;
		uint32 field_58;
		uint32 field_5C;
		uint32 field_60;
		uint32 field_64;
		uint32 field_68;
		bool eventMertensAugustWaiting;
		bool eventMertensKronosInvitation;
		bool isEggOpen;
		uint32 field_78;	// time?
		uint32 field_7C;
		uint32 field_80;
		uint32 field_84;
		uint32 field_88;
		uint32 field_8C;
		uint32 field_90;
		uint32 field_94;
		uint32 field_98;
		uint32 field_9C;
		uint32 field_A0;
		uint32 field_A4;
		uint32 field_A8;
		uint32 field_AC;
		uint32 field_B0;
		uint32 field_B4;
		uint32 field_B8;
		uint32 field_BC;
		uint32 field_C0;
		uint32 field_C4;
		uint32 field_C8;
		uint32 field_CC;
		bool eventMetBoutarel;
		bool eventMetHadija;
		bool eventMetYasmin;
		uint32 field_DC;
		uint32 field_E0;
		uint32 field_E4;
		uint32 field_E8;
		uint32 field_EC;
		uint32 field_F0;
		uint32 field_F4;
		uint32 field_F8;
		uint32 field_FC;
		uint32 field_100;
		uint32 field_104;
		uint32 field_108;
		uint32 field_10C;
		uint32 field_110;
		uint32 field_114;
		uint32 field_118;
		uint32 field_11C;
		uint32 field_120;
		uint32 field_124;
		uint32 field_128;
		uint32 field_12C;
		uint32 field_130;
		uint32 field_134;
		uint32 field_138;
		uint32 field_13C;
		uint32 field_140;
		uint32 field_144;
		uint32 field_148;
		uint32 field_14C;
		uint32 field_150;
		uint32 field_154;
		uint32 field_158;
		uint32 field_15C;
		uint32 field_160;
		uint32 field_164;
		uint32 field_168;
		uint32 field_16C;
		uint32 field_170;
		uint32 field_174;
		uint32 field_178;
		uint32 field_17C;
		uint32 field_180;
		uint32 field_184;
		uint32 field_188;
		uint32 field_18C;
		uint32 field_190;
		uint32 field_194;
		uint32 field_198;
		uint32 field_19C;
		uint32 field_1A0;
		uint32 field_1A4;
		uint32 field_1A8;
		uint32 field_1AC;
		uint32 field_1B0;
		uint32 field_1B4;
		uint32 field_1B8;
		uint32 field_1BC;
		uint32 field_1C0;
		uint32 field_1C4;
		uint32 field_1C8;
		uint32 field_1CC;
		uint32 field_1D0;
		uint32 field_1D4;
		uint32 field_1D8;
		uint32 field_1DC;
		uint32 field_1E0;
		uint32 field_1E4;
		uint32 field_1E8;
		uint32 field_1EC;
		uint32 field_1F0;
		uint32 field_1F4;
		uint32 field_1F8;
		uint32 field_1FC;

		GameProgress() {
			field_0 = 0;
			jacket = kJacketOriginal;
			eventCorpseMovedFromFloor = false;
			field_C = 0;
			eventCorpseFound = false;
			field_14 = 0;                             // 5
			field_18 = 0;
			portrait = _defaultPortrait;
			eventCorpseThrown = false;
			field_24 = 0;
			field_28 = 0;                             // 10
			chapter = kChapter1;
			field_30 = 0;
			eventMetAugust = false;
			isNightTime = false;
			field_3C = 0;                             // 15
			field_40 = 0;
			field_44 = 0;
			field_48 = 0;
			field_4C = 0;
			isTrainRunning = false;                   // 20
			field_54 = 0;
			field_58 = 0;
			field_5C = 0;
			field_60 = 0;
			field_64 = 0;                             // 25
			field_68 = 0;
			eventMertensAugustWaiting = false;
			eventMertensKronosInvitation = false;
			isEggOpen = false;
			field_78 = 0;                             // 30
			field_7C = 0;
			field_80 = 0;
			field_84 = 0;
			field_88 = 0;
			field_8C = 0;                             // 35
			field_90 = 0;
			field_94 = 0;
			field_98 = 0;
			field_9C = 0;
			field_A0 = 0;                             // 40
			field_A4 = 0;
			field_A8 = 0;
			field_AC = 0;
			field_B0 = 0;
			field_B4 = 0;                             // 45
			field_B8 = 0;
			field_BC = 0;
			field_C0 = 0;
			field_C4 = 0;
			field_C8 = 0;                             // 50
			field_CC = 0;
			eventMetBoutarel = false;
			eventMetHadija = false;
			eventMetYasmin = false;
			field_DC = 0;                             // 55
			field_E0 = 0;
			field_E4 = 0;
			field_E8 = 0;
			field_EC = 0;
			field_F0 = 0;                             // 60
			field_F4 = 0;
			field_F8 = 0;
			field_FC = 0;
			field_100 = 0;
			field_104 = 0;                            // 65
			field_108 = 0;
			field_10C = 0;
			field_110 = 0;
			field_114 = 0;
			field_118 = 0;                            // 70
			field_11C = 0;
			field_120 = 0;
			field_124 = 0;
			field_128 = 0;
			field_12C = 0;                            // 75
			field_130 = 0;
			field_134 = 0;
			field_138 = 0;
			field_13C = 0;
			field_140 = 0;                            // 80
			field_144 = 0;
			field_148 = 0;
			field_14C = 0;
			field_150 = 0;
			field_154 = 0;                            // 85
			field_158 = 0;
			field_15C = 0;
			field_160 = 0;
			field_164 = 0;
			field_168 = 0;                            // 90
			field_16C = 0;
			field_170 = 0;
			field_174 = 0;
			field_178 = 0;
			field_17C = 0;                            // 95
			field_180 = 0;
			field_184 = 0;
			field_188 = 0;
			field_18C = 0;
			field_190 = 0;                            // 100
			field_194 = 0;
			field_198 = 0;
			field_19C = 0;
			field_1A0 = 0;
			field_1A4 = 0;                            // 105
			field_1A8 = 0;
			field_1AC = 0;
			field_1B0 = 0;
			field_1B4 = 0;
			field_1B8 = 0;                            // 110
			field_1BC = 0;
			field_1C0 = 0;
			field_1C4 = 0;
			field_1C8 = 0;
			field_1CC = 0;                            // 115
			field_1D0 = 0;
			field_1D4 = 0;
			field_1D8 = 0;
			field_1DC = 0;
			field_1E0 = 0;                            // 120
			field_1E4 = 0;
			field_1E8 = 0;
			field_1EC = 0;
			field_1F0 = 0;
			field_1F4 = 0;                            // 125
			field_1F8 = 0;
			field_1FC = 0;
		}

		/**
		 * Query if if a progress value is equal to the specified value.
		 *
		 * Note: This is necessary because we store different types in the progress structure
		 *       and need to test a value based on an index in Action::getCursor()
		 *
		 * @param index Zero-based index of the progress structure entry
		 * @param val   The value.
		 *
		 * @return true if equal, false if not.
		 */
		bool isEqual(uint index, uint val) {
			return getOrSetValueName(index) == val;
		}

		uint32 getOrSetValueName(uint index, Common::String *name = NULL, const uint32* newValue = NULL) {
			#define EXPOSE_VALUE(idx, entryName, fieldType) \
				case idx: { \
					if (name) (*name) = "" #entryName; \
					if (newValue) {entryName = (fieldType)*newValue;} \
					return (uint32)entryName; \
				}

			switch (index) {
			default:
				error("[GameProgress::getValueName] Invalid index value (was: %d, max:127)", index);
				break;

			EXPOSE_VALUE(0, field_0, uint32);
			EXPOSE_VALUE(1, jacket, JacketType);
			EXPOSE_VALUE(2, eventCorpseMovedFromFloor, bool);
			EXPOSE_VALUE(3, field_C, uint32);
			EXPOSE_VALUE(4, eventCorpseFound, bool);
			EXPOSE_VALUE(5, field_14, uint32);
			EXPOSE_VALUE(6, field_18, uint32);
			EXPOSE_VALUE(7, portrait, uint32);
			EXPOSE_VALUE(8, eventCorpseThrown, bool);
			EXPOSE_VALUE(9, field_24, uint32);
			EXPOSE_VALUE(10, field_28, uint32);
			EXPOSE_VALUE(11, chapter, ChapterIndex);
			EXPOSE_VALUE(12, field_30, uint32);
			EXPOSE_VALUE(13, eventMetAugust, bool);
			EXPOSE_VALUE(14, isNightTime, bool);
			EXPOSE_VALUE(15, field_3C, uint32);
			EXPOSE_VALUE(16, field_40, uint32);
			EXPOSE_VALUE(17, field_44, uint32);
			EXPOSE_VALUE(18, field_48, uint32);
			EXPOSE_VALUE(19, field_4C, uint32);
			EXPOSE_VALUE(20, isTrainRunning, bool);
			EXPOSE_VALUE(21, field_54, uint32);
			EXPOSE_VALUE(22, field_58, uint32);
			EXPOSE_VALUE(23, field_5C, uint32);
			EXPOSE_VALUE(24, field_60, uint32);
			EXPOSE_VALUE(25, field_64, uint32);
			EXPOSE_VALUE(26, field_68, uint32);
			EXPOSE_VALUE(27, eventMertensAugustWaiting, bool);
			EXPOSE_VALUE(28, eventMertensKronosInvitation, bool);
			EXPOSE_VALUE(29, isEggOpen, bool);
			EXPOSE_VALUE(30, field_78, uint32);
			EXPOSE_VALUE(31, field_7C, uint32);
			EXPOSE_VALUE(32, field_80, uint32);
			EXPOSE_VALUE(33, field_84, uint32);
			EXPOSE_VALUE(34, field_88, uint32);
			EXPOSE_VALUE(35, field_8C, uint32);
			EXPOSE_VALUE(36, field_90, uint32);
			EXPOSE_VALUE(37, field_94, uint32);
			EXPOSE_VALUE(38, field_98, uint32);
			EXPOSE_VALUE(39, field_9C, uint32);
			EXPOSE_VALUE(40, field_A0, uint32);
			EXPOSE_VALUE(41, field_A4, uint32);
			EXPOSE_VALUE(42, field_A8, uint32);
			EXPOSE_VALUE(43, field_AC, uint32);
			EXPOSE_VALUE(44, field_B0, uint32);
			EXPOSE_VALUE(45, field_B4, uint32);
			EXPOSE_VALUE(46, field_B8, uint32);
			EXPOSE_VALUE(47, field_BC, uint32);
			EXPOSE_VALUE(48, field_C0, uint32);
			EXPOSE_VALUE(49, field_C4, uint32);
			EXPOSE_VALUE(50, field_C8, uint32);
			EXPOSE_VALUE(51, field_CC, uint32);
			EXPOSE_VALUE(52, eventMetBoutarel, bool);
			EXPOSE_VALUE(53, eventMetHadija, bool);
			EXPOSE_VALUE(54, eventMetYasmin, bool);
			EXPOSE_VALUE(55, field_DC, uint32);
			EXPOSE_VALUE(56, field_E0, uint32);
			EXPOSE_VALUE(57, field_E4, uint32);
			EXPOSE_VALUE(58, field_E8, uint32);
			EXPOSE_VALUE(59, field_EC, uint32);
			EXPOSE_VALUE(60, field_F0, uint32);
			EXPOSE_VALUE(61, field_F4, uint32);
			EXPOSE_VALUE(62, field_F8, uint32);
			EXPOSE_VALUE(63, field_FC, uint32);
			EXPOSE_VALUE(64, field_100, uint32);
			EXPOSE_VALUE(65, field_104, uint32);
			EXPOSE_VALUE(66, field_108, uint32);
			EXPOSE_VALUE(67, field_10C, uint32);
			EXPOSE_VALUE(68, field_110, uint32);
			EXPOSE_VALUE(69, field_114, uint32);
			EXPOSE_VALUE(70, field_118, uint32);
			EXPOSE_VALUE(71, field_11C, uint32);
			EXPOSE_VALUE(72, field_120, uint32);
			EXPOSE_VALUE(73, field_124, uint32);
			EXPOSE_VALUE(74, field_128, uint32);
			EXPOSE_VALUE(75, field_12C, uint32);
			EXPOSE_VALUE(76, field_130, uint32);
			EXPOSE_VALUE(77, field_134, uint32);
			EXPOSE_VALUE(78, field_138, uint32);
			EXPOSE_VALUE(79, field_13C, uint32);
			EXPOSE_VALUE(80, field_140, uint32);
			EXPOSE_VALUE(81, field_144, uint32);
			EXPOSE_VALUE(82, field_148, uint32);
			EXPOSE_VALUE(83, field_14C, uint32);
			EXPOSE_VALUE(84, field_150, uint32);
			EXPOSE_VALUE(85, field_154, uint32);
			EXPOSE_VALUE(86, field_158, uint32);
			EXPOSE_VALUE(87, field_15C, uint32);
			EXPOSE_VALUE(88, field_160, uint32);
			EXPOSE_VALUE(89, field_164, uint32);
			EXPOSE_VALUE(90, field_168, uint32);
			EXPOSE_VALUE(91, field_16C, uint32);
			EXPOSE_VALUE(92, field_170, uint32);
			EXPOSE_VALUE(93, field_174, uint32);
			EXPOSE_VALUE(94, field_178, uint32);
			EXPOSE_VALUE(95, field_17C, uint32);
			EXPOSE_VALUE(96, field_180, uint32);
			EXPOSE_VALUE(97, field_184, uint32);
			EXPOSE_VALUE(98, field_188, uint32);
			EXPOSE_VALUE(99, field_18C, uint32);
			EXPOSE_VALUE(100, field_190, uint32);
			EXPOSE_VALUE(101, field_194, uint32);
			EXPOSE_VALUE(102, field_198, uint32);
			EXPOSE_VALUE(103, field_19C, uint32);
			EXPOSE_VALUE(104, field_1A0, uint32);
			EXPOSE_VALUE(105, field_1A4, uint32);
			EXPOSE_VALUE(106, field_1A8, uint32);
			EXPOSE_VALUE(107, field_1AC, uint32);
			EXPOSE_VALUE(108, field_1B0, uint32);
			EXPOSE_VALUE(109, field_1B4, uint32);
			EXPOSE_VALUE(110, field_1B8, uint32);
			EXPOSE_VALUE(111, field_1BC, uint32);
			EXPOSE_VALUE(112, field_1C0, uint32);
			EXPOSE_VALUE(113, field_1C4, uint32);
			EXPOSE_VALUE(114, field_1C8, uint32);
			EXPOSE_VALUE(115, field_1CC, uint32);
			EXPOSE_VALUE(116, field_1D0, uint32);
			EXPOSE_VALUE(117, field_1D4, uint32);
			EXPOSE_VALUE(118, field_1D8, uint32);
			EXPOSE_VALUE(119, field_1DC, uint32);
			EXPOSE_VALUE(120, field_1E0, uint32);
			EXPOSE_VALUE(121, field_1E4, uint32);
			EXPOSE_VALUE(122, field_1E8, uint32);
			EXPOSE_VALUE(123, field_1EC, uint32);
			EXPOSE_VALUE(124, field_1F0, uint32);
			EXPOSE_VALUE(125, field_1F4, uint32);
			EXPOSE_VALUE(126, field_1F8, uint32);
			EXPOSE_VALUE(127, field_1FC, uint32);
			}
		}

		Common::String toString() {
			Common::String ret = "";

			for (uint i = 0; i < 128; i++) {
				Common::String name = "";
				uint val = getOrSetValueName(i, &name, NULL);
				ret += Common::String::format("(%03d) %s = %d\n", i, name.c_str(), val);
			}

			return ret;
		}

		void saveLoadWithSerializer(Common::Serializer &s) {
			if (s.isLoading()) {
				for (uint i = 0; i < 128; i++) {
					uint32 val;
					s.syncAsUint32LE(val);
					getOrSetValueName(i, NULL, &val);
				}
			} else {
				for (uint i = 0; i < 128; i++) {
					uint32 val = getOrSetValueName(i);
					s.syncAsUint32LE(val);
				}
			}
		}
	};

	struct GameState : public Common::Serializable {
		// Header
		uint32 brightness;
		uint32 volume;

		// Game data
		uint32 field_0;
		TimeValue time;
		uint32 timeDelta;
		uint32 timeTicks;
		bool sceneUseBackup;       // byte
		SceneIndex scene;          // uint32
		SceneIndex sceneBackup;    // uint32
		SceneIndex sceneBackup2;   // uin32

		GameProgress progress;
		byte events[512];

		GameState() {
			brightness = _defaultBrigthness;
			volume = _defaultVolume;

			//Game data
			field_0 = 0;
			time = kTimeCityParis;
			timeDelta = _defaultTimeDelta;
			timeTicks = 0;
			sceneUseBackup = false;
			scene = kSceneDefault;
			sceneBackup = kSceneNone;
			sceneBackup2 = kSceneNone;

			// Clear game events
			memset(events, 0, 512*sizeof(byte));
		}

		/**
		 * Convert this object into a string representation.
		 *
		 * @return A string representation of this object.
		 */
		Common::String toString() {
			Common::String ret = "";

			uint8 hours = 0;
			uint8 minutes = 0;
			getHourMinutes(time, &hours, &minutes);

			ret += Common::String::format("Time: %d (%d:%d)   - Time delta: %d    - Ticks: %d\n", time, hours, minutes, timeDelta, timeTicks);
			ret += Common::String::format("Brightness: %d    - Volume: %d    - UseBackup: %d\n", brightness, volume, sceneUseBackup);
			ret += Common::String::format("Scene: %d    - Scene backup: %d    - Scene backup 2: %d\n", scene, sceneBackup, sceneBackup2);

			return ret;
		}

		void saveLoadWithSerializer(Common::Serializer &s) {
			s.syncAsUint32LE(time);
			s.syncAsUint32LE(timeDelta);
			s.syncAsUint32LE(timeTicks);
			s.syncAsUint32LE(scene);
			s.syncAsByte(sceneUseBackup);
			s.syncAsUint32LE(sceneBackup);
			s.syncAsUint32LE(sceneBackup2);
		}

		void syncEvents(Common::Serializer &s) {
			for (uint i = 0; i < ARRAYSIZE(events); i++)
				s.syncAsByte(events[i]);
		}
	};

	struct Flags {
		bool flag_0;
		bool flag_3;
		bool flag_4;
		bool flag_5;

		bool frameInterval;

		bool isGameRunning;

		// Mouse flags
		bool mouseLeftClick;
		bool mouseRightClick;

		bool mouseLeftPressed;
		bool mouseRightPressed;

		bool flag_entities_0;
		bool flag_entities_1;

		bool shouldRedraw;
		bool shouldDrawEggOrHourGlass;

		int32 nis;

		Flags() {
			flag_0 = false;
			flag_3 = false;
			flag_4 = false;
			flag_5 = false;

			frameInterval = false;

			isGameRunning = false;

			mouseRightClick = false;
			mouseLeftClick = false;

			mouseLeftPressed = false;
			mouseRightPressed = false;

			flag_entities_0 = false;
			flag_entities_1 = false;

			shouldRedraw = false;
			shouldDrawEggOrHourGlass = false;

			nis = 0;
		}

		/**
		 * Convert this object into a string representation.
		 *
		 * @return A string representation of this object.
		 */
		Common::String toString() {
			Common::String ret = "";

			ret += Common::String::format("Unknown: 0:%02d  -  3:%02d  -  4:%02d  -  5:%02d\n", flag_0, flag_3, flag_4, flag_5);
			ret += Common::String::format("FrameInterval: %02d  -  ShouldRedraw:%02d  -  ShouldDrawEggOrHourGlass:%02d\n", frameInterval, shouldRedraw, shouldDrawEggOrHourGlass);
			ret += Common::String::format("IsGameRunning: %02d\n", isGameRunning);
			ret += Common::String::format("Mouse: RightClick:%02d  - LeftClick:%02d\n", mouseRightClick, mouseLeftClick);
			ret += Common::String::format("Entities: 0:%02d  -  1:%02d\n", flag_entities_0, flag_entities_1);
			ret += Common::String::format("NIS: %d\n", nis);

			return ret;
		}
	};

	State(LastExpressEngine *engine);
	~State();

	void reset();

	// Accessors
	Inventory  *getGameInventory() { return _inventory; }
	Objects    *getGameObjects() { return _objects; }
	SavePoints *getGameSavePoints() { return _savepoints; }
	GameState  *getGameState() { return _state; }
	Flags      *getGameFlags() { return _flags; }

	// Time checks
	bool isNightTime() const;

	// Timer
	int getTimer() { return _timer; }
	void setTimer(int val) { _timer = val; }

	// Coordinates
	void setCoordinates(Common::Point coords) { _coords = coords; }
	const Common::Point getCoordinates() { return _coords; }

	// Helpers
	static uint32 getPowerOfTwo(uint32 x);
	static void getHourMinutes(uint32 time, uint8 *hours, uint8 *minutes);

private:
	static const uint32 _defaultBrigthness = 3;
	static const uint32 _defaultVolume = 7;
	static const uint32 _defaultTimeDelta = 3;
	static const uint32 _defaultPortrait = 32;

	LastExpressEngine *_engine;

	// Timer
	int _timer;

	Flags *_flags;           ///< Flags
	Inventory *_inventory;   ///< Inventory
	Objects *_objects;       ///< Objects
	SavePoints *_savepoints; ///< SavePoints
	GameState *_state;       ///< State
	Common::Point _coords;   ///< Current coordinates
};

} // End of namespace LastExpress

#endif // LASTEXPRESS_STATE_H
