/*
   Copyright (C) 2006 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
   wesnoth playturn Copyright (C) 2003 by David White <davidnwhite@verizon.net>
   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.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.

   See the COPYING file for more details.
*/

#include "mouse_events.hpp"

#include "attack_prediction.hpp"
#include "cursor.hpp"
#include "dialogs.hpp"
#include "game_events.hpp"
#include "gettext.hpp"
#include "marked-up_text.hpp"
#include "menu_events.hpp"
#include "preferences_display.hpp"
#include "replay.hpp"
#include "show_dialog.hpp"
#include "unit_abilities.hpp"
#include "wassert.hpp"
#include "wml_separators.hpp"

namespace events{

int commands_disabled = 0;

command_disabler::command_disabler()
{
	++commands_disabled;
}

command_disabler::~command_disabler()
{
	--commands_disabled;
}

bool command_active()
{
#ifdef __APPLE__
	return (SDL_GetModState()&KMOD_META) != 0;
#else
	return false;
#endif
}

namespace{
	// This preview pane is shown in the "Damage Calculations" dialog.
	class battle_prediction_pane : public gui::preview_pane
	{
	public:

		// Lengthy constructor.
		battle_prediction_pane(display &disp, const battle_context& bc, const gamemap& map,
							   const std::vector<team>& teams, const unit_map& units,
							   const gamestatus& status, const game_data& gamedata,
							   const gamemap::location& attacker_loc, const gamemap::location& defender_loc);

		// This method is called to draw the dialog contents.
		void draw_contents();

		// Hack: pretend the preview pane goes to the left.
		bool left_side() const { return 1; }

		// Unused.
		void set_selection(int) {}

	private:
		display &disp_;
		const battle_context& bc_;
		const gamemap& map_;
		const std::vector<team>& teams_;
		const unit_map& units_;
		const gamestatus& status_;
		const game_data& gamedata_;
		const gamemap::location& attacker_loc_;
		const gamemap::location& defender_loc_;
		const unit& attacker_;
		const unit& defender_;

		// Layout constants.
		static const int inter_line_gap_;
		static const int inter_column_gap_;
		static const int inter_units_gap_;
		static const int max_hp_distrib_rows_;

		// Layout computations.
		std::string attacker_label_, defender_label_;
		int attacker_label_width_, defender_label_width_;

		std::vector<std::string> attacker_left_strings_, attacker_right_strings_;
		std::vector<std::string> defender_left_strings_, defender_right_strings_;
		int attacker_strings_width_, attacker_left_strings_width_, attacker_right_strings_width_;
		int defender_strings_width_, defender_left_strings_width_, defender_right_strings_width_;
		int units_strings_height_;

		std::string hp_distrib_string_;
		surface attacker_hp_distrib_, defender_hp_distrib_;
		int hp_distrib_string_width_;
		int attacker_hp_distrib_width_, defender_hp_distrib_width_;
		int attacker_hp_distrib_height_, defender_hp_distrib_height_, hp_distribs_height_;

		int attacker_width_, defender_width_, units_width_;
		int dialog_width_, dialog_height_;

		// This method builds the strings describing the unit damage modifiers.
		// Read the code to understand the arguments.
		void get_unit_strings(const battle_context::unit_stats& stats,
						  const unit& u, const gamemap::location& u_loc, float u_unscathed,
						  const unit& opp, const gamemap::location& opp_loc, const attack_type *opp_weapon,
						  std::vector<std::string>& left_strings, std::vector<std::string>& right_strings,
					      int& left_strings_width, int& right_strings_width, int& strings_width);

		// Utility method that returns the length of the longest string in a vector of strings.
		int get_strings_max_length(const std::vector<std::string>& strings);

		// This method builds the vector containing the <HP, probability> pairs
		// that are required to draw the image of the hitpoints distribution of
		// a combatant after a fight. The method takes as input the probability
		// distribution of the hitpoints of the combatant after the fight.
		void get_hp_prob_vector(const std::vector<double>& hp_dist,
								std::vector<std::pair<int, double> >& hp_prob_vector);

		// This method draws a unit in the dialog pane. Read the code to understand
		// the arguments.
		void draw_unit(int x_off, int damage_line_skip, int left_strings_width,
					   const std::vector<std::string>& left_strings,
					   const std::vector<std::string>& right_strings,
					   const std::string& label, int label_width,
					   surface& hp_distrib, int hp_distrib_width);

		// This method draws the image of the hitpoints distribution of a
		// combatant after a fight. The method takes as input the
		// "hp_prob_vector" computed above and the stats of the combatants.
		// It draws the image in the surface 'surf' and set the width and
		// height of the image in the fields specified.
		void get_hp_distrib_surface(const std::vector<std::pair<int, double> >& hp_prob_vector,
								const battle_context::unit_stats& stats,
									const battle_context::unit_stats& opp_stats,
									surface& surf, int& width, int& height);

		// This method blends a RGB color. The method takes as input a surface,
		// the RGB color to blend and a value specifying how much blending to
		// apply. The blended color is returned. Caution: if you use a
		// transparent color, make sure the resulting color is not equal to the
		// transparent color.
		Uint32 blend_rgb(const surface& surf, unsigned char r, unsigned char g, unsigned char b, unsigned char drop);
	};

	const int battle_prediction_pane::inter_line_gap_ = 3;
	const int battle_prediction_pane::inter_column_gap_ = 30;
	const int battle_prediction_pane::inter_units_gap_ = 30;
	const int battle_prediction_pane::max_hp_distrib_rows_ = 10;

	battle_prediction_pane::battle_prediction_pane(display &disp, const battle_context& bc, const gamemap& map,
												   const std::vector<team>& teams, const unit_map& units,
												   const gamestatus& status, const game_data& gamedata,
												   const gamemap::location& attacker_loc, const gamemap::location& defender_loc)
				: gui::preview_pane(disp.video()), disp_(disp), bc_(bc), map_(map), teams_(teams), units_(units), status_(status),
				  gamedata_(gamedata), attacker_loc_(attacker_loc), defender_loc_(defender_loc),
				  attacker_(units.find(attacker_loc)->second), defender_(units.find(defender_loc)->second)
	{
		// Predict the battle outcome.
		combatant attacker_combatant(bc.get_attacker_stats());
		combatant defender_combatant(bc.get_defender_stats());
		attacker_combatant.fight(defender_combatant);

		const battle_context::unit_stats& attacker_stats = bc.get_attacker_stats();
		const battle_context::unit_stats& defender_stats = bc.get_defender_stats();

		// Create the hitpoints distribution graphics.
		std::vector<std::pair<int, double> > hp_prob_vector;
		get_hp_prob_vector(attacker_combatant.hp_dist, hp_prob_vector);
		get_hp_distrib_surface(hp_prob_vector, attacker_stats, defender_stats, attacker_hp_distrib_,
							   attacker_hp_distrib_width_, attacker_hp_distrib_height_);
		get_hp_prob_vector(defender_combatant.hp_dist, hp_prob_vector);
		get_hp_distrib_surface(hp_prob_vector, defender_stats, attacker_stats, defender_hp_distrib_,
						   defender_hp_distrib_width_, defender_hp_distrib_height_);
		hp_distribs_height_ = maximum<int>(attacker_hp_distrib_height_, defender_hp_distrib_height_);

		// Build the strings and compute the layout.
		std::stringstream str;

		attacker_label_ = _("Attacker");
		defender_label_ = _("Defender");
		attacker_label_width_ = font::line_width(attacker_label_, font::SIZE_PLUS, TTF_STYLE_BOLD);
		defender_label_width_ = font::line_width(defender_label_, font::SIZE_PLUS, TTF_STYLE_BOLD);

		// Get the units strings.
		get_unit_strings(attacker_stats, attacker_, attacker_loc_, attacker_combatant.untouched,
						 defender_, defender_loc_, defender_stats.weapon,
						 attacker_left_strings_, attacker_right_strings_,
						 attacker_left_strings_width_, attacker_right_strings_width_, attacker_strings_width_);

		get_unit_strings(defender_stats, defender_, defender_loc_, defender_combatant.untouched,
						 attacker_, attacker_loc_, attacker_stats.weapon,
						 defender_left_strings_, defender_right_strings_,
						 defender_left_strings_width_, defender_right_strings_width_, defender_strings_width_);

		units_strings_height_ = maximum<int>(attacker_left_strings_.size(), defender_left_strings_.size())
							    * (font::SIZE_NORMAL + inter_line_gap_) + 14;

		hp_distrib_string_ = _("Expected Battle Result (HP)");
		hp_distrib_string_width_ = font::line_width(hp_distrib_string_, font::SIZE_SMALL);

		attacker_width_ = maximum<int>(attacker_label_width_, attacker_strings_width_);
		attacker_width_ = maximum<int>(attacker_width_, hp_distrib_string_width_);
		attacker_width_ = maximum<int>(attacker_width_, attacker_hp_distrib_width_);
		defender_width_ = maximum<int>(defender_label_width_, defender_strings_width_);
		defender_width_ = maximum<int>(defender_width_, hp_distrib_string_width_);
		defender_width_ = maximum<int>(defender_width_, defender_hp_distrib_width_);
		units_width_ = maximum<int>(attacker_width_, defender_width_);

		dialog_width_ = 2 * units_width_ + inter_units_gap_;
		dialog_height_ = 15 + 24 + units_strings_height_ + 14 + 19 + hp_distribs_height_ + 18;

		// Set the dialog size.
		set_measurements(dialog_width_, dialog_height_);
	}

	void battle_prediction_pane::get_unit_strings(const battle_context::unit_stats& stats,
											  const unit& u, const gamemap::location& u_loc, float u_unscathed,
											  const unit& opp, const gamemap::location& opp_loc, const attack_type *opp_weapon,
												  std::vector<std::string>& left_strings, std::vector<std::string>& right_strings,
										      int& left_strings_width, int& right_strings_width, int& strings_width)
	{
		std::stringstream str;
		char str_buf[10];

		// With a weapon.
		if(stats.weapon != NULL) {

			// Set specials context (for safety, it should not have changed normally).
			const attack_type *weapon = stats.weapon;
			weapon->set_specials_context(u_loc, opp_loc, &gamedata_, &units_, &map_, &status_, &teams_, stats.is_attacker, opp_weapon);

			// Get damage modifiers.
			unit_ability_list dmg_specials = weapon->get_specials("damage");
			unit_abilities::effect dmg_effect(dmg_specials, weapon->damage(), stats.backstab_pos);

			// Get the SET damage modifier, if any.
			const unit_abilities::individual_effect *set_dmg_effect = NULL;
			unit_abilities::effect_list::const_iterator i;
			for(i = dmg_effect.begin(); i != dmg_effect.end(); ++i) {
				if(i->type == unit_abilities::SET) {
					set_dmg_effect = &*i;
					break;
				}
			}

			// Either user the SET modifier or the base weapon damage.
			if(set_dmg_effect == NULL) {
				left_strings.push_back(weapon->name());
				str.str("");
				str << weapon->damage();
				right_strings.push_back(str.str());
			} else {
				left_strings.push_back((*set_dmg_effect->ability)["name"]);
				str.str("");
				str << set_dmg_effect->value;
				right_strings.push_back(str.str());
			}

			// Process the ADD damage modifiers.
			for(i = dmg_effect.begin(); i != dmg_effect.end(); ++i) {
				if(i->type == unit_abilities::ADD) {
					left_strings.push_back((*i->ability)["name"]);
					str.str("");
					if(i->value >= 0) str << "+" << i->value;
					else str << i->value;
					right_strings.push_back(str.str());
				}
			}

			// Process the MUL damage modifiers.
			for(i = dmg_effect.begin(); i != dmg_effect.end(); ++i) {
				if(i->type == unit_abilities::MUL) {
					left_strings.push_back((*i->ability)["name"]);
					str.str("");
					str << "* " << (i->value / 100);
					if(i->value % 100) {
						str << "." << ((i->value % 100) / 10);
						if(i->value % 10) str << (i->value % 10);
					}
					right_strings.push_back(str.str());
				}
			}

			// Resistance modifier.
			int resistance_modifier = opp.damage_from(*weapon, !stats.is_attacker, opp_loc);
			if(resistance_modifier != 100) {
				str.str("");
				if(stats.is_attacker) str << _("Defender");
				else str << _("Attacker");
				if(resistance_modifier < 100) str << _(" resistance vs ");
				else str << _(" vulnerability vs ");
				str << gettext(weapon->type().c_str());
				left_strings.push_back(str.str());
				str.str("");
				str << "* " << (resistance_modifier / 100) << "." << ((resistance_modifier % 100) / 10);
				right_strings.push_back(str.str());
			}

			// Slowed penalty.
			if(stats.is_slowed) {
				left_strings.push_back(_("Slowed"));
				right_strings.push_back("* 0.5");
			}

			// Time of day modifier.
			int tod_modifier = combat_modifier(status_, units_, u_loc, u.alignment(), map_);
			if(tod_modifier != 0) {
				left_strings.push_back(_("Time of day"));
				str.str("");
				str << (tod_modifier > 0 ? "+" : "") << tod_modifier << "%";
				right_strings.push_back(str.str());
			}

	// Leadership bonus.
	int leadership_bonus = 0;
	under_leadership(units_, u_loc, &leadership_bonus);
			if(leadership_bonus != 0) {
				left_strings.push_back(_("Leadership"));
				str.str("");
				str << "+" << leadership_bonus << "%";
				right_strings.push_back(str.str());
			}

			// Total damage.
			left_strings.push_back(_("Total damage"));
			str.str("");
			str << stats.damage << "-" << stats.num_blows << " (" << stats.chance_to_hit << "%)";
			right_strings.push_back(str.str());

		// Without a weapon.
		} else {
			left_strings.push_back(_("No usable weapon"));
			right_strings.push_back("");
		}

		// Unscathed probability.
		left_strings.push_back(_("Chance of being unscathed"));
		snprintf(str_buf, 10, "%.1f%%", (float) (u_unscathed * 100.0));
		str_buf[9] = '\0';  //prevents _snprintf error
		right_strings.push_back(str_buf);

#if 0 // might not be en English!
		// Fix capitalisation of left strings.
		for(int i = 0; i < (int) left_strings.size(); i++)
			if(left_strings[i].size() > 0) left_strings[i][0] = toupper(left_strings[i][0]);
#endif

		// Compute the width of the strings.
		left_strings_width = get_strings_max_length(left_strings);
		right_strings_width = get_strings_max_length(right_strings);
		strings_width = left_strings_width + inter_column_gap_ + right_strings_width;
	}

	int battle_prediction_pane::get_strings_max_length(const std::vector<std::string>& strings)
	{
		int max_len = 0;

		for(int i = 0; i < (int) strings.size(); i++)
			max_len = maximum<int>(font::line_width(strings[i], font::SIZE_NORMAL), max_len);

		return max_len;
	}

	void battle_prediction_pane::get_hp_prob_vector(const std::vector<double>& hp_dist,
													std::vector<std::pair<int, double> >& hp_prob_vector)
	{
		hp_prob_vector.clear();

		// First, we sort the probabilities in ascending order.
		std::vector<std::pair<double, int> > prob_hp_vector;
		int i;

		for(i = 0; i < (int) hp_dist.size(); i++) {
			double prob = hp_dist[i];

			// We keep only values above 0.1%.
			if(prob > 0.001)
				prob_hp_vector.push_back(std::pair<double, int>(prob, i));
		}

		std::sort(prob_hp_vector.begin(), prob_hp_vector.end());

		// We store a few of the highest probability hitpoint values.
		int nb_elem = minimum<int>(max_hp_distrib_rows_, prob_hp_vector.size());

		for(i = prob_hp_vector.size() - nb_elem; i < (int) prob_hp_vector.size(); i++)
			hp_prob_vector.push_back(std::pair<int, double>(prob_hp_vector[i].second, prob_hp_vector[i].first));

		// Then, we sort the hitpoint values in ascending order.
		std::sort(hp_prob_vector.begin(), hp_prob_vector.end());
	}

	void battle_prediction_pane::draw_contents()
	{
		// We must align both damage lines.
		int damage_line_skip = maximum<int>(attacker_left_strings_.size(), defender_left_strings_.size()) - 2;

		draw_unit(0, damage_line_skip,
				  attacker_left_strings_width_, attacker_left_strings_, attacker_right_strings_,
				  attacker_label_, attacker_label_width_, attacker_hp_distrib_, attacker_hp_distrib_width_);

		draw_unit(units_width_ + inter_units_gap_, damage_line_skip,
				  defender_left_strings_width_, defender_left_strings_, defender_right_strings_,
				  defender_label_, defender_label_width_, defender_hp_distrib_, defender_hp_distrib_width_);
	}

	void battle_prediction_pane::draw_unit(int x_off, int damage_line_skip, int left_strings_width,
										   const std::vector<std::string>& left_strings,
										   const std::vector<std::string>& right_strings,
										   const std::string& label, int label_width,
										   surface& hp_distrib, int hp_distrib_width)
	{
		CVideo& screen = disp_.video();
		int i;

		// NOTE. A preview pane is not made to be used alone and it is not
		// centered in the middle of the dialog. We "fix" this problem by moving
		// the clip rectangle 10 pixels to the right. This is a kludge and it
		// should be removed by 1) writing a custom dialog handler, or
		// 2) modify preview_pane so that it accepts {left, middle, right} as
		// layout possibilities.

		// Get clip rectangle and center it
		SDL_Rect clip_rect = location();
		clip_rect.x += 10;

		// Current vertical offset. We draw the dialog line-by-line, starting at the top.
		int y_off = 15;

		// Draw unit label.
		font::draw_text_line(&screen, clip_rect, font::SIZE_15, font::NORMAL_COLOUR, label,
							 clip_rect.x + x_off + (units_width_ - label_width) / 2, clip_rect.y + y_off, 0, TTF_STYLE_BOLD);

		y_off += 24;

		// Draw unit left and right strings except the last two (total damage and unscathed probability).
		for(i = 0; i < (int) left_strings.size() - 2; i++) {
			font::draw_text_line(&screen, clip_rect, font::SIZE_NORMAL, font::NORMAL_COLOUR, left_strings[i],
								 clip_rect.x + x_off, clip_rect.y + y_off + (font::SIZE_NORMAL + inter_line_gap_) * i,
								 0, TTF_STYLE_NORMAL);

			font::draw_text_line(&screen, clip_rect, font::SIZE_NORMAL, font::NORMAL_COLOUR, right_strings[i],
								 clip_rect.x + x_off + left_strings_width + inter_column_gap_,
								 clip_rect.y + y_off + (font::SIZE_NORMAL + inter_line_gap_) * i, 0, TTF_STYLE_NORMAL);
		}

		// Ensure both damage lines are aligned.
		y_off += damage_line_skip * (font::SIZE_NORMAL + inter_line_gap_) + 14;

		// Draw total damage and unscathed probability.
		for(i = 0; i < 2; i++) {
			const std::string& left_string = left_strings[left_strings.size() - 2 + i];
			const std::string& right_string = right_strings[right_strings.size() - 2 + i];

			font::draw_text_line(&screen, clip_rect, font::SIZE_NORMAL, font::NORMAL_COLOUR, left_string,
								 clip_rect.x + x_off, clip_rect.y + y_off + (font::SIZE_NORMAL + inter_line_gap_) * i,
								 0, TTF_STYLE_NORMAL);

			font::draw_text_line(&screen, clip_rect, font::SIZE_NORMAL, font::NORMAL_COLOUR, right_string,
								 clip_rect.x + x_off + left_strings_width + inter_column_gap_,
								 clip_rect.y + y_off + (font::SIZE_NORMAL + inter_line_gap_) * i, 0, TTF_STYLE_NORMAL);
		}

		y_off += 2 * (font::SIZE_NORMAL + inter_line_gap_) + 14;

		// Draw hitpoints distribution string.
		font::draw_text(&screen, clip_rect, font::SIZE_SMALL, font::NORMAL_COLOUR, hp_distrib_string_,
						clip_rect.x + x_off + (units_width_ - hp_distrib_string_width_) / 2, clip_rect.y + y_off);

		y_off += 19;

		// Draw hitpoints distributions.
		video().blit_surface(clip_rect.x + x_off + (units_width_ - hp_distrib_width) / 2, clip_rect.y + y_off, hp_distrib);
	}

	void battle_prediction_pane::get_hp_distrib_surface(const std::vector<std::pair<int, double> >& hp_prob_vector,
														const battle_context::unit_stats& stats,
														const battle_context::unit_stats& opp_stats,
														surface& surf, int& width, int& height)
	{
		// Font size. If you change this, you must update the separator space.
		int fs = font::SIZE_SMALL;

		// Space before HP separator.
		int hp_sep = 24 + 6;

		// Bar space between both separators.
		int bar_space = 150;

		// Space after percentage separator.
		int percent_sep = 43 + 6;

		// Surface width and height.
		width = 30 + 2 + bar_space + 2 + percent_sep;
		height = 5 + (fs + 2) * hp_prob_vector.size();

		// Create the surface.
		surf = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height,
									image::pixel_format->BitsPerPixel,
								image::pixel_format->Rmask,
								image::pixel_format->Gmask,
								image::pixel_format->Bmask,
								image::pixel_format->Amask);

		SDL_Rect clip_rect = {0, 0, width, height};
		Uint32 grey_color = SDL_MapRGB(surf->format, 0xb7, 0xc1, 0xc1);
		Uint32 transparent_color = SDL_MapRGB(surf->format, 1, 1, 1);

		// Enable transparency.
		SDL_SetColorKey(surf, SDL_SRCCOLORKEY, transparent_color);
		SDL_FillRect(surf, &clip_rect, transparent_color);

		// Draw the surrounding borders and separators.
		SDL_Rect top_border_rect = {0, 0, width, 2};
		SDL_FillRect(surf, &top_border_rect, grey_color);

		SDL_Rect bottom_border_rect = {0, height - 2, width, 2};
		SDL_FillRect(surf, &bottom_border_rect, grey_color);

		SDL_Rect left_border_rect = {0, 0, 2, height};
		SDL_FillRect(surf, &left_border_rect, grey_color);

		SDL_Rect right_border_rect = {width - 2, 0, 2, height};
		SDL_FillRect(surf, &right_border_rect, grey_color);

		SDL_Rect hp_sep_rect = {hp_sep, 0, 2, height};
		SDL_FillRect(surf, &hp_sep_rect, grey_color);

		SDL_Rect percent_sep_rect = {width - percent_sep - 2, 0, 2, height};
		SDL_FillRect(surf, &percent_sep_rect, grey_color);

		// Draw the rows (lower HP values are at the bottom).
		for(int i = 0; i < (int) hp_prob_vector.size(); i++) {
			char str_buf[10];

			// Get the HP and probability.
			int hp = hp_prob_vector[hp_prob_vector.size() - i - 1].first;
			double prob = hp_prob_vector[hp_prob_vector.size() - i - 1].second;

			SDL_Color row_color;

			// Death line is red.
			if(hp == 0) {
				SDL_Color color = {0xe5, 0, 0, 0};
				row_color = color;
			}

			// Below current hitpoints value is orange.
			else if(hp < (int) stats.hp) {
				// Stone is grey.
				if(opp_stats.stones) {
					SDL_Color color = {0x9a, 0x9a, 0x9a, 0};
					row_color = color;
				} else {
					SDL_Color color = {0xf4, 0xc9, 0, 0};
					row_color = color;
				}
			}

			// Current hitpoints value and above is green.
			else {
				SDL_Color color = {0x08, 0xca, 0, 0};
				row_color = color;
			}

			// Print HP, aligned right.
			snprintf(str_buf, 10, "%d", hp);
			str_buf[9] = '\0';  //prevents _snprintf error
			int hp_width = font::line_width(str_buf, fs);

			// Draw bars.
			font::draw_text_line(surf, clip_rect, fs, font::NORMAL_COLOUR, str_buf,
								 hp_sep - hp_width - 2, 2 + (fs + 2) * i, 0, TTF_STYLE_NORMAL);

			int bar_len = maximum<int>((int) ((prob * (bar_space - 4)) + 0.5), 2);

			SDL_Rect bar_rect_1 = {hp_sep + 4, 6 + (fs + 2) * i, bar_len, 8};
			SDL_FillRect(surf, &bar_rect_1, blend_rgb(surf, row_color.r, row_color.g, row_color.b, 100));

			SDL_Rect bar_rect_2 = {hp_sep + 4, 7 + (fs + 2) * i, bar_len, 6};
			SDL_FillRect(surf, &bar_rect_2, blend_rgb(surf, row_color.r, row_color.g, row_color.b, 66));

			SDL_Rect bar_rect_3 = {hp_sep + 4, 8 + (fs + 2) * i, bar_len, 4};
			SDL_FillRect(surf, &bar_rect_3, blend_rgb(surf, row_color.r, row_color.g, row_color.b, 33));

			SDL_Rect bar_rect_4 = {hp_sep + 4, 9 + (fs + 2) * i, bar_len, 2};
			SDL_FillRect(surf, &bar_rect_4, blend_rgb(surf, row_color.r, row_color.g, row_color.b, 0));

			// Draw probability percentage, aligned right.
			const char *prob_str_format = NULL;

			if(prob > 0.9995) prob_str_format = "100 %%";
			else if(prob >= 0.1) prob_str_format = "%4.1f %%";
			else prob_str_format = " %3.1f %%";

			snprintf(str_buf, 10, prob_str_format, (float) (100.0 * (prob + 0.0005)));
			str_buf[9] = '\0';  //prevents _snprintf error
			int prob_width = font::line_width(str_buf, fs);
			font::draw_text_line(surf, clip_rect, fs, font::NORMAL_COLOUR, str_buf,
							 width - prob_width - 4, 2 + (fs + 2) * i, 0, TTF_STYLE_NORMAL);
		}
	}

	Uint32 battle_prediction_pane::blend_rgb(const surface& surf, unsigned char r, unsigned char g, unsigned char b, unsigned char drop)
	{
		// We simply decrement each component.
		if(r < drop) r = 0; else r -= drop;
		if(g < drop) g = 0; else g -= drop;
		if(b < drop) b = 0; else b -= drop;

		return SDL_MapRGB(surf->format, r, g, b);
	}

	// This class is used when the user clicks on the button
	// to show the "Damage Calculations" dialog.
	class attack_prediction_displayer : public gui::dialog_button_action
	{
	public:
		attack_prediction_displayer(display& disp, const std::vector<battle_context>& bc_vector, const gamemap& map,
								    const std::vector<team>& teams, const unit_map& units,
								    const gamestatus& status, const game_data& gamedata,
									const gamemap::location& attacker_loc, const gamemap::location& defender_loc)
				: disp_(disp), bc_vector_(bc_vector), map_(map), teams_(teams), units_(units), status_(status),
				  gamedata_(gamedata), attacker_loc_(attacker_loc), defender_loc_(defender_loc) {}

		// This method is called when the button is pressed.
		RESULT button_pressed(int selection)
		{
			// Get the selected weapon, if any.
			const size_t index = size_t(selection);

			if(index < bc_vector_.size()) {
				battle_prediction_pane battle_pane(disp_, bc_vector_[index], map_, teams_, units_, status_,
												   gamedata_, attacker_loc_, defender_loc_);
				std::vector<gui::preview_pane*> preview_panes;
				preview_panes.push_back(&battle_pane);

				gui::show_dialog(disp_, NULL, _("Damage Calculations"), "", gui::OK_ONLY, NULL, &preview_panes);
			}

		return NO_EFFECT;
		}

	private:
		display &disp_;
		const std::vector<battle_context>& bc_vector_;
		const gamemap& map_;
	const std::vector<team>& teams_;
		const unit_map& units_;
		const gamestatus& status_;
		const game_data& gamedata_;
		const gamemap::location& attacker_loc_;
		const gamemap::location& defender_loc_;
	};
} //end anonymous namespace

mouse_handler::mouse_handler(display* gui, std::vector<team>& teams, unit_map& units, gamemap& map,
				gamestatus& status, const game_data& gameinfo, undo_list& undo_stack, undo_list& redo_stack):
gui_(gui), teams_(teams), units_(units), map_(map), status_(status), gameinfo_(gameinfo),
undo_stack_(undo_stack), redo_stack_(redo_stack)
{
	minimap_scrolling_ = false;
	last_nearest_ = gamemap::location::NORTH;
	last_second_nearest_ = gamemap::location::NORTH;
	enemy_paths_ = false;
	path_turns_ = 0;
	undo_ = false;
	show_menu_ = false;
	over_route_ = false;
	team_num_ = 1;
}

void mouse_handler::set_team(const int team_number)
{
	team_num_ = team_number;
}

void mouse_handler::mouse_motion(const SDL_MouseMotionEvent& event, const bool browse)
{
	mouse_motion(event.x,event.y, browse);
}

void mouse_handler::mouse_motion(int x, int y, const bool browse)
{
	if(minimap_scrolling_) {
		//if the game is run in a window, we could miss a LMB/MMB up event
		// if it occurs outside our window.
		// thus, we need to check if the LMB/MMB is still down
		minimap_scrolling_ = ((SDL_GetMouseState(NULL,NULL) & (SDL_BUTTON(1) | SDL_BUTTON(2))) != 0);
		if(minimap_scrolling_) {
			const gamemap::location& loc = (*gui_).minimap_location_on(x,y);
			if(loc.valid()) {
				if(loc != last_hex_) {
					last_hex_ = loc;
					(*gui_).scroll_to_tile(loc.x,loc.y,display::WARP,false);
				}
			} else {
				// clicking outside of the minimap will end minimap scrolling
				minimap_scrolling_ = false;
			}
		}
		if(minimap_scrolling_) return;
	}

	gamemap::location::DIRECTION nearest_hex = gamemap::location::NDIRECTIONS;
	gamemap::location::DIRECTION second_nearest_hex = gamemap::location::NDIRECTIONS;
	const gamemap::location new_hex = (*gui_).hex_clicked_on(x,y,&nearest_hex,&second_nearest_hex);

	if(new_hex != last_hex_ || nearest_hex != last_nearest_ || second_nearest_hex != last_second_nearest_) {
		if(new_hex.valid() == false) {
			current_route_.steps.clear();
			(*gui_).set_route(NULL);
		}

		(*gui_).highlight_hex(new_hex);


		//see if we should show the normal cursor, the movement cursor, or
		//the attack cursor

		const unit_map::const_iterator selected_unit = find_unit(selected_hex_);
		const unit_map::const_iterator mouseover_unit = find_unit(new_hex);

		gamemap::location attack_from;
		if(selected_unit != units_.end() && mouseover_unit != units_.end()) {
			attack_from = current_unit_attacks_from(new_hex, nearest_hex, second_nearest_hex);
		}

		if(selected_unit != units_.end() && (current_paths_.routes.count(new_hex) ||
		                                     attack_from.valid())) {
			if(mouseover_unit == units_.end()) {
				cursor::set(cursor::MOVE);
			} else if(viewing_team().is_enemy(mouseover_unit->second.side()) && !mouseover_unit->second.incapacitated()) {
				cursor::set(cursor::ATTACK);
			} else {
				cursor::set(cursor::NORMAL);
			}
		} else {
			cursor::set(cursor::NORMAL);
		}

		if(enemy_paths_) {
			enemy_paths_ = false;
			current_paths_ = paths();
			gui_->unhighlight_reach();
		} else if(over_route_) {
			over_route_ = false;
			current_route_.steps.clear();
			(*gui_).set_route(NULL);
		}

		const gamemap::location& dest = attack_from.valid() ? attack_from : new_hex;
		const unit_map::const_iterator dest_un = find_unit(dest);
		if(dest == selected_hex_ || dest_un != units_.end()) {
			current_route_.steps.clear();
			(*gui_).set_route(NULL);
		} else if(!current_paths_.routes.empty() && map_.on_board(selected_hex_) &&
		   map_.on_board(new_hex)) {

			unit_map::const_iterator un = find_unit(selected_hex_);

			if((new_hex != last_hex_ || attack_from.valid()) && un != units_.end() && !un->second.incapacitated()) {
				current_route_ = get_route(un, dest, current_team());
				if(!browse) {
					(*gui_).set_route(&current_route_);
				}
			}
		}

		unit_map::iterator un = find_unit(new_hex);

		if(un != units_.end() && current_paths_.routes.empty() && !(*gui_).fogged(un->first.x,un->first.y)) {
			if (un->second.side() != team_num_) {
				//unit under cursor is not on our team, highlight reach
				unit_movement_resetter move_reset(un->second);

				const bool ignore_zocs = un->second.get_ability_bool("skirmisher",un->first);
				const bool teleport = un->second.get_ability_bool("teleport",un->first);
				current_paths_ = paths(map_,status_,gameinfo_,units_,new_hex,teams_,
									ignore_zocs,teleport,viewing_team(),path_turns_);
				gui_->highlight_reach(current_paths_);
				enemy_paths_ = true;
			} else {
				//unit is on our team, show path if the unit has one
				unit u = un->second;
				const gamemap::location go_to = u.get_goto();
				if(map_.on_board(go_to)) {
					paths::route route = get_route(un, go_to, current_team());
					gui_->set_route(&route);
				}
				over_route_ = true;
			}
		}
	}

	last_hex_ = new_hex;
	last_nearest_ = nearest_hex;
	last_second_nearest_ = second_nearest_hex;
}

unit_map::iterator mouse_handler::selected_unit()
{
	unit_map::iterator res = find_unit(selected_hex_);
	if(res != units_.end()) {
		return res;
	} else {
		return find_unit(last_hex_);
	}
}

unit_map::iterator mouse_handler::find_unit(const gamemap::location& hex)
{
	return find_visible_unit(units_,hex,map_,teams_,viewing_team());
}

unit_map::const_iterator mouse_handler::find_unit(const gamemap::location& hex) const
{
	return find_visible_unit(units_,hex,map_,teams_,viewing_team());
}

gamemap::location mouse_handler::current_unit_attacks_from(const gamemap::location& loc, const gamemap::location::DIRECTION preferred, const gamemap::location::DIRECTION second_preferred)
{
	const unit_map::const_iterator current = find_unit(selected_hex_);
	if(current == units_.end() || current->second.side() != team_num_) {
		return gamemap::location();
	}

	const unit_map::const_iterator enemy = find_unit(loc);
	if(enemy == units_.end() || current_team().is_enemy(enemy->second.side()) == false) {
		return gamemap::location();
	}

	int best_rating = 100;//smaller is better
	gamemap::location res;
	gamemap::location adj[6];
	get_adjacent_tiles(loc,adj);
	for(size_t n = 0; n != 6; ++n) {
		if(map_.on_board(adj[n]) == false) {
			continue;
		}

		if(adj[n] == selected_hex_) {
			return selected_hex_;
		}

		if(find_unit(adj[n]) != units_.end()) {
			continue;
		}

		if(current_paths_.routes.count(adj[n])) {
			static const size_t NDIRECTIONS = gamemap::location::NDIRECTIONS;
			unsigned int difference = abs(int(preferred - n));
			if(difference > NDIRECTIONS/2) {
				difference = NDIRECTIONS - difference;
			}
			unsigned int second_difference = abs(int(second_preferred - n));
			if(second_difference > NDIRECTIONS/2) {
				second_difference = NDIRECTIONS - second_difference;
			}
			const int rating = difference * 2 + (second_difference > difference);
			if(rating < best_rating || res.valid() == false) {
				best_rating = rating;
				res = adj[n];
			}
		}
	}

	return res;
}

paths::route mouse_handler::get_route(unit_map::const_iterator un, gamemap::location go_to, team &team)
{
	// The pathfinder will check unit visibility (fogged/stealthy).
	unit u = un->second;
	const shortest_path_calculator calc(u,team,units_,teams_,map_);
		
	const std::set<gamemap::location>* teleports = NULL;
	std::set<gamemap::location> allowed_teleports;
	if(u.get_ability_bool("teleport",un->first)) {
		allowed_teleports = vacant_villages(team.villages(),units_);
		teleports = &allowed_teleports;
		if(team.villages().count(un->first))
			allowed_teleports.insert(un->first);
		
	}
	
	paths::route route = a_star_search(un->first, go_to, 10000.0, &calc, map_.x(), map_.y(), teleports);
	route.move_left = route_turns_to_complete(u,map_,route);

	return route;
}

void mouse_handler::mouse_press(const SDL_MouseButtonEvent& event, const bool browse)
{
	show_menu_ = false;
	mouse_motion(event.x, event.y, browse);

	if(is_left_click(event) && event.state == SDL_RELEASED) {
		minimap_scrolling_ = false;
	} else if(is_middle_click(event) && event.state == SDL_RELEASED) {
		minimap_scrolling_ = false;
	} else if(is_left_click(event) && event.state == SDL_PRESSED) {
		left_click(event, browse);
	} else if(is_right_click(event) && event.state == SDL_PRESSED) {
		// FIXME: when it's not our turn, movement gets highlighted
		// merely by mousing over.  This hack means we don't require a
		// two clicks to access right menu.
		if (gui_->viewing_team() == team_num_-1 && !current_paths_.routes.empty()) {
			selected_hex_ = gamemap::location();
			gui_->select_hex(gamemap::location());
			gui_->unhighlight_reach();
			current_paths_ = paths();
			current_route_.steps.clear();
			gui_->set_route(NULL);

			cursor::set(cursor::NORMAL);
		} else {
			gui_->draw(); // redraw highlight (and maybe some more)
			const theme::menu* const m = gui_->get_theme().context_menu();
			if (m != NULL)
				show_menu_ = true;
			else
				LOG_STREAM(warn, display) << "no context menu found...\n";
		}
	} else if(is_middle_click(event) && event.state == SDL_PRESSED) {
		// clicked on a hex on the minimap? then initiate minimap scrolling
		const gamemap::location& loc = gui_->minimap_location_on(event.x,event.y);
		minimap_scrolling_ = false;
		if(loc.valid()) {
			minimap_scrolling_ = true;
			last_hex_ = loc;
			gui_->scroll_to_tile(loc.x,loc.y,display::WARP,false);
			return;
		} else {
		const SDL_Rect& rect = gui_->map_area();
		const int centerx = (rect.x + rect.w)/2;
		const int centery = (rect.y + rect.h)/2;

		const int xdisp = event.x - centerx;
		const int ydisp = event.y - centery;

		gui_->scroll(xdisp,ydisp);
		}
	} else if((event.button == SDL_BUTTON_WHEELUP ||
		event.button == SDL_BUTTON_WHEELDOWN) && !commands_disabled) {
		const int speed = preferences::scroll_speed() *
			(event.button == SDL_BUTTON_WHEELUP ? -1:1);

		const int centerx = gui_->mapx()/2;
		const int centery = gui_->y()/2;

		const int xdisp = abs(centerx - event.x);
		const int ydisp = abs(centery - event.y);

		if(xdisp > ydisp)
			gui_->scroll(speed,0);
		else
			gui_->scroll(0,speed);
	}
}

bool mouse_handler::is_left_click(const SDL_MouseButtonEvent& event)
{
	return event.button == SDL_BUTTON_LEFT && !command_active();
}

bool mouse_handler::is_middle_click(const SDL_MouseButtonEvent& event)
{
	return event.button == SDL_BUTTON_MIDDLE;
}

bool mouse_handler::is_right_click(const SDL_MouseButtonEvent& event)
{
	return event.button == SDL_BUTTON_RIGHT || event.button == SDL_BUTTON_LEFT && command_active();
}

void mouse_handler::left_click(const SDL_MouseButtonEvent& event, const bool browse)
{
	undo_ = false;
	bool check_shroud = teams_[team_num_ - 1].auto_shroud_updates();

	// clicked on a hex on the minimap? then initiate minimap scrolling
	const gamemap::location& loc = gui_->minimap_location_on(event.x,event.y);
	minimap_scrolling_ = false;
	if(loc.valid()) {
		minimap_scrolling_ = true;
		last_hex_ = loc;
		gui_->scroll_to_tile(loc.x,loc.y,display::WARP,false);
		return;
	}

	gamemap::location::DIRECTION nearest_hex, second_nearest_hex;
	gamemap::location hex = gui_->hex_clicked_on(event.x,event.y,&nearest_hex,&second_nearest_hex);

	unit_map::iterator u = find_unit(selected_hex_);

	//if the unit is selected and then itself clicked on,
	//any goto command is cancelled
	if(u != units_.end() && !browse && selected_hex_ == hex && u->second.side() == team_num_) {
		u->second.set_goto(gamemap::location());
	}

	//if we can move to that tile
	std::map<gamemap::location,paths::route>::const_iterator
			route = enemy_paths_ ? current_paths_.routes.end() :
	                               current_paths_.routes.find(hex);

	unit_map::iterator enemy = find_unit(hex);

	const gamemap::location src = selected_hex_;
	paths orig_paths = current_paths_;

	//see if we're trying to do a move-and-attack
	if(!browse && !commands_disabled && u != units_.end() && u->second.attacks_left()>0 && enemy != units_.end() && !current_route_.steps.empty()) {
		const gamemap::location& attack_from = current_unit_attacks_from(hex, nearest_hex, second_nearest_hex);
		if(attack_from.valid()) {
			if(move_unit_along_current_route(false)) { //move the unit without updating shroud
				// a WML event could have invalidated both attacker and defender
				// so make sure they're valid before attacking 
				u = find_unit(attack_from);
				enemy = find_unit(hex);
				if(u != units_.end() && u->second.side() == team_num_ &&
					enemy != units_.end() && current_team().is_enemy(enemy->second.side()) && !enemy->second.incapacitated()) {
					//if shroud or fog is active, rememember nits a and after attack check if someone isn`t seen
					std::set<gamemap::location> known_units;

					if (teams_[team_num_-1].uses_shroud() || teams_[team_num_-1].uses_fog()){
						 for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
				       if(teams_[team_num_-1].fogged(u->first.x,u->first.y) == false) {
				         known_units.insert(u->first);
				         teams_[team_num_-1].see(u->second.side()-1);
								}
							}
					}
					if(!commands_disabled && attack_enemy(u,enemy) == false) {
						undo_ = true;
						selected_hex_ = src;
						gui_->select_hex(src);
						current_paths_ = orig_paths;
						gui_->highlight_reach(current_paths_);
						return;
					}
					else //attack == true
					{
						if (teams_[team_num_-1].uses_shroud() || teams_[team_num_-1].uses_fog()){
							//check if some new part of map discovered or is active delay shroud updates, which need special care
							if (clear_shroud(*gui_, status_, map_, gameinfo_, units_, teams_, team_num_ - 1)||!teams_[team_num_-1].auto_shroud_updates()){
								clear_undo_stack();
								gui_->invalidate_all();
								gui_->draw();
								//some new part of map discovered
								for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
						       if(teams_[team_num_-1].fogged(u->first.x,u->first.y) == false) {
											//check if unit is not known
											if (known_units.find(u->first)==known_units.end())
											{
												game_events::raise("sighted",u->first,attack_from);
											}
									 }
								}
								game_events::pump();
								return;
							}
						}
					}
				}
			}

			if(check_shroud && clear_shroud(*gui_, status_, map_, gameinfo_, units_, teams_, team_num_ - 1)) {
				clear_undo_stack();
				gui_->invalidate_all();
				gui_->draw();
			}

			return;
		}
	}

	//see if we're trying to attack an enemy
	if(!commands_disabled && u != units_.end() && route != current_paths_.routes.end() && enemy != units_.end() &&
	   hex != selected_hex_ && !browse &&
	   enemy->second.side() != u->second.side() &&
	   current_team().is_enemy(enemy->second.side())) {
		attack_enemy(u,enemy);
	}

	//otherwise we're trying to move to a hex
	else if(!commands_disabled && !browse && selected_hex_.valid() && selected_hex_ != hex &&
		     units_.count(selected_hex_) && !enemy_paths_ &&
		     enemy == units_.end() && !current_route_.steps.empty() &&
		     current_route_.steps.front() == selected_hex_) {
		move_unit_along_current_route(check_shroud);
		if(check_shroud && clear_shroud(*gui_, status_, map_, gameinfo_, units_, teams_, team_num_ - 1)) {
			clear_undo_stack();
		}
	} else {
		gui_->unhighlight_reach();
		current_paths_ = paths();

		selected_hex_ = hex;
		gui_->select_hex(hex);
		current_route_.steps.clear();
		gui_->set_route(NULL);

		const unit_map::iterator it = find_unit(hex);

		if(it != units_.end() && it->second.side() == team_num_ && !gui_->fogged(it->first.x,it->first.y)) {
			const bool ignore_zocs = it->second.get_ability_bool("skirmisher",it->first);
			const bool teleport = it->second.get_ability_bool("teleport",it->first);
			current_paths_ = paths(map_,status_,gameinfo_,units_,hex,teams_,
								   ignore_zocs,teleport,viewing_team(),path_turns_);

			next_unit_ = it->first;

			show_attack_options(it);

			gui_->highlight_reach(current_paths_);

			unit u = it->second;
			const gamemap::location go_to = u.get_goto();
			if(map_.on_board(go_to)) {
				paths::route route = get_route(it, go_to, current_team());
				gui_->set_route(&route);
			}
			game_events::fire("select",hex);
		}
	}
}

void mouse_handler::clear_undo_stack()
{
	if(teams_[team_num_ - 1].auto_shroud_updates() == false)
		apply_shroud_changes(undo_stack_,gui_,status_,map_,gameinfo_,units_,teams_,team_num_-1);
	undo_stack_.clear();
}

bool mouse_handler::move_unit_along_current_route(bool check_shroud)
{
	const std::vector<gamemap::location> steps = current_route_.steps;
	if(steps.empty()) {
		return false;
	}

	const size_t moves = ::move_unit(gui_,gameinfo_,status_,map_,units_,teams_,
	                   steps,&recorder,&undo_stack_,&next_unit_,false,check_shroud);

	cursor::set(cursor::NORMAL);

	gui_->invalidate_game_status();

	selected_hex_ = gamemap::location();
	gui_->select_hex(gamemap::location());

	gui_->set_route(NULL);
	gui_->unhighlight_reach();
	current_paths_ = paths();

	if(moves == 0)
		return false;

	redo_stack_.clear();

	wassert(moves <= steps.size());
	const gamemap::location& dst = steps[moves-1];
	const unit_map::const_iterator u = units_.find(dst);

	//u may be equal to units_.end() in the case of e.g. a [teleport]
	if(u != units_.end()) {
		//Reselect the unit if the move was interrupted
		if(dst != steps.back()) {
			selected_hex_ = dst;
			gui_->select_hex(dst);
		}

		current_route_.steps.clear();
		show_attack_options(u);

		if(current_paths_.routes.empty() == false) {
			current_paths_.routes[dst] = paths::route();
			selected_hex_ = dst;
			gui_->select_hex(dst);
			gui_->highlight_reach(current_paths_);
		}
	}

	return moves == steps.size();
}

bool mouse_handler::attack_enemy(unit_map::iterator attacker, unit_map::iterator defender)
{
	//we must get locations by value instead of by references, because the iterators
	//may become invalidated later
	const gamemap::location attacker_loc = attacker->first;
	const gamemap::location defender_loc = defender->first;

	std::vector<std::string> items;

	std::vector<battle_context> bc_vector;
	unsigned int i, best = 0;
	for (i = 0; i < attacker->second.attacks().size(); i++) {
		// skip weapons with attack_weight=0
		if (attacker->second.attacks()[i].attack_weight() > 0) {
			battle_context bc(map_, teams_, units_, status_, gameinfo_, attacker->first, defender->first, i);
			bc_vector.push_back(bc);
			if (bc.better_attack(bc_vector[best], 0.5)) {
				best = i;
			}
		}
	}

	for (i = 0; i < bc_vector.size(); i++) {
		const battle_context::unit_stats& att = bc_vector[i].get_attacker_stats();
		const battle_context::unit_stats& def = bc_vector[i].get_defender_stats();
		config tmp_config;
		attack_type no_weapon(tmp_config, "fake_attack", "");
		const attack_type& attw = attack_type(*att.weapon);
		const attack_type& defw = attack_type(def.weapon ? *def.weapon : no_weapon);

		//if there is an attack special or defend special, we output a single space for the other unit, to make sure
		//that the attacks line up nicely.
		std::string special_pad = "";
		if (!attw.weapon_specials().empty() || !defw.weapon_specials().empty())
			special_pad = " ";

		std::stringstream atts;
		if (i == best) {
			atts << DEFAULT_ITEM;
		}
		atts << IMAGE_PREFIX << attw.icon() << COLUMN_SEPARATOR
			 << font::BOLD_TEXT << attw.name() << "\n" << att.damage << "-"
			 << att.num_blows << " " << (attw.range().empty() ? "" : gettext(attw.range().c_str())) << " (" << att.chance_to_hit << "%)\n"
			 << attw.weapon_specials() << special_pad
			 << COLUMN_SEPARATOR << _("vs") << COLUMN_SEPARATOR
			 << font::BOLD_TEXT << defw.name() << "\n" << def.damage << "-"
			 << def.num_blows << " " << (defw.range().empty() ? "" : gettext(defw.range().c_str())) << " (" << def.chance_to_hit << "%)\n"
			 << defw.weapon_specials() << special_pad << COLUMN_SEPARATOR
			 << IMAGE_PREFIX << defw.icon();

		items.push_back(atts.str());
	}

	//make it so that when we attack an enemy, the attacking unit
	//is again shown in the status bar, so that we can easily
	//compare between the attacking and defending unit
	gui_->highlight_hex(gamemap::location());
	gui_->draw(true,true);

	attack_prediction_displayer ap_displayer(*gui_, bc_vector, map_, teams_, units_, status_, gameinfo_, attacker_loc, defender_loc);
	std::vector<gui::dialog_button> buttons;
	buttons.push_back(gui::dialog_button(&ap_displayer, _("Damage Calculations")));

	int res = 0;

	{
		dialogs::unit_preview_pane attacker_preview(*gui_,&map_,attacker->second,dialogs::unit_preview_pane::SHOW_BASIC,true);
		dialogs::unit_preview_pane defender_preview(*gui_,&map_,defender->second,dialogs::unit_preview_pane::SHOW_BASIC,false);
		std::vector<gui::preview_pane*> preview_panes;
		preview_panes.push_back(&attacker_preview);
		preview_panes.push_back(&defender_preview);

		res = gui::show_dialog2(*gui_,NULL,_("Attack Enemy"),
				_("Choose weapon:")+std::string("\n"),
				gui::OK_CANCEL,&items,&preview_panes,"",NULL,-1,NULL,NULL,-1,-1,
				NULL,&buttons);
	}

	cursor::set(cursor::NORMAL);
	if(size_t(res) < bc_vector.size()) {
		const battle_context::unit_stats &att = bc_vector[res].get_attacker_stats();
		const battle_context::unit_stats &def = bc_vector[res].get_defender_stats();

		attacker->second.set_goto(gamemap::location());
		clear_undo_stack();
		redo_stack_.clear();

		current_paths_ = paths();
		gui_->unhighlight_reach();

		gui_->invalidate_all();
		gui_->draw();

		const bool defender_human = teams_[defender->second.side()-1].is_human();

		recorder.add_attack(attacker_loc,defender_loc,att.attack_num,def.attack_num);

		//MP_COUNTDOWN grant time bonus for attacking
		current_team().set_action_bonus_count(1 + current_team().action_bonus_count());

		try {
			attack(*gui_,map_,teams_,attacker_loc,defender_loc,att.attack_num,def.attack_num,units_,status_,gameinfo_);
		} catch(end_level_exception&) {
			//if the level ends due to a unit being killed, still see if
			//either the attacker or defender should advance
			dialogs::advance_unit(gameinfo_,map_,units_,attacker_loc,*gui_);
			dialogs::advance_unit(gameinfo_,map_,units_,defender_loc,*gui_,!defender_human);
			throw;
		}

		dialogs::advance_unit(gameinfo_,map_,units_,attacker_loc,*gui_);
		dialogs::advance_unit(gameinfo_,map_,units_,defender_loc,*gui_,!defender_human);

		selected_hex_ = gamemap::location();
		current_route_.steps.clear();
		gui_->set_route(NULL);

		check_victory(units_,teams_);

		gui_->invalidate_all();
		gui_->draw(); //clear the screen

		return true;
	} else {
		return false;
	}
}

void mouse_handler::show_attack_options(unit_map::const_iterator u)
{
	team& current_team = teams_[team_num_-1];

	if(u == units_.end() || u->second.attacks_left() == 0)
		return;

	for(unit_map::const_iterator target = units_.begin(); target != units_.end(); ++target) {
		if(current_team.is_enemy(target->second.side()) &&
			distance_between(target->first,u->first) == 1 && !target->second.incapacitated()) {
			current_paths_.routes[target->first] = paths::route();
		}
	}
}

bool mouse_handler::unit_in_cycle(unit_map::const_iterator it)
{
	if(it->second.side() == team_num_ && unit_can_move(it->first,units_,map_,teams_) && it->second.user_end_turn() == false && !gui_->fogged(it->first.x,it->first.y)) {
		bool is_enemy = current_team().is_enemy(int(gui_->viewing_team()+1));
		return is_enemy == false || it->second.invisible(it->first,units_,teams_) == false;
	}

	return false;

}

void mouse_handler::cycle_units()
{
	unit_map::const_iterator it = units_.find(next_unit_);
	if(it != units_.end()) {
		for(++it; it != units_.end(); ++it) {
			if(unit_in_cycle(it)) {
				break;
			}
		}
	}

	if(it == units_.end()) {
		for(it = units_.begin(); it != units_.end(); ++it) {
			if(unit_in_cycle(it)) {
				break;
			}
		}
	}

	if(it != units_.end() && !gui_->fogged(it->first.x,it->first.y)) {
		const bool ignore_zocs = it->second.get_ability_bool("skirmisher",it->first);
		const bool teleport = it->second.get_ability_bool("teleport",it->first);
		current_paths_ = paths(map_,status_,gameinfo_,units_,it->first,teams_,ignore_zocs,teleport,viewing_team(),path_turns_);
		gui_->highlight_reach(current_paths_);

		gui_->scroll_to_tile(it->first.x,it->first.y,display::WARP);
	}

	if(it != units_.end()) {
		next_unit_ = it->first;
		selected_hex_ = next_unit_;
		gui_->select_hex(selected_hex_);
		gui_->highlight_hex(selected_hex_);
		current_route_.steps.clear();
		gui_->set_route(NULL);
		last_hex_=gamemap::location(-1,-1);
		int mousex, mousey;
		SDL_GetMouseState(&mousex, &mousey);
		mouse_motion(mousex, mousey, true);

		show_attack_options(it);
	} else {
		next_unit_ = gamemap::location();
	}
}

void mouse_handler::cycle_back_units()
{
	unit_map::const_iterator it = units_.find(next_unit_);
	if(it != units_.begin()) {
		for(--it; it != units_.begin(); --it) {
			if(unit_in_cycle(it)) {
				break;
			}
		}
	}

	if(it == units_.begin()) {
		it = units_.end();
		for(--it; it != units_.begin(); --it) {
			if(unit_in_cycle(it)) {
				break;
			}
		}
	}

	if(it != units_.begin() && !gui_->fogged(it->first.x,it->first.y)) {
		const bool ignore_zocs = it->second.get_ability_bool("skirmisher",it->first);
		const bool teleport = it->second.get_ability_bool("teleport",it->first);
		current_paths_ = paths(map_,status_,gameinfo_,units_,it->first,teams_,ignore_zocs,teleport,viewing_team(),path_turns_);
		gui_->highlight_reach(current_paths_);

		gui_->scroll_to_tile(it->first.x,it->first.y,display::WARP);
	}

	if(it != units_.begin()) {
		next_unit_ = it->first;
		selected_hex_ = next_unit_;
		gui_->select_hex(selected_hex_);
		gui_->highlight_hex(selected_hex_);
		current_route_.steps.clear();
		gui_->set_route(NULL);
		show_attack_options(it);
	} else {
		next_unit_ = gamemap::location();
	}
}

void mouse_handler::set_current_paths(paths new_paths) {
	gui_->unhighlight_reach();
	current_paths_ = new_paths;
	current_route_.steps.clear();
	gui_->set_route(NULL);
}

}
