/* 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 "ultima/ultima4/ultima4.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/map/city.h"
#include "ultima/ultima4/controllers/combat_controller.h"
#include "ultima/ultima4/map/dungeon.h"
#include "ultima/ultima4/map/map.h"
#include "ultima/ultima4/map/maploader.h"
#include "ultima/ultima4/map/mapmgr.h"
#include "ultima/ultima4/game/item.h"
#include "ultima/ultima4/game/moongate.h"
#include "ultima/ultima4/game/person.h"
#include "ultima/ultima4/game/portal.h"
#include "ultima/ultima4/map/shrine.h"
#include "ultima/ultima4/map/tilemap.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/ultima4/map/xml_map.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/core/config.h"

namespace Ultima {
namespace Ultima4 {

MapMgr *MapMgr::_instance = nullptr;

MapMgr *MapMgr::getInstance() {
	if (_instance == nullptr)
		_instance = new MapMgr();
	return _instance;
}

void MapMgr::destroy() {
	if (_instance != nullptr) {
		delete _instance;
		_instance = nullptr;
	}
}

MapMgr::MapMgr() {
	const Config *config = Config::getInstance();
	Map *map;

	Std::vector<ConfigElement> maps = config->getElement("maps").getChildren();
	for (const auto &i : maps) {
		map = initMapFromConf(i);

		// Map actually gets loaded later, when it's needed
		registerMap(map);
	}
}

MapMgr::~MapMgr() {
	for (auto *i : _mapList)
		delete i;
}

void MapMgr::unloadMap(MapId id) {
	delete _mapList[id];
	const Config *config = Config::getInstance();
	Std::vector<ConfigElement> maps = config->getElement("maps").getChildren();

	for (const auto &i : maps) {
		if (id == static_cast<MapId>(i.getInt("id"))) {
			Map *map = initMapFromConf(i);
			_mapList[id] = map;
			break;
		}
	}

}

Map *MapMgr::initMap(Map::Type type) {
	Map *map;

	switch (type) {
	case Map::WORLD:
		map = new Map();
		break;

	case Map::COMBAT:
		map = new CombatMap();
		break;

	case Map::SHRINE:
		map = new Shrine();
		break;

	case Map::DUNGEON:
		map = new Dungeon();
		break;

	case Map::CITY:
		map = new City();
		break;

	case Map::XML:
		map = new XMLMap();
		break;

	default:
		error("Error: invalid map type used");
		break;
	}

	return map;
}

Map *MapMgr::get(MapId id) {
	// if the map hasn't been loaded yet, load it!
	if (!_mapList[id]->_data.size()) {
		MapLoader *loader = g_mapLoaders->getLoader(_mapList[id]->_type);
		if (loader == nullptr)
			error("can't load map of type \"%d\"", _mapList[id]->_type);

		loader->load(_mapList[id]);
	}

	return _mapList[id];
}

void MapMgr::registerMap(Map *map) {
	if (_mapList.size() <= map->_id)
		_mapList.resize(map->_id + 1, nullptr);

	if (_mapList[map->_id] != nullptr)
		error("Error: A map with id '%d' already exists", map->_id);

	_mapList[map->_id] = map;
}

Map *MapMgr::initMapFromConf(const ConfigElement &mapConf) {
	Map *map;
	static const char *const mapTypeEnumStrings[] = { "world", "city", "shrine", "combat", "dungeon", "xml", nullptr };
	static const char *const borderBehaviorEnumStrings[] = { "wrap", "exit", "fixed", nullptr };

	map = initMap(static_cast<Map::Type>(mapConf.getEnum("type", mapTypeEnumStrings)));
	if (!map)
		return nullptr;

	map->_id = static_cast<MapId>(mapConf.getInt("id"));
	map->_type = static_cast<Map::Type>(mapConf.getEnum("type", mapTypeEnumStrings));
	map->_fname = mapConf.getString("fname");
	map->_width = mapConf.getInt("width");
	map->_height = mapConf.getInt("height");
	map->_levels = mapConf.getInt("levels");
	map->_chunkWidth = mapConf.getInt("chunkwidth");
	map->_chunkHeight = mapConf.getInt("chunkheight");
	map->_offset = mapConf.getInt("offset");
	map->_borderBehavior = static_cast<Map::BorderBehavior>(mapConf.getEnum("borderbehavior", borderBehaviorEnumStrings));

	if (isCombatMap(map)) {
		CombatMap *cm = dynamic_cast<CombatMap *>(map);
		assert(cm);
		cm->setContextual(mapConf.getBool("contextual"));
	}

	if (mapConf.getBool("showavatar"))
		map->_flags |= SHOW_AVATAR;

	if (mapConf.getBool("nolineofsight"))
		map->_flags |= NO_LINE_OF_SIGHT;

	if (mapConf.getBool("firstperson"))
		map->_flags |= FIRST_PERSON;

	map->_music = static_cast<Music::Type>(mapConf.getInt("music"));
	map->_tileSet = g_tileSets->get(mapConf.getString("tileset"));
	map->_tileMap = g_tileMaps->get(mapConf.getString("tilemap"));

	Std::vector<ConfigElement> children = mapConf.getChildren();
	for (const auto &i : children) {
		if (i.getName() == "city") {
			City *city = dynamic_cast<City *>(map);
			assert(city);
			initCityFromConf(i, city);
		} else if (i.getName() == "shrine") {
			Shrine *shrine = dynamic_cast<Shrine *>(map);
			assert(shrine);
			initShrineFromConf(i, shrine);
		} else if (i.getName() == "dungeon") {
			Dungeon *dungeon = dynamic_cast<Dungeon *>(map);
			assert(dungeon);
			initDungeonFromConf(i, dungeon);
		} else if (i.getName() == "portal")
			map->_portals.push_back(initPortalFromConf(i));
		else if (i.getName() == "moongate")
			createMoongateFromConf(i);
		else if (i.getName() == "compressedchunk")
			map->_compressedChunks.push_back(initCompressedChunkFromConf(i));
		else if (i.getName() == "label")
			map->_labels[i.getString("name")] = MapCoords(i.getInt("x"), i.getInt("y"), i.getInt("z", 0));
		else if (i.getName() == "tiles" && map->_type == Map::XML)
			static_cast<XMLMap *>(map)->_tilesText = i.getNode()->firstChild()->text();
	}

	return map;
}

void MapMgr::initCityFromConf(const ConfigElement &cityConf, City *city) {
	city->_name = cityConf.getString("name");
	city->_type = cityConf.getString("type");
	city->_tlkFname = cityConf.getString("tlk_fname");

	Std::vector<ConfigElement> children = cityConf.getChildren();
	for (const auto &i : children) {
		if (i.getName() == "personrole")
			city->_personRoles.push_back(initPersonRoleFromConf(i));
	}
}

PersonRole *MapMgr::initPersonRoleFromConf(const ConfigElement &personRoleConf) {
	PersonRole *personrole;
	static const char *const roleEnumStrings[] = { "companion", "weaponsvendor", "armorvendor", "foodvendor", "tavernkeeper",
	                                         "reagentsvendor", "healer", "innkeeper", "guildvendor", "horsevendor",
	                                         "lordbritish", "hawkwind", nullptr
	                                       };

	personrole = new PersonRole();

	personrole->_role = personRoleConf.getEnum("role", roleEnumStrings) + NPC_TALKER_COMPANION;
	personrole->_id = personRoleConf.getInt("id");

	return personrole;
}

Portal *MapMgr::initPortalFromConf(const ConfigElement &portalConf) {
	Portal *portal;

	portal = new Portal();

	portal->_portalConditionsMet = nullptr;
	portal->_retroActiveDest = nullptr;

	portal->_coords = MapCoords(
	                      portalConf.getInt("x"),
	                      portalConf.getInt("y"),
	                      portalConf.getInt("z", 0));
	portal->_destid = static_cast<MapId>(portalConf.getInt("destmapid"));

	portal->_start.x = static_cast<unsigned short>(portalConf.getInt("startx"));
	portal->_start.y = static_cast<unsigned short>(portalConf.getInt("starty"));
	portal->_start.z = static_cast<unsigned short>(portalConf.getInt("startlevel", 0));

	Common::String prop = portalConf.getString("action");
	if (prop == "none")
		portal->_triggerAction = ACTION_NONE;
	else if (prop == "enter")
		portal->_triggerAction = ACTION_ENTER;
	else if (prop == "klimb")
		portal->_triggerAction = ACTION_KLIMB;
	else if (prop == "descend")
		portal->_triggerAction = ACTION_DESCEND;
	else if (prop == "exit_north")
		portal->_triggerAction = ACTION_EXIT_NORTH;
	else if (prop == "exit_east")
		portal->_triggerAction = ACTION_EXIT_EAST;
	else if (prop == "exit_south")
		portal->_triggerAction = ACTION_EXIT_SOUTH;
	else if (prop == "exit_west")
		portal->_triggerAction = ACTION_EXIT_WEST;
	else
		error("unknown trigger_action: %s", prop.c_str());

	prop = portalConf.getString("condition");
	if (!prop.empty()) {
		if (prop == "shrine")
			portal->_portalConditionsMet = &shrineCanEnter;
		else if (prop == "abyss")
			portal->_portalConditionsMet = &Items::isAbyssOpened;
		else
			error("unknown portalConditionsMet: %s", prop.c_str());
	}

	portal->_saveLocation = portalConf.getBool("savelocation");

	portal->_message = portalConf.getString("message");

	prop = portalConf.getString("transport");
	if (prop == "foot")
		portal->_portalTransportRequisites = TRANSPORT_FOOT;
	else if (prop == "footorhorse")
		portal->_portalTransportRequisites = TRANSPORT_FOOT_OR_HORSE;
	else
		error("unknown transport: %s", prop.c_str());

	portal->_exitPortal = portalConf.getBool("exits");

	// Used as a shortcut for specifying the display tile
	// for new/fan maps being added to the overworld
	portal->_tile = portalConf.exists("tile") ? portalConf.getInt("tile") : -1;

	Std::vector<ConfigElement> children = portalConf.getChildren();
	for (const auto &i : children) {
		if (i.getName() == "retroActiveDest") {
			portal->_retroActiveDest = new PortalDestination();

			portal->_retroActiveDest->_coords = MapCoords(
			                                        i.getInt("x"),
			                                        i.getInt("y"),
			                                        i.getInt("z", 0));
			portal->_retroActiveDest->_mapid = static_cast<MapId>(i.getInt("mapid"));
		}
	}

	return portal;
}

void MapMgr::initShrineFromConf(const ConfigElement &shrineConf, Shrine *shrine) {
	static const char *const virtues[] = {"Honesty", "Compassion", "Valor", "Justice", "Sacrifice", "Honor", "Spirituality", "Humility", nullptr};

	shrine->setVirtue(static_cast<Virtue>(shrineConf.getEnum("virtue", virtues)));
	shrine->setMantra(shrineConf.getString("mantra"));
}

void MapMgr::initDungeonFromConf(const ConfigElement &dungeonConf, Dungeon *dungeon) {
	dungeon->_nRooms = dungeonConf.getInt("rooms");
	dungeon->_rooms = nullptr;
	dungeon->_roomMaps = nullptr;
	dungeon->_name = dungeonConf.getString("name");
}

void MapMgr::createMoongateFromConf(const ConfigElement &moongateConf) {
	int phase = moongateConf.getInt("phase");
	Coords coords(moongateConf.getInt("x"), moongateConf.getInt("y"));

	g_moongates->add(phase, coords);
}

int MapMgr::initCompressedChunkFromConf(const ConfigElement &compressedChunkConf) {
	return compressedChunkConf.getInt("index");
}

} // End of namespace Ultima4
} // End of namespace Ultima
