/* ScummVM - Scumm Interpreter
 * Copyright (C) 2003-2005 The ScummVM project
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header$
 *
 */

#include "stdafx.h"

#include "backends/fs/fs.h"

#include "base/gameDetector.h"
#include "base/plugins.h"

#include "common/config-manager.h"
#include "common/file.h"
#include "common/timer.h"
#include "common/savefile.h"
#include "common/system.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/music.h"
#include "queen/resource.h"
#include "queen/sound.h"
#include "queen/talk.h"
#include "queen/walk.h"

#include "sound/mididrv.h"


/* Flight of the Amazon Queen */
static const GameSettings queen_setting =
	{ "queen", "Flight of the Amazon Queen", 0 };

GameList Engine_QUEEN_gameList() {
	GameList games;
	games.push_back(queen_setting);
	return games;
}

DetectedGameList Engine_QUEEN_detectGames(const FSList &fslist) {
	DetectedGameList detectedGames;

	// Iterate over all files in the given directory
	for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		if (!file->isDirectory()) {
			const char *gameName = file->displayName().c_str();

			if (0 == scumm_stricmp("queen.1", gameName) || 0 == scumm_stricmp("queen.1c", gameName)) {
				// Match found, add to list of candidates, then abort loop.
				detectedGames.push_back(queen_setting);
				break;
			}
		}
	}
	return detectedGames;
}

Engine *Engine_QUEEN_create(GameDetector *detector, OSystem *syst) {
	return new Queen::QueenEngine(detector, syst);
}

REGISTER_PLUGIN("Flight of the Amazon Queen", Engine_QUEEN_gameList, Engine_QUEEN_create, Engine_QUEEN_detectGames)

namespace Queen {

QueenEngine::QueenEngine(GameDetector *detector, OSystem *syst)
	: Engine(syst) {
}

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

void QueenEngine::registerDefaultSettings() {
	ConfMan.registerDefault("music_mute", false);
	ConfMan.registerDefault("sfx_mute", false);
	ConfMan.registerDefault("talkspeed", Logic::DEFAULT_TALK_SPEED);
	ConfMan.registerDefault("speech_mute", _resource->isDemo() || _resource->isInterview());
	ConfMan.registerDefault("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;
	}

	// ensure text is always on when voice is off
	if (!_sound->speechOn()) {
		_subtitles = true;
	}
	
	// demo and interview versions don't have speech at all
	if (_sound->speechOn() && (_resource->isDemo() || _resource->isInterview())) {
		_sound->speechToggle(false);
	}
}

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

void QueenEngine::writeOptionSettings() {
	ConfMan.set("music_volume", _music->volume());
	ConfMan.set("music_mute", !_sound->musicOn());
	ConfMan.set("sfx_mute", !_sound->sfxOn());
	ConfMan.set("talkspeed", _talkSpeed);
	ConfMan.set("speech_mute", !_sound->speechOn());
	ConfMan.set("subtitles", _subtitles);
	ConfMan.flushToDisk();
}

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

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

	_input->delay();

	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(0, "Quicksave");
		}
		if (_input->quickLoad()) {
			_input->quickLoadReset();
			loadGameState(0);
		}
		if (_system->getMillis() - _lastSaveTime >= AUTOSAVE_INTERVAL) {
			saveGameState(AUTOSAVE_SLOT, "Autosave");
			_lastSaveTime = _system->getMillis();
		}
	}
	if (!_input->cutawayRunning()) {
		if (checkPlayerInput) {
			_command->updatePlayer();
		}
		if (_input->idleTime() >= Input::DELAY_SCREEN_BLANKER) {
			_display->blankScreen();
		}
	}
}

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

void QueenEngine::saveGameState(uint16 slot, const char *desc) {
	debug(3, "Saving game to slot %d", slot);
	char name[20];
	makeGameStateName(slot, name);
	SaveFile *file = _saveFileMan->openSavefile(name, true);
	if (file) {
		// save data
		byte *saveData = new byte[30000];
		byte *p = saveData;
		_bam->saveState(p);
		_grid->saveState(p);
		_logic->saveState(p);
		_sound->saveState(p);
		uint32 dataSize = p - saveData;

		// write header
		GameStateHeader header;
		memset(&header, 0, sizeof(header));
		file->writeUint32BE('SCVM');
		header.version = TO_BE_32(SAVESTATE_CUR_VER);
		header.flags = TO_BE_32(0);
		header.dataSize = TO_BE_32(dataSize);
		strncpy(header.description, desc, sizeof(header.description) - 1);
		file->write(&header, sizeof(header));

		// write save data
		if (file->write(saveData, dataSize) != dataSize) {
			warning("Can't write file '%s'. (Disk full?)", name);
		}
		delete[] saveData;
		delete file;
	} else {
		warning("Can't create file '%s', game not saved", name);
	}
}

void QueenEngine::loadGameState(uint16 slot) {
	debug(3, "Loading game from slot %d", slot);
	GameStateHeader header;
	SaveFile *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");
		} 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)) {
				error("Corrupted savegame file");
			}
			_logic->setupRestoredGame();
		}
		delete[] saveData;
		delete file;
	}
}

SaveFile *QueenEngine::readGameStateHeader(uint16 slot, GameStateHeader *gsh) {
	char name[20];
	makeGameStateName(slot, name);
	SaveFile *file = _saveFileMan->openSavefile(name, false);
	if (file && file->readUint32BE() == '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(uint16 slot, char *buf) {
	if (slot == AUTOSAVE_SLOT) {
		strcpy(buf, "queen.asd");
	} else {
		sprintf(buf, "queen.s%02d", slot);
	}
}

void QueenEngine::findGameStateDescriptions(char descriptions[100][32]) {
	char filename[20];
	makeGameStateName(0, filename);
	filename[strlen(filename) - 2] = 0;
	bool marks[SAVESTATE_MAX];
	_saveFileMan->listSavefiles(filename, marks, SAVESTATE_MAX);
	for (int i = 0; i < SAVESTATE_MAX; ++i) {
		if (marks[i]) {
			GameStateHeader header;
			SaveFile *f = readGameStateHeader(i, &header);
			strcpy(descriptions[i], header.description);
			delete f;
		}
	}
}

void QueenEngine::errorString(const char *buf1, char *buf2) {
	strcpy(buf2, buf1);
}

int QueenEngine::go() {
	_logic->start();
	if (ConfMan.hasKey("save_slot") && canLoadOrSave()) {
		loadGameState(ConfMan.getInt("save_slot"));
	}
	_lastSaveTime = _system->getMillis();
	_quit = false;
	while (!_quit) {
		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 0;
}

int QueenEngine::init(GameDetector &detector) {
	_system->beginGFXTransaction();
		initCommonGFX(detector);
		_system->initSize(GAME_SCREEN_WIDTH, GAME_SCREEN_HEIGHT);
	_system->endGFXTransaction();

	_bam = new BamScene(this);
	_resource = new Resource();
	_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);

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

	if (!_mixer->isReady())
		warning("Sound initialisation failed.");
	_mixer->setVolumeForSoundType(SoundMixer::kSFXAudioDataType, ConfMan.getInt("sfx_volume"));
	// Set mixer music volume to maximum, since music volume is regulated by MusicPlayer's MIDI messages
	_mixer->setVolumeForSoundType(SoundMixer::kMusicAudioDataType, SoundMixer::kMaxMixerVolume);

	int midiDriver = MidiDriver::detectMusicDriver(MDT_NATIVE | MDT_ADLIB | MDT_PREFER_NATIVE);
	MidiDriver *driver = MidiDriver::createMidi(midiDriver);
	if (!driver)
		driver = MidiDriver_ADLIB_create(_mixer);
	else if (ConfMan.getBool("native_mt32") || (midiDriver == MD_MT32))
		driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
		
	_music = new Music(driver, this);
	_music->hasNativeMT32(ConfMan.getBool("native_mt32") || (midiDriver == MD_MT32));

	_sound = Sound::giveSound(_mixer, this, _resource->compression());
	_walk = new Walk(this);

	registerDefaultSettings();
	readOptionSettings();

	return 0;
}

} // End of namespace Queen