/* 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/savefile.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "graphics/thumbnail.h"

#include "tucker/tucker.h"

namespace Tucker {

#define kSavegameSignature MKTAG('T', 'C', 'K', 'R')

enum {
	kSavegameVersionCurrent = 2,
	kSavegameVersionMinimum = 1
};

enum SavegameFlag {
	kSavegameFlagAutosave = 1 << 0
};

Common::String generateGameStateFileName(const char *target, int slot, bool prefixOnly) {
	Common::String name(target);
	if (prefixOnly) {
		name += ".#*";
	} else {
		name += Common::String::format(".%d", slot);
	}
	return name;
}

static void saveOrLoadVar(Common::WriteStream &stream, int &i) {
	stream.writeSint32LE(i);
}

static void saveOrLoadVar(Common::ReadStream &stream, int &i) {
	i = stream.readSint32LE();
}

static void saveOrLoadVar(Common::WriteStream &stream, Location &location) {
	stream.writeSint32LE((int)location);
}

static void saveOrLoadVar(Common::ReadStream &stream, Location &location) {
	location = (Location)stream.readSint32LE();
}

template<class S>
TuckerEngine::SavegameError TuckerEngine::saveOrLoadGameStateData(S &s) {
	for (int i = 0; i < kFlagsTableSize; ++i) {
		saveOrLoadVar(s, _flagsTable[i]);
	}
	for (int i = 0; i < 40; ++i) {
		saveOrLoadVar(s, _inventoryObjectsList[i]);
	}
	for (int i = 0; i < 50; ++i) {
		saveOrLoadVar(s, _inventoryItemsState[i]);
	}
	for (int i = 0; i < 50; ++i) {
		saveOrLoadVar(s, _panelObjectsOffsetTable[i]);
	}
	saveOrLoadVar(s, _mainSpritesBaseOffset);
	saveOrLoadVar(s, _selectedObject._xPos);
	saveOrLoadVar(s, _selectedObject._yPos);
	saveOrLoadVar(s, _location);
	saveOrLoadVar(s, _xPosCurrent);
	saveOrLoadVar(s, _yPosCurrent);
	saveOrLoadVar(s, _inventoryObjectsCount);
	saveOrLoadVar(s, _inventoryObjectsOffset);

	return s.err() ? kSavegameIoError : kSavegameNoError;
}

Common::Error TuckerEngine::loadGameState(int slot) {
	Common::String fileName = generateGameStateFileName(_targetName.c_str(), slot);
	Common::InSaveFile *file = _saveFileMan->openForLoading(fileName);

	if (!file) {
		return Common::kReadingFailed;
	}

	SavegameHeader header;
	SavegameError savegameError = readSavegameHeader(file, header);

	if (!savegameError) {
		savegameError = saveOrLoadGameStateData(*file);
	}

	if (savegameError) {
		switch (savegameError) {
		case kSavegameInvalidTypeError:
			warning("Invalid savegame '%s' (does not look like a ScummVM Tucker-engine savegame)", fileName.c_str());
			break;

		case kSavegameInvalidVersionError:
			warning("Invalid savegame '%s' (expected savegame version v%i-v%i, got v%i)",
				fileName.c_str(), kSavegameVersionMinimum, kSavegameVersionCurrent, header.version);
			break;

		default:
			warning("Failed to load savegame '%s'", fileName.c_str());
			break;
		}

		delete file;
		return Common::kReadingFailed;
	}

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

	_nextLocation = _location;
	setBlackPalette();
	loadBudSpr();
	_forceRedrawPanelItems = true;
	_panelType = kPanelTypeNormal;
	setCursorState(kCursorStateNormal);

	delete file;
	return Common::kNoError;
}


WARN_UNUSED_RESULT TuckerEngine::SavegameError TuckerEngine::readSavegameHeader(const char *target, int slot, SavegameHeader &header) {
	Common::String fileName = generateGameStateFileName(target, slot);
	Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(fileName);

	if (!file) {
		return kSavegameNotFoundError;
	}

	SavegameError savegameError = readSavegameHeader(file, header);

	delete file;
	return savegameError;
}

WARN_UNUSED_RESULT TuckerEngine::SavegameError TuckerEngine::readSavegameHeader(Common::InSaveFile *file, SavegameHeader &header, bool skipThumbnail) {
	header.version   = 0;
	header.flags     = 0;
	header.description.clear();
	header.saveDate  = 0;
	header.saveTime  = 0;
	header.playTime  = 0;
	header.thumbnail = nullptr;

	if (file->readUint32BE() == kSavegameSignature) {
		header.version = file->readUint16LE();
	} else {
		// possibly an old, headerless savegame

		file->seek(0, SEEK_SET);

		header.version = file->readUint16LE();
		// old savegames are always version 1
		if (header.version != 1) {
			return kSavegameInvalidTypeError;
		}

		file->skip(2);
	}

	if (header.version < kSavegameVersionMinimum || header.version > kSavegameVersionCurrent) {
		return kSavegameInvalidVersionError;
	}

	if (header.version >= 2) {
		// savegame flags
		header.flags = file->readUint32LE();

		char ch;
		while ((ch = (char)file->readByte()) != '\0')
			header.description += ch;

		header.saveDate = file->readUint32LE();
		header.saveTime = file->readUint32LE();
		header.playTime = file->readUint32LE();

		if (!Graphics::loadThumbnail(*file, header.thumbnail, skipThumbnail)) {
			return kSavegameIoError;
		}
	}

	return ((file->err() || file->eos()) ? kSavegameIoError : kSavegameNoError);
}

TuckerEngine::SavegameError TuckerEngine::writeSavegameHeader(Common::OutSaveFile *file, SavegameHeader &header) {
	// Tucker savegame signature
	file->writeUint32BE(kSavegameSignature);

	// version information
	file->writeUint16LE(kSavegameVersionCurrent);

	// savegame flags
	file->writeUint32LE(header.flags);

	// savegame name
	file->writeString(header.description);
	file->writeByte(0);

	// creation/play time
	TimeDate curTime;
	_system->getTimeAndDate(curTime);
	header.saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF);
	header.saveTime = ((curTime.tm_hour & 0xFF) << 16) | (((curTime.tm_min)     & 0xFF) <<  8) | ((curTime.tm_sec)         & 0xFF);
	header.playTime = g_engine->getTotalPlayTime() / 1000;
	file->writeUint32LE(header.saveDate);
	file->writeUint32LE(header.saveTime);
	file->writeUint32LE(header.playTime);

	// thumbnail
	Graphics::saveThumbnail(*file);

	return (file->err() ? kSavegameIoError : kSavegameNoError);
}

Common::Error TuckerEngine::saveGameState(int slot, const Common::String &description) {
	return writeSavegame(slot, description, false);
}

Common::Error TuckerEngine::writeSavegame(int slot, const Common::String &description, bool autosave) {
	Common::String fileName = generateGameStateFileName(_targetName.c_str(), slot);
	Common::OutSaveFile *file = _saveFileMan->openForSaving(fileName);
	SavegameHeader header;
	SavegameError savegameError = kSavegameNoError;

	if (!file)
		savegameError = kSavegameIoError;

	if (!savegameError) {
		// savegame flags
		if (autosave)
			header.flags |= kSavegameFlagAutosave;

		// description
		header.description = description;

		savegameError = writeSavegameHeader(file, header);
	}

	if (!savegameError)
		savegameError = saveOrLoadGameStateData(*file);

	if (!savegameError)
		file->finalize();

	delete file;

	if (savegameError) {
		warning("Error writing savegame '%s'", fileName.c_str());
		return Common::kWritingFailed;
	}

	return Common::kNoError;
}

bool TuckerEngine::isAutosaveAllowed() {
	return isAutosaveAllowed(_targetName.c_str());
}

bool TuckerEngine::isAutosaveAllowed(const char *target) {
	SavegameHeader savegameHeader;
	SavegameError savegameError = readSavegameHeader(target, kAutoSaveSlot, savegameHeader);
	return (savegameError == kSavegameNotFoundError || (savegameHeader.flags & kSavegameFlagAutosave));
}

void TuckerEngine::writeAutosave() {
	if (canSaveGameStateCurrently()) {
		// unconditionally reset last autosave timestamp so we don't start
		// hammering the disk in case we can't/don't actually write the file
		_lastSaveTime = _system->getMillis();

		if (!isAutosaveAllowed()) {
			warning("Refusing to overwrite non-autosave savegame in slot %i, skipping autosave", kAutoSaveSlot);
			return;
		}

		if (writeSavegame(kAutoSaveSlot, "Autosave", true).getCode() != Common::kNoError) {
			warning("Can't create autosave in slot %i, game not saved", kAutoSaveSlot);
			return;
		}
	}
}

bool TuckerEngine::canLoadOrSave() const {
	return !_player && _cursorState != kCursorStateDisabledHidden;
}

bool TuckerEngine::canLoadGameStateCurrently() {
	return canLoadOrSave();
}

bool TuckerEngine::canSaveGameStateCurrently() {
	return canLoadOrSave();
}

bool TuckerEngine::existsSavegame() {
	Common::String pattern = generateGameStateFileName(_targetName.c_str(), 0, true);
	return !_saveFileMan->listSavefiles(pattern).empty();
}

} // namespace Tucker