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

/*
	Savegame format
	---------------

	header: 32 bytes
	    uint32 {4}      - signature: 0x12001200
	    uint32 {4}      - chapter - needs to be [0; 5]
	    uint32 {4}      - time - needs to be >= 32 [1061100; timeMax]
	    uint32 {4}      - ?? needs to be >= 32
	    uint32 {4}      - ?? needs to be = 1
	    uint32 {4}      - Brightness (needs to be [0-6])
	    uint32 {4}      - Volume (needs to be [0-7])
	    uint32 {4}      - ?? needs to be = 9

	Game data Format
	-----------------

	uint32 {4}      - entity
	uint32 {4}      - current time
	uint32 {4}      - time delta (how much a tick is in "real" time)
	uint32 {4}      - time ticks
	uint32 {4}      - scene Index               max: 2500
	byte {1}        - use backup scene
	uint32 {4}      - backup Scene Index 1      max: 2500
	uint32 {4}      - backup Scene Index 2      max: 2500
	uint32 {4}      - selected inventory item   max: 32
	uint32 {4*100*10} - positions (by car)
	uint32 {4*16}   - compartments
	uint32 {4*16}   - compartments ??
	uint32 {4*128}  - game progress
	byte {512}      - game events
	byte {7*32}     - inventory
	byte {5*128}    - objects
	byte {1262*40}  - entities (characters and train entities)

	uint32 {4}      - sound queue state
	uint32 {4}      - ??
	uint32 {4}      - number of sound entries
	byte {count*68} - sound entries

	byte {16*128}   - save point data
	uint32 {4}      - number of save points (max: 128)
	byte {count*16} - save points

	... more unknown stuff

*/

#include "lastexpress/shared.h"

#include "common/savefile.h"
#include "common/serializer.h"
#include "common/memstream.h"

namespace LastExpress {

// Savegame signatures
#define SAVEGAME_SIGNATURE       0x12001200
#define SAVEGAME_ENTRY_SIGNATURE 0xE660E660

class LastExpressEngine;

class SaveLoad {
public:
	SaveLoad(LastExpressEngine *engine);
	~SaveLoad();

	// Init
	void create(GameId id);
	void clear(bool clearStream = false);
	uint32 init(GameId id, bool resetHeaders);

	// Save & Load
	void loadGame(GameId id);
	void loadGame(GameId id, uint32 index);
	void saveGame(SavegameType type, EntityIndex entity, uint32 value);

	void loadVolumeBrightness();
	void saveVolumeBrightness();

	// Getting information
	static bool isSavegamePresent(GameId id);
	static bool isSavegameValid(GameId id);

	bool isGameFinished(uint32 menuIndex, uint32 savegameIndex);

	// Accessors
 	uint32       getTime(uint32 index) { return getEntry(index)->time; }
	ChapterIndex getChapter(uint32 index) { return getEntry(index)->chapter; }
	uint32       getValue(uint32 index) { return getEntry(index)->value; }
	uint32       getLastSavegameTicks() const { return _gameTicksLastSavegame; }

private:
	class SavegameStream : public Common::MemoryWriteStreamDynamic, public Common::SeekableReadStream {
	public:
		SavegameStream() : MemoryWriteStreamDynamic(DisposeAfterUse::YES),
		 _eos(false) {}

		int32 pos() const { return MemoryWriteStreamDynamic::pos(); }
		int32 size() const { return MemoryWriteStreamDynamic::size(); }
		bool seek(int32 offset, int whence = SEEK_SET) { return MemoryWriteStreamDynamic::seek(offset, whence); }
		bool eos() const { return _eos; }
		uint32 read(void *dataPtr, uint32 dataSize) {
			if ((int32)dataSize > size() - pos()) {
				dataSize = size() - pos();
				_eos = true;
			}
			memcpy(dataPtr, getData() + pos(), dataSize);

			seek(dataSize, SEEK_CUR);

			return dataSize;
		}
	private:
		bool _eos;
	};

	LastExpressEngine *_engine;

	struct SavegameMainHeader : Common::Serializable {
		uint32 signature;
		uint32 count;
		uint32 offset;
		uint32 offsetEntry;
		uint32 keepIndex;
		int32 brightness;
		int32 volume;
		uint32 field_1C;

		SavegameMainHeader() {
			signature = SAVEGAME_SIGNATURE;
			count = 0;
			offset = 32;
			offsetEntry = 32;
			keepIndex = 0;
			brightness = 3;
			volume = 7;
			field_1C = 9;
		}

		void saveLoadWithSerializer(Common::Serializer &s) {
			s.syncAsUint32LE(signature);
			s.syncAsUint32LE(count);
			s.syncAsUint32LE(offset);
			s.syncAsUint32LE(offsetEntry);
			s.syncAsUint32LE(keepIndex);
			s.syncAsUint32LE(brightness);
			s.syncAsUint32LE(volume);
			s.syncAsUint32LE(field_1C);
		}

		bool isValid() {
			if (signature != SAVEGAME_SIGNATURE)
				return false;

			/* Check not needed as it can never be < 0
			if (header.chapter < 0)
				return false;*/

			if (offset < 32)
				return false;

			if (offsetEntry < 32)
				return false;

			if (keepIndex != 1 && keepIndex != 0)
				return false;

			if (brightness < 0 || brightness > 6)
				return false;

			if (volume < 0 || volume > 7)
				return false;

			if (field_1C != 9)
				return false;

			return true;
		}
	};

	struct SavegameEntryHeader : Common::Serializable {
		uint32 signature;
		SavegameType type;
		uint32 time;
		int offset;
		ChapterIndex chapter;
		uint32 value;
		int field_18;
		int field_1C;

		SavegameEntryHeader() {
			signature = SAVEGAME_ENTRY_SIGNATURE;
			type = kSavegameTypeIndex;
			time = kTimeNone;
			offset = 0;
			chapter = kChapterAll;
			value = 0;
			field_18 = 0;
			field_1C = 0;
		}

		void saveLoadWithSerializer(Common::Serializer &s) {
			s.syncAsUint32LE(signature);
			s.syncAsUint32LE(type);
			s.syncAsUint32LE(time);
			s.syncAsUint32LE(offset);
			s.syncAsUint32LE(chapter);
			s.syncAsUint32LE(value);
			s.syncAsUint32LE(field_18);
			s.syncAsUint32LE(field_1C);
		}

		bool isValid() {
			if (signature != SAVEGAME_ENTRY_SIGNATURE)
				return false;

			if (type < kSavegameTypeTime || type > kSavegameTypeTickInterval)
				return false;

			if (time < kTimeStartGame || time > kTimeCityConstantinople)
				return false;

			if (offset <= 0 || offset & 15)
				return false;

			/* No check for < 0, as it cannot happen normaly */
			if (chapter == 0)
				return false;

			return true;
		}
	};

	SavegameStream *_savegame;
	Common::Array<SavegameEntryHeader *> _gameHeaders;
	uint32 _gameTicksLastSavegame;

	// Headers
	static bool loadMainHeader(Common::InSaveFile *stream, SavegameMainHeader *header);

	// Entries
	void writeEntry(SavegameType type, EntityIndex entity, uint32 val);
	void readEntry(SavegameType *type, EntityIndex *entity, uint32 *val, bool keepIndex);

	SavegameEntryHeader *getEntry(uint32 index);

	// Opening save files
	static Common::String getFilename(GameId id);
	static Common::InSaveFile  *openForLoading(GameId id);
	static Common::OutSaveFile *openForSaving(GameId id);

	// Savegame stream
	void initStream();
	void loadStream(GameId id);
	void flushStream(GameId id);
};

} // End of namespace LastExpress

#endif // LASTEXPRESS_SAVELOAD_H