/*
   Copyright (C) 2010 - 2015 by Fabian Mueller <fabianmueller5@gmx.de>
   Part of the Battle for Wesnoth Project http://www.wesnoth.org/

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2
   or at your option any later version.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.

   See the COPYING file for more details.
*/

#include "pathfind/teleport.hpp"

#include "serialization/string_utils.hpp"
#include "team.hpp"
#include "terrain_filter.hpp"
#include "unit.hpp"
#include "log.hpp"
#include "resources.hpp"

#include <boost/foreach.hpp>

static lg::log_domain log_engine("engine");
#define ERR_PF LOG_STREAM(err, log_engine)

namespace pathfind {


namespace {
	const std::string reversed_suffix = "-__REVERSED__";
}

// This constructor is *only* meant for loading from saves
teleport_group::teleport_group(const config& cfg) : cfg_(cfg), reversed_(cfg["reversed"].to_bool(false)), id_(cfg["id"])
{
	assert(cfg.has_attribute("id"));
	assert(cfg.has_attribute("reversed"));

	assert(cfg_.child_count("source") == 1);
	assert(cfg_.child_count("target") == 1);
	assert(cfg_.child_count("filter") == 1);
}

teleport_group::teleport_group(const vconfig& cfg, bool reversed) : cfg_(cfg.get_config()), reversed_(reversed), id_()
{
	assert(cfg_.child_count("source") == 1);
	assert(cfg_.child_count("target") == 1);
	assert(cfg_.child_count("filter") == 1);
	if (cfg["id"].empty()) {
		id_ = resources::tunnels->next_unique_id();
	} else {
		id_ = cfg["id"].str();
		if (reversed_) // Differentiate the reverse tunnel from the forward one
			id_ += reversed_suffix;
	}
}

void teleport_group::get_teleport_pair(
		  teleport_pair& loc_pair
		, const unit& u
		, const bool ignore_units) const
{
	const map_location &loc = u.get_location();
	static unit_map empty_unit_map;
	unit_map *units;
	if (ignore_units) {
		units = &empty_unit_map;
	} else {
		units = resources::units;
	}
	vconfig filter(cfg_.child_or_empty("filter"), true);
	vconfig source(cfg_.child_or_empty("source"), true);
	vconfig target(cfg_.child_or_empty("target"), true);
	if (u.matches_filter(filter, loc)) {

		scoped_xy_unit teleport_unit("teleport_unit", loc.x, loc.y, *resources::units);

		terrain_filter source_filter(source, *units);
		source_filter.get_locations(reversed_ ? loc_pair.second : loc_pair.first);

		terrain_filter target_filter(target, *units);
		target_filter.get_locations(reversed_ ? loc_pair.first : loc_pair.second);
	}
}

const std::string& teleport_group::get_teleport_id() const {
	return id_;
}

bool teleport_group::always_visible() const {
	return cfg_["always_visible"].to_bool(false);
}

config teleport_group::to_config() const {
	config retval = cfg_;
	retval["saved"] = "yes";
	retval["reversed"] = reversed_ ? "yes" : "no";
	retval["id"] = id_;
	return retval;
}

teleport_map::teleport_map(
		  const std::vector<teleport_group>& groups
		, const unit& u
		, const team &viewing_team
		, const bool see_all
		, const bool ignore_units)
	: teleport_map_()
	, sources_()
	, targets_()
{

	BOOST_FOREACH(const teleport_group& group, groups) {

		teleport_pair locations;
		group.get_teleport_pair(locations, u, ignore_units);
		if (!see_all && !group.always_visible() && viewing_team.is_enemy(u.side())) {
			teleport_pair filter_locs;
			BOOST_FOREACH(const map_location &loc, locations.first)
				if(!viewing_team.fogged(loc))
					filter_locs.first.insert(loc);
			BOOST_FOREACH(const map_location &loc, locations.second)
				if(!viewing_team.fogged(loc))
					filter_locs.second.insert(loc);
			locations.first.swap(filter_locs.first);
			locations.second.swap(filter_locs.second);
		}
		std::string teleport_id = group.get_teleport_id();

		std::set<map_location>::iterator source_it = locations.first.begin();
		for (; source_it != locations.first.end(); ++source_it ) {
			if(teleport_map_.count(*source_it) == 0) {
				std::set<std::string> id_set;
				id_set.insert(teleport_id);
				teleport_map_.insert(std::make_pair(*source_it, id_set));
			} else {
				(teleport_map_.find(*source_it)->second).insert(teleport_id);
			}
		}
		sources_.insert(std::make_pair(teleport_id, locations.first));
		targets_.insert(std::make_pair(teleport_id, locations.second));
	}
}

void teleport_map::get_adjacents(std::set<map_location>& adjacents, map_location loc) const {

	if (teleport_map_.count(loc) == 0) {
		return;
	} else {
		const std::set<std::string>& keyset = (teleport_map_.find(loc)->second);
		for(std::set<std::string>::const_iterator it = keyset.begin(); it != keyset.end(); ++it) {

			const std::set<map_location>& target = targets_.find(*it)->second;
			adjacents.insert(target.begin(), target.end());
		}
	}
}

void teleport_map::get_sources(std::set<map_location>& sources) const {

	std::map<std::string, std::set<map_location> >::const_iterator it;
	for(it = sources_.begin(); it != sources_.end(); ++it) {
		sources.insert(it->second.begin(), it->second.end());
	}
}

void teleport_map::get_targets(std::set<map_location>& targets) const {

	std::map<std::string, std::set<map_location> >::const_iterator it;
	for(it = targets_.begin(); it != targets_.end(); ++it) {
		targets.insert(it->second.begin(), it->second.end());
	}
}


const teleport_map get_teleport_locations(const unit &u,
	const team &viewing_team,
	bool see_all, bool ignore_units)
{
	std::vector<teleport_group> groups;

	if (u.get_ability_bool("teleport")) {
		BOOST_FOREACH (const unit_ability & teleport, u.get_abilities("teleport")) {
			const int tunnel_count = (teleport.first)->child_count("tunnel");
			for(int i = 0; i < tunnel_count; ++i) {
				config teleport_group_cfg = (teleport.first)->child("tunnel", i);
				groups.push_back(teleport_group(vconfig(teleport_group_cfg, true), false));
			}
		}
	}

	const std::vector<teleport_group>& global_groups = resources::tunnels->get();
	groups.insert(groups.end(), global_groups.begin(), global_groups.end());

	return teleport_map(groups, u, viewing_team, see_all, ignore_units);
}

manager::manager(const config &cfg) : tunnels_(), id_(cfg["next_teleport_group_id"].to_int(0)) {
	const int tunnel_count = cfg.child_count("tunnel");
	for(int i = 0; i < tunnel_count; ++i) {
		const config& t = cfg.child("tunnel", i);
		if(!t["saved"].to_bool()) {
			lg::wml_error << "Do not use [tunnel] directly in a [scenario]. Use it in an [event] or [abilities] tag.\n";
			continue;
		}
		const teleport_group tunnel(t);
		this->add(tunnel);
	}
}

void manager::add(const teleport_group &group) {
	tunnels_.push_back(group);
}

void manager::remove(const std::string &id) {
	std::vector<teleport_group>::iterator t = tunnels_.begin();
	for(;t != tunnels_.end();) {
		if (t->get_teleport_id() == id || t->get_teleport_id() == id + reversed_suffix) {
			t = tunnels_.erase(t);
		} else {
			++t;
		}
	}
}

const std::vector<teleport_group>& manager::get() const {
	return tunnels_;
}

config manager::to_config() const {
	config store;

	std::vector<teleport_group>::const_iterator tunnel = tunnels_.begin();
	for(; tunnel != tunnels_.end(); ++tunnel) {
		store.add_child("tunnel", tunnel->to_config());
	}
	store["next_teleport_group_id"] = str_cast(id_);

	return store;
}

std::string manager::next_unique_id() {
	return str_cast(++id_);
}


}//namespace pathfind
