/* 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.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-1-1/engines/queen/queen.cpp $
 * $Id: queen.cpp 42751 2009-07-25 12:59:46Z lordhoto $
 *
 */

#include "base/plugins.h"

#include "common/config-manager.h"
#include "common/file.h"
#include "common/fs.h"
#include "common/savefile.h"
#include "common/system.h"
#include "common/events.h"
#include "common/EventRecorder.h"

#include "queen/queen.h"
#include "queen/bankman.h"
#include "queen/command.h"
#include "queen/cutaway.h"
#include "queen/debug.h"
#include "queen/display.h"
#include "queen/graphics.h"
#include "queen/grid.h"
#include "queen/input.h"
#include "queen/logic.h"
#include "queen/resource.h"
#include "queen/sound.h"
#include "queen/talk.h"
#include "queen/walk.h"

#include "engines/metaengine.h"

static const PlainGameDescriptor queenGameDescriptor = {
	"queen", "Flight of the Amazon Queen"
};

class QueenMetaEngine : public MetaEngine {
public:
	virtual const char *getName() const;
	virtual const char *getOriginalCopyright() const;

	virtual bool hasFeature(MetaEngineFeature f) const;
	virtual GameList getSupportedGames() const;
	virtual GameDescriptor findGame(const char *gameid) const;
	virtual GameList detectGames(const Common::FSList &fslist) const;
	virtual SaveStateList listSaves(const char *target) const;
	virtual void removeSaveState(const char *target, int slot) const;

	virtual Common::Error createInstance(OSystem *syst, Engine **engine) const;
};

const char *QueenMetaEngine::getName() const {
	return "Flight of the Amazon Queen";
}

const char *QueenMetaEngine::getOriginalCopyright() const {
	return "Flight of the Amazon Queen (C) John Passfield and Steve Stamatiadis";
}

bool QueenMetaEngine::hasFeature(MetaEngineFeature f) const {
	return
		(f == kSupportsListSaves) ||
		(f == kSupportsLoadingDuringStartup) ||
		(f == kSupportsDeleteSave);
}

bool Queen::QueenEngine::hasFeature(EngineFeature f) const {
	return
		(f == kSupportsRTL) ||
		(f == kSupportsSubtitleOptions);
}

GameList QueenMetaEngine::getSupportedGames() const {
	GameList games;
	games.push_back(queenGameDescriptor);
	return games;
}

GameDescriptor QueenMetaEngine::findGame(const char *gameid) const {
	if (0 == scumm_stricmp(gameid, queenGameDescriptor.gameid)) {
		return queenGameDescriptor;
	}
	return GameDescriptor();
}

GameList QueenMetaEngine::detectGames(const Common::FSList &fslist) const {
	GameList detectedGames;

	// Iterate over all files in the given directory
	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		if (file->isDirectory()) {
			continue;
		}
		if (file->getName().equalsIgnoreCase("queen.1") || file->getName().equalsIgnoreCase("queen.1c")) {
			Common::File dataFile;
			if (!dataFile.open(*file)) {
				continue;
			}
			Queen::DetectedGameVersion version;
			if (Queen::Resource::detectVersion(&version, &dataFile)) {
				GameDescriptor dg(queenGameDescriptor.gameid, queenGameDescriptor.description, version.language, version.platform);
				if (version.features & Queen::GF_DEMO) {
					dg.updateDesc("Demo");
					dg.setGUIOptions(Common::GUIO_NOSPEECH);
				} else if (version.features & Queen::GF_INTERVIEW) {
					dg.updateDesc("Interview");
					dg.setGUIOptions(Common::GUIO_NOSPEECH);
				} else if (version.features & Queen::GF_FLOPPY) {
					dg.updateDesc("Floppy");
					dg.setGUIOptions(Common::GUIO_NOSPEECH);
				} else if (version.features & Queen::GF_TALKIE) {
					dg.updateDesc("Talkie");
				}
				detectedGames.push_back(dg);
				break;
			}
		}
	}
	return detectedGames;
}

SaveStateList QueenMetaEngine::listSaves(const char *target) const {
	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
	Common::StringList filenames;
	char saveDesc[32];
	Common::String pattern("queen.s??");

	filenames = saveFileMan->listSavefiles(pattern);
	sort(filenames.begin(), filenames.end());	// Sort (hopefully ensuring we are sorted numerically..)

	SaveStateList saveList;
	for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
		// Obtain the last 2 digits of the filename, since they correspond to the save slot
		int slotNum = atoi(file->c_str() + file->size() - 2);

		if (slotNum >= 0 && slotNum <= 99) {
			Common::InSaveFile *in = saveFileMan->openForLoading(*file);
			if (in) {
				for (int i = 0; i < 4; i++)
					in->readUint32BE();
				in->read(saveDesc, 32);
				saveList.push_back(SaveStateDescriptor(slotNum, saveDesc));
				delete in;
			}
		}
	}

	return saveList;
}

void QueenMetaEngine::removeSaveState(const char *target, int slot) const {
	char extension[6];
	snprintf(extension, sizeof(extension), ".s%02d", slot);

	Common::String filename = target;
	filename += extension;

	g_system->getSavefileManager()->removeSavefile(filename);
}

Common::Error QueenMetaEngine::createInstance(OSystem *syst, Engine **engine) const {
	assert(engine);
	*engine = new Queen::QueenEngine(syst);
	return Common::kNoError;
}

#if PLUGIN_ENABLED_DYNAMIC(QUEEN)
	REGISTER_PLUGIN_DYNAMIC(QUEEN, PLUGIN_TYPE_ENGINE, QueenMetaEngine);
#else
	REGISTER_PLUGIN_STATIC(QUEEN, PLUGIN_TYPE_ENGINE, QueenMetaEngine);
#endif

namespace Queen {

QueenEngine::QueenEngine(OSystem *syst)
	: Engine(syst), _debugger(0) {
	g_eventRec.registerRandomSource(randomizer, "queen");
}

QueenEngine::~QueenEngine() {
	delete _bam;
	delete _resource;
	delete _bankMan;
	delete _command;
	delete _debugger;
	delete _display;
	delete _graphics;
	delete _grid;
	delete _input;
	delete _logic;
	delete _sound;
	delete _walk;
}

void QueenEngine::registerDefaultSettings() {
	ConfMan.registerDefault("talkspeed", Logic::DEFAULT_TALK_SPEED);
	ConfMan.registerDefault("subtitles", true);
	_subtitles = true;
}

void QueenEngine::checkOptionSettings() {
	// check talkspeed value
	if (_talkSpeed < MIN_TEXT_SPEED) {
		_talkSpeed = MIN_TEXT_SPEED;
	} else if (_talkSpeed > MAX_TEXT_SPEED) {
		_talkSpeed = MAX_TEXT_SPEED;
	}

	// demo and interview versions don't have speech at all
	if (_sound->speechOn() && (_resource->isDemo() || _resource->isInterview())) {
		_sound->speechToggle(false);
	}

	// ensure text is always on when voice is off
	if (!_sound->speechOn()) {
		_subtitles = true;
	}
}

void QueenEngine::syncSoundSettings() {
	readOptionSettings();
}

void QueenEngine::readOptionSettings() {
	_sound->setVolume(ConfMan.getInt("music_volume"));
	_sound->musicToggle(!ConfMan.getBool("music_mute"));
	_sound->sfxToggle(!ConfMan.getBool("sfx_mute"));
	_sound->speechToggle(!ConfMan.getBool("speech_mute"));
	_talkSpeed = (ConfMan.getInt("talkspeed") * (MAX_TEXT_SPEED - MIN_TEXT_SPEED) + 255 / 2) / 255 + MIN_TEXT_SPEED;
	_subtitles = ConfMan.getBool("subtitles");
	checkOptionSettings();
}

void QueenEngine::writeOptionSettings() {
	ConfMan.setInt("music_volume", _sound->getVolume());
	ConfMan.setBool("music_mute", !_sound->musicOn());
	ConfMan.setBool("sfx_mute", !_sound->sfxOn());
	ConfMan.setInt("talkspeed", ((_talkSpeed - MIN_TEXT_SPEED) * 255 + (MAX_TEXT_SPEED - MIN_TEXT_SPEED) / 2) / (MAX_TEXT_SPEED - MIN_TEXT_SPEED));
	ConfMan.setBool("speech_mute", !_sound->speechOn());
	ConfMan.setBool("subtitles", _subtitles);
	ConfMan.flushToDisk();
}

void QueenEngine::update(bool checkPlayerInput) {
	if (_debugger->isAttached()) {
		_debugger->onFrame();
	}

	_graphics->update(_logic->currentRoom());
	_logic->update();

	int frameDelay = (_lastUpdateTime + Input::DELAY_NORMAL - _system->getMillis());
	if (frameDelay <= 0) {
		frameDelay = 1;
	}
	_input->delay(frameDelay);
	_lastUpdateTime = _system->getMillis();

	if (!_resource->isInterview()) {
		_display->palCustomScroll(_logic->currentRoom());
	}
	BobSlot *joe = _graphics->bob(0);
	_display->update(joe->active, joe->x, joe->y);

	_input->checkKeys();
	if (_input->debugger()) {
		_input->debuggerReset();
		_debugger->attach();
	}
	if (canLoadOrSave()) {
		if (_input->quickSave()) {
			_input->quickSaveReset();
			saveGameState(SLOT_QUICKSAVE, "Quicksave");
		}
		if (_input->quickLoad()) {
			_input->quickLoadReset();
			loadGameState(SLOT_QUICKSAVE);
		}
		if (shouldPerformAutoSave(_lastSaveTime)) {
			saveGameState(SLOT_AUTOSAVE, "Autosave");
			_lastSaveTime = _system->getMillis();
		}
	}
	if (!_input->cutawayRunning()) {
		if (checkPlayerInput) {
			_command->updatePlayer();
		}
		if (_input->idleTime() >= Input::DELAY_SCREEN_BLANKER) {
			_display->blankScreen();
		}
	}
	_sound->updateMusic();
}

bool QueenEngine::canLoadOrSave() const {
	return !_input->cutawayRunning() && !(_resource->isDemo() || _resource->isInterview());
}

Common::Error QueenEngine::saveGameState(int slot, const char *desc) {
	debug(3, "Saving game to slot %d", slot);
	char name[20];
	Common::Error err = Common::kNoError;
	makeGameStateName(slot, name);
	Common::OutSaveFile *file = _saveFileMan->openForSaving(name);
	if (file) {
		// save data
		byte *saveData = new byte[SAVESTATE_MAX_SIZE];
		byte *p = saveData;
		_bam->saveState(p);
		_grid->saveState(p);
		_logic->saveState(p);
		_sound->saveState(p);
		uint32 dataSize = p - saveData;
		assert(dataSize < SAVESTATE_MAX_SIZE);

		// write header
		file->writeUint32BE('SCVM');
		file->writeUint32BE(SAVESTATE_CUR_VER);
		file->writeUint32BE(0);
		file->writeUint32BE(dataSize);
		char description[32];
		memset(description, 0, 32);
		strncpy(description, desc, 31);
		file->write(description, 32);

		// write save data
		file->write(saveData, dataSize);
		file->finalize();

		// check for errors
		if (file->err()) {
			warning("Can't write file '%s'. (Disk full?)", name);
			err = Common::kWritingFailed;
		}
		delete[] saveData;
		delete file;
	} else {
		warning("Can't create file '%s', game not saved", name);
		err = Common::kCreatingFileFailed;
	}

	return err;
}

Common::Error QueenEngine::loadGameState(int slot) {
	debug(3, "Loading game from slot %d", slot);
	Common::Error err = Common::kNoError;
	GameStateHeader header;
	Common::InSaveFile *file = readGameStateHeader(slot, &header);
	if (file && header.dataSize != 0) {
		byte *saveData = new byte[header.dataSize];
		byte *p = saveData;
		if (file->read(saveData, header.dataSize) != header.dataSize) {
			warning("Error reading savegame file");
			err = Common::kReadingFailed;
		} else {
			_bam->loadState(header.version, p);
			_grid->loadState(header.version, p);
			_logic->loadState(header.version, p);
			_sound->loadState(header.version, p);
			if (header.dataSize != (uint32)(p - saveData)) {
				warning("Corrupted savegame file");
				err = Common::kReadingFailed;	// FIXME
			} else {
				_logic->setupRestoredGame();
			}
		}
		delete[] saveData;
		delete file;
	} else {
		err = Common::kReadingFailed;
	}

	return err;
}

Common::InSaveFile *QueenEngine::readGameStateHeader(int slot, GameStateHeader *gsh) {
	char name[20];
	makeGameStateName(slot, name);
	Common::InSaveFile *file = _saveFileMan->openForLoading(name);
	if (file && file->readUint32BE() == MKID_BE('SCVM')) {
		gsh->version = file->readUint32BE();
		gsh->flags = file->readUint32BE();
		gsh->dataSize = file->readUint32BE();
		file->read(gsh->description, sizeof(gsh->description));
	} else {
		memset(gsh, 0, sizeof(GameStateHeader));
	}
	return file;
}

void QueenEngine::makeGameStateName(int slot, char *buf) const {
	if (slot == SLOT_LISTPREFIX) {
		strcpy(buf, "queen.s??");
	} else if (slot == SLOT_AUTOSAVE) {
		strcpy(buf, "queen.asd");
	} else {
		assert(slot >= 0);
		sprintf(buf, "queen.s%02d", slot);
	}
}

int QueenEngine::getGameStateSlot(const char *filename) const {
	int i = -1;
	const char *slot = strrchr(filename, '.');
	if (slot && slot[1] == 's') {
		i = atoi(slot + 2);
	}
	return i;
}

void QueenEngine::findGameStateDescriptions(char descriptions[100][32]) {
	char prefix[20];
	makeGameStateName(SLOT_LISTPREFIX, prefix);
	Common::StringList filenames = _saveFileMan->listSavefiles(prefix);
	for (Common::StringList::const_iterator it = filenames.begin(); it != filenames.end(); ++it) {
		int i = getGameStateSlot(it->c_str());
		if (i >= 0 && i < SAVESTATE_MAX_NUM) {
			GameStateHeader header;
			Common::InSaveFile *f = readGameStateHeader(i, &header);
			strcpy(descriptions[i], header.description);
			delete f;
		}
	}
}

GUI::Debugger *QueenEngine::getDebugger() {
	return _debugger;
}

Common::Error QueenEngine::run() {
	initGraphics(GAME_SCREEN_WIDTH, GAME_SCREEN_HEIGHT, false);

	_resource = new Resource();

	_bam = new BamScene(this);
	_bankMan = new BankManager(_resource);
	_command = new Command(this);
	_debugger = new Debugger(this);
	_display = new Display(this, _system);
	_graphics = new Graphics(this);
	_grid = new Grid(this);
	_input = new Input(_resource->getLanguage(), _system, this);

	if (_resource->isDemo()) {
		_logic = new LogicDemo(this);
	} else if (_resource->isInterview()) {
		_logic = new LogicInterview(this);
	} else {
		_logic = new LogicGame(this);
	}

	_sound = Sound::makeSoundInstance(_mixer, this, _resource->getCompression());
	_walk = new Walk(this);
	//_talkspeedScale = (MAX_TEXT_SPEED - MIN_TEXT_SPEED) / 255.0;

	registerDefaultSettings();
	readOptionSettings();

	_logic->start();
	if (ConfMan.hasKey("save_slot") && canLoadOrSave()) {
		loadGameState(ConfMan.getInt("save_slot"));
	}
	_lastSaveTime = _lastUpdateTime = _system->getMillis();

	while (!shouldQuit()) {
		if (_logic->newRoom() > 0) {
			_logic->update();
			_logic->oldRoom(_logic->currentRoom());
			_logic->currentRoom(_logic->newRoom());
			_logic->changeRoom();
			_display->fullscreen(false);
			if (_logic->currentRoom() == _logic->newRoom()) {
				_logic->newRoom(0);
			}
		} else if (_logic->joeWalk() == JWM_EXECUTE) {
			_logic->joeWalk(JWM_NORMAL);
			_command->executeCurrentAction();
		} else {
			_logic->joeWalk(JWM_NORMAL);
			update(true);
		}
	}

	return Common::kNoError;
}

} // End of namespace Queen
