/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "ags/ags.h"
#include "ags/detection.h"
#include "ags/events.h"
#include "ags/game_scanner.h"
#include "ags/music.h"
#include "ags/console.h"
#include "common/scummsys.h"
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/events.h"
#include "common/file.h"
#include "common/util.h"
#include "engines/util.h"

#include "ags/shared/core/platform.h"

#include "ags/lib/std/set.h"
#include "ags/shared/ac/common.h"
#include "ags/engine/ac/game.h"
#include "ags/globals.h"
#include "ags/engine/ac/game_setup.h"
#include "ags/engine/ac/game_state.h"
#include "ags/engine/ac/room.h"
#include "ags/shared/core/def_version.h"
#include "ags/engine/debugging/debugger.h"
#include "ags/engine/debugging/debug_log.h"
#include "ags/shared/debugging/out.h"
#include "ags/engine/game/savegame.h"
#include "ags/engine/game/savegame_components.h"
#include "ags/engine/main/config.h"
#include "ags/engine/main/engine.h"
#include "ags/engine/main/main.h"
#include "ags/engine/main/quit.h"
#include "ags/engine/platform/base/ags_platform_driver.h"
#include "ags/engine/script/cc_instance.h"
#include "ags/engine/script/script.h"
#include "ags/engine/ac/route_finder.h"
#include "ags/shared/core/asset_manager.h"
#include "ags/shared/util/directory.h"
#include "ags/shared/script/cc_common.h"

#ifdef ENABLE_AGS_TESTS
#include "ags/tests/test_all.h"
#endif

// Include translation.h last as some AGS classes have member such as _sc, which clash with
// macro defined in translation.h.
#include "common/translation.h"

namespace AGS {

AGSEngine *g_vm;

AGSEngine::AGSEngine(OSystem *syst, const AGSGameDescription *gameDesc) : Engine(syst),
	_gameDescription(gameDesc), _randomSource("AGS"), _events(nullptr), _music(nullptr),
	_gfxDriver(nullptr), _globals(nullptr), _forceTextAA(false) {
	g_vm = this;

	AGS3::script_commands_init();
	AGS3::Engine::SavegameComponents::component_handlers_init();
	_events = new EventsManager();
	_globals = new ::AGS3::Globals();

	Common::String forceAA;
	if (ConfMan.getActiveDomain()->tryGetVal("force_text_aa", forceAA))
		Common::parseBool(forceAA, _forceTextAA);

	// WORKAROUND: Certain games need to force AA to render the text correctly
	if (_gameDescription->desc.flags & GAMEFLAG_FORCE_AA)
		_forceTextAA = true;
}

AGSEngine::~AGSEngine() {
	if (_globals && _G(proper_exit) == 0) {
		_G(platform)->DisplayAlert("Error: the program has exited without requesting it.\n"
		                           "Program pointer: %+03d  (write this number down), ACI version %s\n"
		                           "If you see a list of numbers above, please write them down and contact\n"
		                           "developers. Otherwise, note down any other information displayed.",
		                           _G(our_eip), _G(EngineVersion).LongString.GetCStr());
	}

	delete _events;
	delete _music;
	delete _globals;
	AGS3::Engine::SavegameComponents::component_handlers_free();
	AGS3::script_commands_free();
}

uint32 AGSEngine::getFeatures() const {
	return _gameDescription->desc.flags;
}

const PluginVersion *AGSEngine::getNeededPlugins() const {
	return _gameDescription->_plugins;
}

Common::String AGSEngine::getGameId() const {
	return _gameDescription->desc.gameId;
}

Common::Error AGSEngine::run() {
	if (debugChannelSet(-1, kDebugScan)) {
		// Scan the given folder and subfolders for unknown games
		AGS3::GameScanner scanner;
		scanner.scan(ConfMan.get("path"));
		return Common::kNoError;
	}

	if (isUnsupportedPre25()) {
		GUIErrorMessage(_("The selected game uses a pre-2.5 version of the AGS engine, which is not supported."));
		return Common::kNoError;
	}

	if (is64BitGame()) {
		// If the game file was opened and the engine started, but the
		// size is -1, then it must be a game like Strangeland where
		// the data file is > 2Gb
		GUIErrorMessage(_("The selected game has a data file greater than 2Gb, "
			"which isn't supported by your version of ScummVM yet."));
		return Common::kNoError;
	}

	if (debugChannelSet(-1, kDebugScript))
		AGS3::ccSetOption(SCOPT_DEBUGRUN, 1);

#ifdef ENABLE_AGS_TESTS
	AGS3::Test_DoAllTests();
	return Common::kNoError;
#endif

	setDebugger(new AGSConsole(this));

	const char *filename = _gameDescription->desc.filesDescriptions[0].fileName;
	const char *ARGV[] = { "scummvm.exe", filename };
	const int ARGC = 2;
	AGS3::main_init(ARGC, ARGV);

	_G(debug_flags) = 0;

	if (ConfMan.hasKey("display_fps"))
		_G(display_fps) = ConfMan.getBool("display_fps") ? AGS3::kFPS_Forced : AGS3::kFPS_Hide;

	AGS3::ConfigTree startup_opts;
	int res = AGS3::main_process_cmdline(startup_opts, ARGC, ARGV);
	if (res != 0)
		return Common::kUnknownError;

	if (_G(justDisplayVersion)) {
		_G(platform)->WriteStdOut(AGS3::get_engine_string().GetCStr());
		return Common::kNoError;
	}

	if (_G(justDisplayHelp)) {
		AGS3::main_print_help();
		return Common::kNoError;
	}

	if (!_G(justTellInfo))
		_G(platform)->SetGUIMode(true);
	AGS3::init_debug(startup_opts, _G(justTellInfo));
	AGS3::AGS::Shared::Debug::Printf("%s", AGS3::get_engine_string().GetCStr());

	AGS3::main_set_gamedir(ARGC, ARGV);

	// Update shell associations and exit
	if (_G(debug_flags) & DBG_REGONLY)
		return Common::kNoError;

	_music = new Music();

	_G(loadSaveGameOnStartup) = ConfMan.getInt("save_slot");

	syncSoundSettings();
	AGS3::initialize_engine(startup_opts);

	// Do shutdown stuff
	::AGS3::quit_free();

	return Common::kNoError;
}

SaveStateList AGSEngine::listSaves() const {
	return getMetaEngine()->listSaves(_targetName.c_str());
}

bool AGSEngine::getPixelFormat(int depth, Graphics::PixelFormat &format) const {
	Common::List<Graphics::PixelFormat> supportedFormatsList = g_system->getSupportedFormats();

	if (depth == 8) {
		format = Graphics::PixelFormat::createFormatCLUT8();
		return true;
	}

	// Prefer format with the requested color depth
	for (Common::List<Graphics::PixelFormat>::iterator it =
			supportedFormatsList.begin(); it != supportedFormatsList.end(); ++it) {
		if (it->bpp() == depth) {
			format = *it;
			return true;
		}
	}

	// Allow using 16 bit <-> 32 bit conversions by using the preferred graphics mode
	if (!supportedFormatsList.empty()) {
		format = supportedFormatsList.front();
		return true;
	}

	return false;
}


void AGSEngine::setGraphicsMode(size_t w, size_t h, int colorDepth) {
	Common::List<Graphics::PixelFormat> supportedFormatsList = g_system->getSupportedFormats();
	Graphics::PixelFormat format;
	if (!getPixelFormat(colorDepth, format))
		error("Unsupported color depth %d", colorDepth);

	initGraphics(w, h, &format);
}

bool AGSEngine::isUnsupportedPre25() const {
	return _gameDescription->desc.extra &&
		!strcmp(_gameDescription->desc.extra, "Pre 2.5");
}

bool AGSEngine::is64BitGame() const {
	Common::File f;
	return f.open(_gameDescription->desc.filesDescriptions[0].fileName)
		&& f.size() == -1;
}

Common::FSNode AGSEngine::getGameFolder() {
	return Common::FSNode(ConfMan.get("path"));
}

bool AGSEngine::canLoadGameStateCurrently() {
	return !_GP(thisroom).Options.SaveLoadDisabled &&
	       !_G(inside_script) && !_GP(play).fast_forward && !_G(no_blocking_functions);
}

bool AGSEngine::canSaveGameStateCurrently() {
	return !_GP(thisroom).Options.SaveLoadDisabled &&
	       !_G(inside_script) && !_GP(play).fast_forward && !_G(no_blocking_functions);
}

Common::Error AGSEngine::loadGameState(int slot) {
	(void)AGS3::try_restore_save(slot);
	return Common::kNoError;
}

Common::Error AGSEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
	(void)AGS3::save_game(slot, desc.c_str());
	return Common::kNoError;
}

void AGSEngine::GUIError(const Common::String &msg) {
	GUIErrorMessage(msg);
}

void AGSEngine::syncSoundSettings() {
	// Digital audio
	Engine::syncSoundSettings();
	// MIDI
	_music->syncVolume();
}

} // namespace AGS