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

/*
 * This code is based on Labyrinth of Time code with assistance of
 *
 * Copyright (c) 1993 Terra Nova Development
 * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
 *
 */

#include "common/savefile.h"
#include "common/translation.h"

#include "gui/message.h"
#include "gui/saveload.h"

#include "graphics/thumbnail.h"
#include "engines/savestate.h"

#include "lab/lab.h"
#include "lab/dispman.h"
#include "lab/eventman.h"
#include "lab/labsets.h"
#include "lab/music.h"
#include "lab/processroom.h"
#include "lab/speciallocks.h"

namespace Lab {

#define SAVEGAME_ID       MKTAG('L', 'O', 'T', 'S')
#define SAVEGAME_VERSION  1

void LabEngine::writeSaveGameHeader(Common::OutSaveFile *out, const Common::String &saveName) {
	out->writeUint32BE(SAVEGAME_ID);

	// Write version
	out->writeByte(SAVEGAME_VERSION);

	// Write savegame name
	out->writeString(saveName);
	out->writeByte(0);

	// Save the game thumbnail
	Graphics::saveThumbnail(*out);

	// Creation date/time
	TimeDate curTime;
	_system->getTimeAndDate(curTime);

	uint32 saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF);
	uint16 saveTime = ((curTime.tm_hour & 0xFF) << 8) | ((curTime.tm_min) & 0xFF);
	uint32 playTime = getTotalPlayTime() / 1000;

	out->writeUint32BE(saveDate);
	out->writeUint16BE(saveTime);
	out->writeUint32BE(playTime);
}

WARN_UNUSED_RESULT bool readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header, bool skipThumbnail) {
	uint32 id = in->readUint32BE();

	// Check if it's a valid ScummVM savegame
	if (id != SAVEGAME_ID)
		return false;

	// Read in the version
	header._version = in->readByte();

	// Check that the save version isn't newer than this binary
	if (header._version > SAVEGAME_VERSION)
		return false;

	// Read in the save name
	Common::String saveName;
	char ch;
	while ((ch = (char)in->readByte()) != '\0')
		saveName += ch;
	header._descr.setDescription(saveName);

	// Get the thumbnail
	Graphics::Surface *thumbnail = nullptr;
	if (!Graphics::loadThumbnail(*in, thumbnail, skipThumbnail)) {
		return false;
	}

	header._descr.setThumbnail(thumbnail);

	uint32 saveDate = in->readUint32BE();
	uint16 saveTime = in->readUint16BE();
	uint32 playTime = in->readUint32BE();

	int day = (saveDate >> 24) & 0xFF;
	int month = (saveDate >> 16) & 0xFF;
	int year = saveDate & 0xFFFF;
	header._descr.setSaveDate(year, month, day);

	int hour = (saveTime >> 8) & 0xFF;
	int minutes = saveTime & 0xFF;
	header._descr.setSaveTime(hour, minutes);

	header._descr.setPlayTime(playTime * 1000);
	if (g_engine)
		g_engine->setTotalPlayTime(playTime * 1000);

	return true;
}

bool LabEngine::saveGame(int slot, const Common::String desc) {
	Common::String fileName = getSaveStateName(slot);
	Common::SaveFileManager *saveFileManager = _system->getSavefileManager();
	Common::OutSaveFile *file = saveFileManager->openForSaving(fileName);

	if (!file)
		return false;

	// Load scene pic
	_graphics->readPict(getPictName(false));


	writeSaveGameHeader(file, desc);
	file->writeUint16LE(_roomNum);
	file->writeUint16LE(getDirection());
	file->writeUint16LE(getQuarters());

	// Conditions
	for (int i = 0; i < _conditions->_lastElement / (8 * 2); i++)
		file->writeUint16LE(_conditions->_array[i]);

	// Rooms found
	for (int i = 0; i < _roomsFound->_lastElement / (8 * 2); i++)
		file->writeUint16LE(_roomsFound->_array[i]);

	_specialLocks->save(file);

	// Breadcrumbs
	for (uint i = 0; i < MAX_CRUMBS; i++) {
		file->writeUint16LE(_breadCrumbs[i]._crumbRoomNum);
		file->writeUint16LE(_breadCrumbs[i]._crumbDirection);
	}

	file->flush();
	file->finalize();
	delete file;

	_mainDisplay = true;
	_alternate = false;
	_event->simulateEvent();
	_graphics->screenUpdate();

	return true;
}

bool LabEngine::loadGame(int slot) {
	Common::String fileName = getSaveStateName(slot);
	Common::SaveFileManager *saveFileManager = _system->getSavefileManager();
	Common::InSaveFile *file = saveFileManager->openForLoading(fileName);

	if (!file)
		return false;

	SaveGameHeader header;
	if (!readSaveGameHeader(file, header)) {
		delete file;
		return false;
	}

	_roomNum = file->readUint16LE();
	_music->checkRoomMusic(1, _roomNum);
	_direction = file->readUint16LE();
	setQuarters(file->readUint16LE());

	// Conditions
	for (int i = 0; i < _conditions->_lastElement / (8 * 2); i++)
		_conditions->_array[i] = file->readUint16LE();

	// Rooms found
	for (int i = 0; i < _roomsFound->_lastElement / (8 * 2); i++)
		_roomsFound->_array[i] = file->readUint16LE();

	_specialLocks->load(file);

	// Breadcrumbs
	for (int i = 0; i < MAX_CRUMBS; i++) {
		_breadCrumbs[i]._crumbRoomNum = file->readUint16LE();
		_breadCrumbs[i]._crumbDirection = file->readUint16LE();
	}

	_droppingCrumbs = (_breadCrumbs[0]._crumbRoomNum != 0);
	_followingCrumbs = false;

	for (int i = 0; i < MAX_CRUMBS; i++) {
		if (_breadCrumbs[i]._crumbRoomNum == 0)
			break;
		_numCrumbs = i;
	}

	delete file;

	_curFileName = " ";
	_closeDataPtr = nullptr;
	_followingCrumbs = false;
	_graphics->_longWinInFront = false;
	_event->initMouse();

	_mainDisplay = true;
	_alternate = false;
	_event->simulateEvent();
	_graphics->screenUpdate();

	return true;
}

bool LabEngine::saveRestoreGame() {
	bool isOK = false;

	// The original had one screen for saving/loading. We have two.
	// Ask the user which screen to use.
	GUI::MessageDialog saveOrLoad(_("Would you like to save or restore a game?"), _("Save"), _("Restore"));

	int choice = saveOrLoad.runModal();
	if (choice == GUI::kMessageOK) {
		// Save
		GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
		int slot = dialog->runModalWithCurrentTarget();
		if (slot >= 0) {
			Common::String desc = dialog->getResultString();

			if (desc.empty()) {
				// create our own description for the saved game, the user didn't enter it
				desc = dialog->createDefaultSaveDescription(slot);
			}

			isOK = saveGame(slot, desc);
		}
		delete dialog;
	} else {
		// Restore
		GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
		int slot = dialog->runModalWithCurrentTarget();
		if (slot >= 0) {
			isOK = loadGame(slot);
		}
		delete dialog;
	}

	return isOK;
}

} // End of namespace Lab
