/* 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.
 *
 */

#include "common/scummsys.h"
#include "common/algorithm.h"
#include "common/memstream.h"
#include "common/substream.h"
#include "common/translation.h"
#include "graphics/scaler.h"
#include "graphics/thumbnail.h"
#include "gui/saveload.h"
#include "xeen/saves.h"
#include "xeen/files.h"
#include "xeen/xeen.h"

namespace Xeen {

SavesManager::SavesManager(const Common::String &targetName): _targetName(targetName),
		_wonWorld(false), _wonDarkSide(false) {
	File::_xeenSave = nullptr;
	File::_darkSave = nullptr;
}

SavesManager::~SavesManager() {
	delete File::_xeenSave;
	delete File::_darkSave;
}

const char *const SAVEGAME_STR = "XEEN";
#define SAVEGAME_STR_SIZE 6

WARN_UNUSED_RESULT bool SavesManager::readSavegameHeader(Common::InSaveFile *in, XeenSavegameHeader &header, bool skipThumbnail) {
	char saveIdentBuffer[SAVEGAME_STR_SIZE + 1];

	// Validate the header Id
	in->read(saveIdentBuffer, SAVEGAME_STR_SIZE + 1);
	if (strncmp(saveIdentBuffer, SAVEGAME_STR, SAVEGAME_STR_SIZE))
		return false;

	header._version = in->readByte();
	if (header._version > XEEN_SAVEGAME_VERSION)
		return false;

	// Read in the string
	header._saveName.clear();
	char ch;
	while ((ch = (char)in->readByte()) != '\0')
		header._saveName += ch;

	// Get the thumbnail
	if (!Graphics::loadThumbnail(*in, header._thumbnail, skipThumbnail)) {
		return false;
	}

	// Read in save date/time
	header._year = in->readSint16LE();
	header._month = in->readSint16LE();
	header._day = in->readSint16LE();
	header._hour = in->readSint16LE();
	header._minute = in->readSint16LE();
	header._totalFrames = in->readUint32LE();

	return true;
}

void SavesManager::writeSavegameHeader(Common::OutSaveFile *out, XeenSavegameHeader &header) {
	EventsManager &events = *g_vm->_events;
	Screen &screen = *g_vm->_screen;

	// Write out a savegame header
	out->write(SAVEGAME_STR, SAVEGAME_STR_SIZE + 1);

	out->writeByte(XEEN_SAVEGAME_VERSION);

	// Write savegame name
	out->writeString(header._saveName);
	out->writeByte('\0');

	// Write a thumbnail of the screen
	uint8 thumbPalette[768];
	screen.getPalette(thumbPalette);
	Graphics::Surface saveThumb;
	::createThumbnail(&saveThumb, (const byte *)screen.getPixels(),
		screen.w, screen.h, thumbPalette);
	Graphics::saveThumbnail(*out, saveThumb);
	saveThumb.free();

	// Write out the save date/time
	TimeDate td;
	g_system->getTimeAndDate(td);
	out->writeSint16LE(td.tm_year + 1900);
	out->writeSint16LE(td.tm_mon + 1);
	out->writeSint16LE(td.tm_mday);
	out->writeSint16LE(td.tm_hour);
	out->writeSint16LE(td.tm_min);
	out->writeUint32LE(events.playTime());
}

Common::Error SavesManager::saveGameState(int slot, const Common::String &desc) {
	Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving(
		generateSaveName(slot));
	if (!out)
		return Common::kCreatingFileFailed;

	// Push map and party data to the save archives
	Map &map = *g_vm->_map;
	map.saveMaze();

	// Write the savegame header
	XeenSavegameHeader header;
	header._saveName = desc;
	writeSavegameHeader(out, header);

	// Loop through saving the sides' save archives
	SaveArchive *archives[2] = { File::_xeenSave, File::_darkSave };
	for (int idx = 0; idx < 2; ++idx) {
		if (archives[idx]) {
			archives[idx]->save(*out);
		} else {
			// Side isn't present
			out->writeUint32LE(0);
		}
	}

	// Write out miscellaneous
	FileManager &files = *g_vm->_files;
	files.save(*out);

	out->finalize();
	delete out;

	return Common::kNoError;
}

Common::Error SavesManager::loadGameState(int slot) {
	Combat &combat = *g_vm->_combat;
	EventsManager &events = *g_vm->_events;
	FileManager &files = *g_vm->_files;
	Map &map = *g_vm->_map;
	Party &party = *g_vm->_party;

	Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(
		generateSaveName(slot));
	if (!saveFile)
		return Common::kReadingFailed;

	// Load the savaegame header
	XeenSavegameHeader header;
	if (!readSavegameHeader(saveFile, header))
		error("Invalid savegame");

	// Set the total play time
	events.setPlayTime(header._totalFrames);

	// Loop through loading the sides' save archives
	SaveArchive *archives[2] = { File::_xeenSave, File::_darkSave };
	for (int idx = 0; idx < 2; ++idx) {
		uint fileSize = saveFile->readUint32LE();

		if (archives[idx]) {
			if (fileSize) {
				Common::SeekableSubReadStream arcStream(saveFile, saveFile->pos(),
					saveFile->pos() + fileSize);
				archives[idx]->load(arcStream);
			} else {
				archives[idx]->reset((idx == 1) ? File::_darkCc : File::_xeenCc);
			}
		} else {
			assert(!fileSize);
		}
	}

	// Read in miscellaneous
	files.load(*saveFile);

	// Load the character roster and party
	File::_currentSave->loadParty();

	// Reset any combat information from the previous game
	combat.reset();
	party._treasure.reset();

	// Load the new map
	map.clearMaze();
	map._loadCcNum = files._ccNum;
	map.load(party._mazeId);

	delete saveFile;
	return Common::kNoError;
}

Common::String SavesManager::generateSaveName(int slot) {
	return Common::String::format("%s.%03d", _targetName.c_str(), slot);
}

void SavesManager::newGame() {
	delete File::_xeenSave;
	delete File::_darkSave;
	File::_xeenSave = nullptr;
	File::_darkSave = nullptr;

	// Reset any combat information from the previous game
	g_vm->_combat->reset();

	// Reset the game states
	if (g_vm->getGameID() != GType_Clouds) {
		File::_darkSave = new SaveArchive(g_vm->_party);
		File::_darkSave->reset(File::_darkCc);
	}
	if (g_vm->getGameID() != GType_DarkSide && g_vm->getGameID() != GType_Swords) {
		File::_xeenSave = new SaveArchive(g_vm->_party);
		File::_xeenSave->reset(File::_xeenCc);
	}

	File::_currentSave = g_vm->getGameID() == GType_DarkSide || g_vm->getGameID() == GType_Swords ?
		File::_darkSave : File::_xeenSave;
	assert(File::_currentSave);

	// Load the character roster and party
	File::_currentSave->loadParty();

	// Set any final initial values
	Party &party = *g_vm->_party;
	party.resetBlacksmithWares();
	party._totalTime = 0;

	switch (g_vm->getGameID()) {
	case GType_Swords:
		party._year = 1050;
		break;
	case GType_DarkSide:
		party._year = 850;
		break;
	default:
		party._year = 610;
		break;
	}
	party._day = 1;
}

bool SavesManager::loadGame() {
	GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Load game:"), _("Load"), false);
	int slotNum = dialog->runModalWithCurrentTarget();
	delete dialog;

	if (slotNum != -1) {
		(void)loadGameState(slotNum);
		g_vm->_interface->drawParty(true);
	}

	return slotNum != -1;
}

bool SavesManager::saveGame() {
	Map &map = *g_vm->_map;

	if (map.mazeData()._mazeFlags & RESTRICTION_SAVE) {
		ErrorScroll::show(g_vm, Res.SAVE_OFF_LIMITS, WT_NONFREEZED_WAIT);
		return false;
	} else if (!g_vm->canSaveGameStateCurrently()) {
		return false;
	} else {
		GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
		int slotNum = dialog->runModalWithCurrentTarget();
		Common::String saveName = dialog->getResultString();
		delete dialog;

		if (slotNum != -1)
			saveGameState(slotNum, saveName);

		return slotNum != -1;
	}
}

void SavesManager::doAutosave() {
	if (saveGameState(kAutoSaveSlot, _("Autosave")).getCode() != Common::kNoError)
		g_vm->GUIError(_("Failed to autosave"));
}

} // End of namespace Xeen