/* 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 "graphics/thumbnail.h"

#include "saga/saga.h"
#include "saga/actor.h"
#include "saga/events.h"
#include "saga/interface.h"
#include "saga/isomap.h"
#include "saga/music.h"
#include "saga/render.h"
#include "saga/scene.h"
#include "saga/script.h"

#define CURRENT_SAGA_VER 8

namespace Saga {

static SaveFileData emptySlot = {
	 "", 0
};

char* SagaEngine::calcSaveFileName(uint slotNumber) {
	static char name[MAX_FILE_NAME];
	sprintf(name, "%s.s%02u", _targetName.c_str(), slotNumber);
	return name;
}

SaveFileData *SagaEngine::getSaveFile(uint idx) {
	if (idx >= MAX_SAVES) {
		error("getSaveFileName wrong idx");
	}
	if (isSaveListFull()) {
		return &_saveFiles[_saveFilesCount - idx - 1];
	} else {
		if (!emptySlot.name[0])
			Common::strlcpy(emptySlot.name, getTextString(kTextNewSave), SAVE_TITLE_SIZE);

		return (idx == 0) ? &emptySlot : &_saveFiles[_saveFilesCount - idx];
	}
}

bool SagaEngine::locateSaveFile(char *saveName, uint &titleNumber) {
	uint i;
	for (i = 0; i < _saveFilesCount; i++) {
		if (strcmp(saveName, _saveFiles[i].name) == 0) {
			if (isSaveListFull()) {
				titleNumber = _saveFilesCount - i - 1;
			} else {
				titleNumber = _saveFilesCount - i;
			}
			return true;
		}
	}
	return false;
}

uint SagaEngine::getNewSaveSlotNumber() const {
	uint i, j;
	bool found;
	for (i = 0; i < MAX_SAVES; i++) {
		found = false;
		for (j = 0; j < _saveFilesCount; j++) {
			if (_saveFiles[j].slotNumber == i) {
				found = true;
				break;
			}
		}
		if (!found) {
			return i;
		}
	}

	error("getNewSaveSlotNumber save list is full");
}

static int compareSaveFileData(const void *a, const void *b) {
	const SaveFileData *s1 = (const SaveFileData *)a;
	const SaveFileData *s2 = (const SaveFileData *)b;

	if (s1->slotNumber < s2->slotNumber) {
		return -1;
	} else if (s1->slotNumber > s2->slotNumber) {
		return 1;
	} else {
		return 0;
	}
}

void SagaEngine::fillSaveList() {

	int i;
	Common::InSaveFile *in;
	Common::StringArray filenames;
	char slot[3];
	int slotNumber;
	char *name;

	name = calcSaveFileName(MAX_SAVES);
	name[strlen(name) - 2] = '*';
	name[strlen(name) - 1] = 0;

	filenames = _saveFileMan->listSavefiles(name);

	for (i = 0; i < MAX_SAVES; i++) {
		_saveFiles[i].name[0] = 0;
		_saveFiles[i].slotNumber = (uint)-1;
	}

	_saveFilesCount = 0;

	for (Common::StringArray::iterator file = filenames.begin(); file != filenames.end(); ++file){
		//Obtain the last 2 digits of the filename, since they correspond to the save slot
		slot[0] = file->c_str()[file->size()-2];
		slot[1] = file->c_str()[file->size()-1];
		slot[2] = 0;

		slotNumber = atoi(slot);
		if (slotNumber >= 0 && slotNumber < MAX_SAVES) {
			name = calcSaveFileName(slotNumber);
			if ((in = _saveFileMan->openForLoading(name)) != NULL) {
				_saveHeader.type = in->readUint32BE();
				_saveHeader.size = in->readUint32LE();
				_saveHeader.version = in->readUint32LE();
				in->read(_saveHeader.name, sizeof(_saveHeader.name));

				if (_saveHeader.type != MKTAG('S','A','G','A')) {
					warning("SagaEngine::load wrong save %s format", name);
					i++;
					continue;
				}
				strcpy(_saveFiles[_saveFilesCount].name, _saveHeader.name);
				_saveFiles[_saveFilesCount].slotNumber = slotNumber;
				delete in;
				_saveFilesCount++;
			}
		}
	}

	qsort(_saveFiles, _saveFilesCount, sizeof(_saveFiles[0]), compareSaveFileData);
}

void SagaEngine::save(const char *fileName, const char *saveName) {
	Common::OutSaveFile *out;
	char title[TITLESIZE];

	if (!(out = _saveFileMan->openForSaving(fileName))) {
		return;
	}

	_saveHeader.type = MKTAG('S','A','G','A');
	_saveHeader.size = 0;
	_saveHeader.version = CURRENT_SAGA_VER;
	// Note that IHNM has a smaller save title size than ITE
	// We allocate the ITE save title size here, to preserve
	// savegame backwards compatibility
	Common::strlcpy(_saveHeader.name, saveName, SAVE_TITLE_SIZE);

	out->writeUint32BE(_saveHeader.type);
	out->writeUint32LE(_saveHeader.size);
	out->writeUint32LE(_saveHeader.version);
	out->write(_saveHeader.name, sizeof(_saveHeader.name));

	// Original game title
	memset(title, 0, TITLESIZE);
	Common::strlcpy(title, _gameTitle.c_str(), TITLESIZE);
	out->write(title, TITLESIZE);

	// Thumbnail
	// First draw scene without save dialog
	int oldMode = _interface->getMode();
	_interface->setMode(kPanelMain);
	_render->drawScene();

	Graphics::saveThumbnail(*out);

	_interface->setMode(oldMode);

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

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

	// Surrounding scene
	out->writeSint32LE(_scene->getOutsetSceneNumber());
#ifdef ENABLE_IHNM
	if (getGameId() == GID_IHNM) {
		out->writeSint32LE(_scene->currentChapterNumber());
		out->writeSint32LE(0);	// obsolete, was used for the protagonist
		out->writeSint32LE(_scene->getCurrentMusicTrack());
		out->writeSint32LE(_scene->getCurrentMusicRepeat());
	}
#endif
	// Inset scene
	out->writeSint32LE(_scene->currentSceneNumber());

#ifdef ENABLE_IHNM
	if (getGameId() == GID_IHNM) {
		out->writeUint32LE(_globalFlags);
		for (int i = 0; i < ARRAYSIZE(_ethicsPoints); i++)
			out->writeSint16LE(_ethicsPoints[i]);
	}
#endif

	_interface->saveState(out);

	_actor->saveState(out);

	out->writeSint16LE(_script->_commonBuffer.size());

	out->write(_script->_commonBuffer.getBuffer(), _script->_commonBuffer.size());

	// ISO map x, y coordinates for ITE
	if (getGameId() == GID_ITE) {
		out->writeSint16LE(_isoMap->getMapPosition().x);
		out->writeSint16LE(_isoMap->getMapPosition().y);
	}

	out->finalize();

	if (out->err())
		warning("Can't write file '%s'. (Disk full?)", fileName);

	delete out;

	_interface->resetSaveReminder();
}

void SagaEngine::load(const char *fileName) {
	Common::InSaveFile *in;
	int commonBufferSize;
	int sceneNumber, insetSceneNumber;
	int mapx, mapy;
	char title[TITLESIZE];

	if (!(in = _saveFileMan->openForLoading(fileName))) {
		return;
	}

	_saveHeader.type = in->readUint32BE();
	_saveHeader.size = in->readUint32LE();
	_saveHeader.version = in->readUint32LE();
	in->read(_saveHeader.name, sizeof(_saveHeader.name));

	// Some older saves were not written in an endian safe fashion.
	// We try to detect this here by checking for extremly high version values.
	// If found, we retry with the data swapped.
	if (_saveHeader.version > 0xFFFFFF) {
		warning("This savegame is not endian safe, retrying with the data swapped");
		_saveHeader.version = SWAP_BYTES_32(_saveHeader.version);
	}

	debug(2, "Save version: 0x%X", _saveHeader.version);

	if (_saveHeader.version < 4)
		warning("This savegame is not endian-safe. There may be problems");

	if (_saveHeader.type != MKTAG('S','A','G','A')) {
		error("SagaEngine::load wrong save game format");
	}

	if (_saveHeader.version > 4) {
		in->read(title, TITLESIZE);
		debug(0, "Save is for: %s", title);
	}

	if (_saveHeader.version >= 6) {
		// We don't need the thumbnail here, so just read it and discard it
		Graphics::skipThumbnail(*in);

		in->readUint32BE();	// save date
		in->readUint16BE(); // save time

		if (_saveHeader.version >= 8) {
			uint32 playTime = in->readUint32BE();
			g_engine->setTotalPlayTime(playTime * 1000);
		}
	}

	// Clear pending events here, and don't process queued music events
	_events->clearList(false);

	// Surrounding scene
	sceneNumber = in->readSint32LE();
#ifdef ENABLE_IHNM
	if (getGameId() == GID_IHNM) {
		int currentChapter = _scene->currentChapterNumber();
		_scene->setChapterNumber(in->readSint32LE());
		in->skip(4);	// obsolete, was used for setting the protagonist
		if (_scene->currentChapterNumber() != currentChapter)
			_scene->changeScene(-2, 0, kTransitionFade, _scene->currentChapterNumber());
		_scene->setCurrentMusicTrack(in->readSint32LE());
		_scene->setCurrentMusicRepeat(in->readSint32LE());
		_music->stop();
		if (_scene->currentChapterNumber() == 8)
			_interface->setMode(kPanelChapterSelection);
		if (!isIHNMDemo()) {
			_music->play(_music->_songTable[_scene->getCurrentMusicTrack()], _scene->getCurrentMusicRepeat() ? MUSIC_LOOP : MUSIC_NORMAL);
		} else {
			_music->play(3, MUSIC_LOOP);
		}
	}
#endif

	// Inset scene
	insetSceneNumber = in->readSint32LE();

#ifdef ENABLE_IHNM
	if (getGameId() == GID_IHNM) {
		_globalFlags = in->readUint32LE();
		for (int i = 0; i < ARRAYSIZE(_ethicsPoints); i++)
			_ethicsPoints[i] = in->readSint16LE();
	}
#endif

	_interface->loadState(in);

	_actor->loadState(in);

	commonBufferSize = in->readSint16LE();
	_script->_commonBuffer.resize(commonBufferSize);
	in->read(_script->_commonBuffer.getBuffer(), commonBufferSize);

	if (getGameId() == GID_ITE) {
		mapx = in->readSint16LE();
		mapy = in->readSint16LE();
		_isoMap->setMapPosition(mapx, mapy);
	}
	// Note: the mapx, mapy ISO map positions were incorrectly saved
	// for IHNM too, which has no ISO map scenes, up to save version 6.
	// Since they're at the end of the savegame, we just ignore them

	delete in;

	// Mute volume to prevent outScene music play
	int volume = _music->getVolume();
	_music->setVolume(0);

	_scene->clearSceneQueue();
	_scene->changeScene(sceneNumber, ACTOR_NO_ENTRANCE, kTransitionNoFade);

	_events->handleEvents(0); //dissolve backgrounds

	if (insetSceneNumber != sceneNumber) {
		_render->setFlag(RF_DISABLE_ACTORS);
		_scene->draw();
		_render->drawScene();
		_render->clearFlag(RF_DISABLE_ACTORS);
		_scene->changeScene(insetSceneNumber, ACTOR_NO_ENTRANCE, kTransitionNoFade);
	}

	_music->setVolume(volume);

	_interface->draw();

	// Abort any scene entry protagonist animations and auto-cue speeches.
	// Fixes bug #10009.
	_actor->abortAllSpeeches();
	_actor->_protagonist->_location = _actor->_protagonist->_finalTarget;
	_actor->actorEndWalk(ID_PROTAG, true);
}

} // End of namespace Saga
