/*
   Copyright (C) 2010 - 2015 by Gabriel Morin <gabrielmorin (at) gmail (dot) com>
   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 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.

   See the COPYING file for more details.
*/

/**
 * @file
 * Method bodies for the arrow class.
 */

#include "arrow.hpp"

#include "game_display.hpp"
#include "log.hpp"
#include "resources.hpp"

#include <boost/foreach.hpp>

static lg::log_domain log_arrows("arrows");
#define ERR_ARR LOG_STREAM(err, log_arrows)
#define WRN_ARR LOG_STREAM(warn, log_arrows)
#define LOG_ARR LOG_STREAM(info, log_arrows)
#define DBG_ARR LOG_STREAM(debug, log_arrows)

#define SCREEN (static_cast<display*>(resources::screen))

arrow::arrow(bool hidden)
	: layer_(display::LAYER_ARROWS)
	, color_("red")
	, style_(STYLE_STANDARD)
	, path_()
	, previous_path_()
	, symbols_map_()
	, hidden_(true)
{
	if(!hidden)
		show();
}

arrow::~arrow()
{
	hide();
}

void arrow::hide()
{
	if(hidden_)
		return;
	hidden_ = true;

	if (SCREEN)
	{
		invalidate_arrow_path(path_);
		SCREEN->remove_arrow(*this);
	}
}

void arrow::show()
{
	if(!hidden_)
		return;
	hidden_ = false;

	if(SCREEN)
		SCREEN->add_arrow(*this);
}

void arrow::set_path(arrow_path_t const& path)
{
	if (valid_path(path))
	{
		previous_path_ = path_;
		path_ = path;
		update_symbols();
		if(!hidden_)
		{
			invalidate_arrow_path(previous_path_);
			notify_arrow_changed();
		}
	}
}

void arrow::reset()
{
	invalidate_arrow_path(previous_path_);
	invalidate_arrow_path(path_);
	previous_path_.clear();
	path_.clear();
	symbols_map_.clear();
	notify_arrow_changed();
}

void arrow::set_color(std::string const& color)
{
	color_ = color;
	if (valid_path(path_))
	{
		update_symbols();
	}
}

std::string const arrow::STYLE_STANDARD = "standard";
std::string const arrow::STYLE_HIGHLIGHTED = "highlighted";
std::string const arrow::STYLE_FOCUS = "focus";
std::string const arrow::STYLE_FOCUS_INVALID = "focus_invalid";

void arrow::set_style(const std::string& style)
{
	style_ = style;
	if (valid_path(path_))
	{
		update_symbols();
	}
}

arrow_path_t const& arrow::get_path() const
{
	return path_;
}

arrow_path_t const& arrow::get_previous_path() const
{
	return previous_path_;
}

bool arrow::path_contains(map_location const& hex) const
{
	bool contains = symbols_map_.find(hex) != symbols_map_.end();
	return contains;
}

void arrow::draw_hex(map_location const& hex)
{
	if(path_contains(hex))
	{
		SCREEN->render_image(SCREEN->get_location_x(hex), SCREEN->get_location_y(hex), layer_,
					hex, image::get_image(symbols_map_[hex], image::SCALED_TO_ZOOM));
	}
}

bool arrow::valid_path(arrow_path_t const& path)
{
	if (path.size() >= 2)
		return true;
	else
		return false;
}

void arrow::update_symbols()
{
	if (!valid_path(path_))
	{
		WRN_ARR << "arrow::update_symbols called with invalid path\n";
		return;
	}

	symbols_map_.clear();
	invalidate_arrow_path(path_);

	std::string const mods = "~RC(FF00FF>"+ color_ + ")"; //magenta to current color

	std::string const dirname = "arrows/";
	std::string prefix = "";
	std::string suffix = "";
	std::string image_filename = "";
	arrow_path_t::const_iterator const arrow_start_hex = path_.begin();
	arrow_path_t::const_iterator const arrow_pre_end_hex = path_.end() - 2;
	arrow_path_t::const_iterator const arrow_end_hex = path_.end() - 1;
	bool teleport_out = false;

	arrow_path_t::iterator hex;
	for (hex = path_.begin(); hex != path_.end(); ++hex)
	{
		prefix = "";
		suffix = "";
		image_filename = "";
		bool start = false;
		bool pre_end = false;
		bool end = false;

		// teleport in if we teleported out last hex
		bool teleport_in = teleport_out;
		teleport_out = false;

		// Determine some special cases
		if (hex == arrow_start_hex)
			start = true;
		if (hex == arrow_pre_end_hex)
			pre_end = true;
		else if (hex == arrow_end_hex)
			end = true;
		if (hex != arrow_end_hex && !tiles_adjacent(*hex, *(hex + 1)))
			teleport_out = true;

		// calculate enter and exit directions, if available
		map_location::DIRECTION enter_dir = map_location::NDIRECTIONS;
		if (!start && !teleport_in)
		{
			enter_dir = hex->get_relative_dir(*(hex-1));
		}
		map_location::DIRECTION exit_dir = map_location::NDIRECTIONS;
		if (!end && !teleport_out)
		{
			exit_dir = hex->get_relative_dir(*(hex+1));
		}

		// Now figure out the actual images
		if (teleport_out)
		{
			prefix = "teleport-out";
			if (enter_dir != map_location::NDIRECTIONS)
			{
				suffix = map_location::write_direction(enter_dir);
			}
		}
		else if (teleport_in)
		{
			prefix = "teleport-in";
			if (exit_dir != map_location::NDIRECTIONS)
			{
				suffix = map_location::write_direction(exit_dir);
			}
		}
		else if (start)
		{
			prefix = "start";
			suffix = map_location::write_direction(exit_dir);
			if (pre_end)
			{
				suffix = suffix + "_ending";
			}
		}
		else if (end)
		{
			prefix = "end";
			suffix = map_location::write_direction(enter_dir);
		}
		else
		{
			std::string enter, exit;
			enter = map_location::write_direction(enter_dir);
			exit = map_location::write_direction(exit_dir);
			if (pre_end)
			{
				exit = exit + "_ending";
			}

			assert(abs(enter_dir - exit_dir) > 1); //impossible turn?
			if (enter_dir < exit_dir)
			{
				prefix = enter;
				suffix = exit;
			}
			else //(enter_dir > exit_dir)
			{
				prefix = exit;
				suffix = enter;
			}
		}

		image_filename = dirname + style_ + "/" + prefix;
		if (suffix != "")
		{
			image_filename += ("-" + suffix);
		}
		image_filename += ".png";
		assert(image_filename != "");

		image::locator image = image::locator(image_filename, mods);
		if (!image.file_exists())
		{
			ERR_ARR << "Image " << image_filename << " not found.\n";
			image = image::locator(game_config::images::missing);
		}
		symbols_map_[*hex] = image;
	}
}

void arrow::invalidate_arrow_path(arrow_path_t const& path)
{
	if(!SCREEN) return;

	BOOST_FOREACH(map_location const& loc, path)
	{
		SCREEN->invalidate(loc);
	}
}

void arrow::notify_arrow_changed()
{
	if(!SCREEN) return;

	SCREEN->update_arrow(*this);
}
