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

/*
 * This code is based on original Sfinx source code
 * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon
 */

#include "common/memstream.h"
#include "common/savefile.h"
#include "common/system.h"
#include "graphics/thumbnail.h"
#include "graphics/surface.h"
#include "graphics/palette.h"
#include "graphics/scaler.h"
#include "cge2/events.h"
#include "cge2/snail.h"
#include "cge2/hero.h"
#include "cge2/text.h"

namespace CGE2 {

#define kSavegameCheckSum (1997 + _now + _sex + kWorldHeight)
#define kBadSVG           99

bool CGE2Engine::canSaveGameStateCurrently() {
	return (_gamePhase == kPhaseInGame) && _mouse->_active &&
		_commandHandler->idle() && (_soundStat._wait == nullptr);
}

Common::Error CGE2Engine::saveGameState(int slot, const Common::String &desc) {
	storeHeroPos();
	saveGame(slot, desc);
	sceneUp(_now);
	return Common::kNoError;
}

void CGE2Engine::saveGame(int slotNumber, const Common::String &desc) {
	// Set up the serializer
	Common::String slotName = generateSaveName(slotNumber);
	Common::OutSaveFile *saveFile = g_system->getSavefileManager()->openForSaving(slotName);

	// Write out the ScummVM savegame header
	SavegameHeader header;
	header.saveName = desc;
	header.version = kSavegameVersion;
	writeSavegameHeader(saveFile, header);

	// Write out the data of the savegame
	sceneDown();
	syncGame(nullptr, saveFile);

	// Finish writing out game data
	saveFile->finalize();
	delete saveFile;
}

bool CGE2Engine::canLoadGameStateCurrently() {
	return (_gamePhase == kPhaseInGame) && _mouse->_active;
}

Common::Error CGE2Engine::loadGameState(int slot) {
	_commandHandler->clear();
	_commandHandlerTurbo->clear();
	sceneDown();
	if (!loadGame(slot))
		return Common::kReadingFailed;
	sceneUp(_now);
	initToolbar();
	return Common::kNoError;
}

bool CGE2Engine::loadGame(int slotNumber) {
	Common::MemoryReadStream *readStream;

	// Open up the savegame file
	Common::String slotName = generateSaveName(slotNumber);
	Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(slotName);

	// Read the data into a data buffer
	int size = saveFile->size();
	byte *dataBuffer = (byte *)malloc(size);
	saveFile->read(dataBuffer, size);
	readStream = new Common::MemoryReadStream(dataBuffer, size, DisposeAfterUse::YES);
	delete saveFile;

	// Check to see if it's a ScummVM savegame or not
	char buffer[kSavegameStrSize + 1];
	readStream->read(buffer, kSavegameStrSize + 1);

	if (strncmp(buffer, kSavegameStr, kSavegameStrSize + 1) != 0) {
		delete readStream;
		return false;
	} else {
		SavegameHeader saveHeader;

		if (!readSavegameHeader(readStream, saveHeader)) {
			delete readStream;
			return false;
		}

		g_engine->setTotalPlayTime(saveHeader.playTime * 1000);
	}

	resetGame();

	// Get in the savegame
	syncGame(readStream, nullptr);
	delete readStream;

	loadHeroes();

	return true;
}

void CGE2Engine::resetGame() {
	_busyPtr = nullptr;
	busy(false);
	_spare->clear();
	_vga->_showQ->clear();
	loadScript("CGE.INI", true);
	delete _infoLine;
	_infoLine = new InfoLine(this, kInfoW);
}

void CGE2Engine::writeSavegameHeader(Common::OutSaveFile *out, SavegameHeader &header) {
	// Write out a savegame header
	out->write(kSavegameStr, kSavegameStrSize + 1);

	out->writeByte(kSavegameVersion);

	// Write savegame name
	out->write(header.saveName.c_str(), header.saveName.size() + 1);

	// Get the active palette
	uint8 thumbPalette[256 * 3];
	g_system->getPaletteManager()->grabPalette(thumbPalette, 0, 256);

	// Stop the heroes from moving and redraw them before taking the picture.
	for (int i = 0; i < 2; i++)
		_heroTab[i]->_ptr->park();
	_vga->show();

	// Create a thumbnail and save it
	Graphics::Surface *thumb = new Graphics::Surface();
	Graphics::Surface *s = _vga->_page[0];
	::createThumbnail(thumb, (const byte *)s->getPixels(), kScrWidth, kScrHeight, thumbPalette);
	Graphics::saveThumbnail(*out, *thumb);
	thumb->free();
	delete thumb;

	// 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(g_engine->getTotalPlayTime() / 1000);
}

WARN_UNUSED_RESULT bool CGE2Engine::readSavegameHeader(Common::InSaveFile *in, SavegameHeader &header, bool skipThumbnail) {
	header.version     = 0;
	header.saveName.clear();
	header.thumbnail   = nullptr;
	header.saveYear    = 0;
	header.saveMonth   = 0;
	header.saveDay     = 0;
	header.saveHour    = 0;
	header.saveMinutes = 0;
	header.playTime    = 0;

	// Get the savegame version
	header.version = in->readByte();
	if (header.version > kSavegameVersion)
		return false;

	// Read in the string
	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.saveYear    = in->readSint16LE();
	header.saveMonth   = in->readSint16LE();
	header.saveDay     = in->readSint16LE();
	header.saveHour    = in->readSint16LE();
	header.saveMinutes = in->readSint16LE();

	if (header.version >= 2) {
		header.playTime = in->readUint32LE();
	}


	return true;
}

void CGE2Engine::syncGame(Common::SeekableReadStream *readStream, Common::WriteStream *writeStream) {
	Common::Serializer s(readStream, writeStream);

	// Synchronise header data
	syncHeader(s);

	// Synchronise _spare
	_spare->sync(s);

	if (s.isSaving()) {
		// Save the references of the items in the heroes pockets:
		for (int i = 0; i < 2; i++) {
			for (int j = 0; j < kPocketMax; j++) {
				int ref = _heroTab[i]->_downPocketId[j];
				s.syncAsSint16LE(ref);
			}
		}
	} else {
		// Load items to the pockets
		for (int i = 0; i < 2; i++) {
			for (int j = 0; j < kPocketMax; j++) {
				int ref = 0;
				s.syncAsSint16LE(ref);
				_heroTab[i]->_downPocketId[j] = ref;
			}
		}
	}

	// Heroes' _posTabs
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < kSceneMax; j++) {
			s.syncAsSint16LE(_heroTab[i]->_posTab[j]->x);
			s.syncAsSint16LE(_heroTab[i]->_posTab[j]->y);
		}
	}
}

void CGE2Engine::syncHeader(Common::Serializer &s) {
	s.syncAsUint16LE(_now);
	s.syncAsUint16LE(_sex);
	s.syncAsUint16LE(_vga->_rot._len);
	s.syncAsUint16LE(_waitSeq);
	s.syncAsUint16LE(_waitRef);

	if (s.isSaving()) {
		// Write checksum
		int checksum = kSavegameCheckSum;
		s.syncAsUint16LE(checksum);
	} else {
		// Read checksum and validate it
		uint16 checksum = 0;
		s.syncAsUint16LE(checksum);
		if (checksum != kSavegameCheckSum)
			error("%s", _text->getText(kBadSVG));
	}
}

/**
* Support method that generates a savegame name
* @param slot		Slot number
*/
Common::String CGE2Engine::generateSaveName(int slot) {
	return Common::String::format("%s.%03d", _targetName.c_str(), slot);
}

} // End of namespace CGE2
