/* $Id: playturn.cpp 8760 2005-11-06 19:55:06Z ott $ */
/*
   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 "global.hpp"

#include "actions.hpp"
#include "dialogs.hpp"
#include "events.hpp"
#include "game_errors.hpp"
#include "game_events.hpp"
#include "gettext.hpp"
#include "help.hpp"
#include "hotkeys.hpp"
#include "image.hpp"
#include "log.hpp"
#include "map_label.hpp"
#include "mouse.hpp"
#include "network.hpp"
#include "playlevel.hpp"
#include "playturn.hpp"
#include "preferences.hpp"
#include "replay.hpp"
#include "sound.hpp"
#include "statistics.hpp"
#include "tooltips.hpp"
#include "unit.hpp"
#include "unit_display.hpp"
#include "util.hpp"
#include "wassert.hpp"
#include "wml_separators.hpp"
#include "serialization/string_utils.hpp"
#include "widgets/menu.hpp"

#include "wesconfig.h"

#include <cmath>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <string>

namespace {
	int commands_disabled = 0;
}

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

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

void play_turn(const game_data& gameinfo, game_state& state_of_game,
               const gamestatus& status, const config& terrain_config,
               const config& level, CKey& key, display& gui, gamemap& map,
               std::vector<team>& teams, int team_num,
               std::map<gamemap::location,unit>& units,
               turn_info::floating_textbox& textbox,
               replay_network_sender& network_sender)
{
	log_scope("player turn");

	gui.set_team(team_num-1);
	gui.recalculate_minimap();
	gui.invalidate_all();
	gui.draw();
	gui.update_display();

	const paths_wiper wiper(gui);

	if(preferences::turn_bell()) {
		sound::play_sound(game_config::sounds::turn_bell);
	}

	if(preferences::turn_dialog()) {
		gui::show_dialog(gui,NULL,"",_("It is now your turn"),gui::MESSAGE);
	}

	const std::string& turn_cmd = preferences::turn_cmd();
	if(turn_cmd.empty() == false) {
		system(turn_cmd.c_str());
	}

	turn_info turn_data(gameinfo,state_of_game,status,terrain_config,level,
	                    key,gui,map,teams,team_num,units,turn_info::PLAY_TURN,textbox,network_sender);

	//execute gotos - first collect gotos in a list
	std::vector<gamemap::location> gotos;

	for(unit_map::iterator ui = units.begin(); ui != units.end(); ++ui) {
		if(ui->second.get_goto() == ui->first)
			ui->second.set_goto(gamemap::location());

		if(ui->second.side() == team_num && map.on_board(ui->second.get_goto()))
			gotos.push_back(ui->first);
	}

	for(std::vector<gamemap::location>::const_iterator g = gotos.begin(); g != gotos.end(); ++g) {
		unit_map::const_iterator ui = units.find(*g);
		turn_data.move_unit_to_loc(ui,ui->second.get_goto(),false);
	}

	turn_data.start_interactive_turn();

	while(!turn_data.turn_over()) {

		try {
			config cfg;
			const network::connection res = network::receive_data(cfg);
			std::deque<config> backlog;

			if(res != network::null_connection) {
				turn_data.process_network_data(cfg,res,backlog);
			}

			turn_data.turn_slice();
		} catch(end_level_exception& e) {
			turn_data.send_data();
			throw e;
		}

		// gui.invalidate_animations();
		gui.draw();

		turn_data.send_data();
	}

	//send one more time to make sure network is up-to-date.
	turn_data.send_data();
}

turn_info::turn_info(const game_data& gameinfo, game_state& state_of_game,
                     const gamestatus& status, const config& terrain_config,
		     const config& level, CKey& key, display& gui, gamemap& map,
		     std::vector<team>& teams, int team_num, unit_map& units,
		     TURN_MODE mode, floating_textbox& textbox,
		     replay_network_sender& replay_sender)
  : paths_wiper(gui),
    gameinfo_(gameinfo), state_of_game_(state_of_game), status_(status),
    terrain_config_(terrain_config), level_(level),
    key_(key), gui_(gui), map_(map), teams_(teams), team_num_(team_num),
    units_(units), browse_(mode != PLAY_TURN), allow_network_commands_(mode == BROWSE_NETWORKED),
    left_button_(false), right_button_(false), middle_button_(false),
	minimap_scrolling_(false), enemy_paths_(false),
	last_nearest_(gamemap::location::NORTH),
	last_second_nearest_(gamemap::location::NORTH),
	path_turns_(0), end_turn_(false), start_ncmd_(-1), textbox_(textbox), replay_sender_(replay_sender)
{
	enemies_visible_ = enemies_visible();

	gui_.enable_menu("endturn", !browse_);
}

void turn_info::turn_slice()
{
	events::pump();
	events::raise_process_event();

	if (gui_.video().modeChanged()) {
		update_textbox_location();
	}

	events::raise_draw_event();

	const theme::menu* const m = gui_.menu_pressed();
	if(m != NULL) {
		const SDL_Rect& menu_loc = m->location(gui_.screen_area());
		show_menu(m->items(),menu_loc.x+1,menu_loc.y + menu_loc.h + 1,false);
		return;
	}

	int mousex, mousey;
	SDL_GetMouseState(&mousex,&mousey);
	tooltips::process(mousex, mousey);

	const int scroll_threshold = 5;

	if(key_[SDLK_UP] || mousey < scroll_threshold)
		gui_.scroll(0,-preferences::scroll_speed());

	if(key_[SDLK_DOWN] || mousey > gui_.y()-scroll_threshold)
		gui_.scroll(0,preferences::scroll_speed());

	const bool use_left_right = (textbox_.active() == false);
	if(use_left_right && key_[SDLK_LEFT] || mousex < scroll_threshold)
		gui_.scroll(-preferences::scroll_speed(),0);

	if(use_left_right && key_[SDLK_RIGHT] || mousex > gui_.x()-scroll_threshold)
		gui_.scroll(preferences::scroll_speed(),0);

	if(!browse_ && current_team().objectives_changed()) {
		dialogs::show_objectives(gui_, level_, current_team().objectives());
		current_team().reset_objectives_changed();
	}

}

bool turn_info::turn_over() const { return end_turn_; }

void turn_info::send_data()
{
	if(undo_stack_.empty() || end_turn_) {
		replay_sender_.commit_and_sync();
	} else {
		replay_sender_.sync_non_undoable();
	}
}

void turn_info::handle_event(const SDL_Event& event)
{
	if(gui::in_dialog()) {
		return;
	}

	switch(event.type) {
	case SDL_KEYDOWN:
		//detect key press events, unless there is a textbox present on-screen
		//in which case the key press events should go only to it.
		if(textbox_.active() == false) {
			hotkey::key_event(gui_,event.key,this);
		} else if(event.key.keysym.sym == SDLK_ESCAPE) {
			close_textbox();
		} else if(event.key.keysym.sym == SDLK_TAB) {
			tab_textbox();
		} else if(event.key.keysym.sym == SDLK_RETURN) {
			enter_textbox();
		}

		//intentionally fall-through
	case SDL_KEYUP:

		//if the user has pressed 1 through 9, we want to show how far
		//the unit can move in that many turns
		if(event.key.keysym.sym >= '1' && event.key.keysym.sym <= '7') {
			const int new_path_turns = (event.type == SDL_KEYDOWN) ?
			                           event.key.keysym.sym - '1' : 0;

			if(new_path_turns != path_turns_) {
				path_turns_ = new_path_turns;

				const unit_map::iterator u = selected_unit();

				if(u != units_.end() && u->second.side() == team_num_) {
					const bool ignore_zocs = u->second.type().is_skirmisher();
					const bool teleport = u->second.type().teleports();
					current_paths_ = paths(map_,status_,gameinfo_,units_,u->first,
					                       teams_,ignore_zocs,teleport,
					                       path_turns_);
					gui_.set_paths(&current_paths_);
				}
			}
		}

		break;
	case SDL_MOUSEMOTION:
		// ignore old mouse motion events in the event queue
		SDL_Event new_event;

		if(SDL_PeepEvents(&new_event,1,SDL_GETEVENT,
					SDL_EVENTMASK(SDL_MOUSEMOTION)) > 0) {
			while(SDL_PeepEvents(&new_event,1,SDL_GETEVENT,
						SDL_EVENTMASK(SDL_MOUSEMOTION)) > 0);
			mouse_motion(new_event.motion);
		} else {
			mouse_motion(event.motion);
		}
		break;
	case SDL_MOUSEBUTTONDOWN:
	case SDL_MOUSEBUTTONUP:
		mouse_press(event.button);
		break;
	default:
		break;
	}
}

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

void turn_info::mouse_motion(int x, int y)
{
	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 team& current_team = teams_[team_num_-1];
	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(current_team.is_enemy(mouseover_unit->second.side()) && !mouseover_unit->second.stone()) {
				cursor::set(cursor::ATTACK);
			} else {
				cursor::set(cursor::NORMAL);
			}
		} else {
			cursor::set(cursor::NORMAL);
		}

		if(enemy_paths_) {
			enemy_paths_ = false;
			current_paths_ = paths();
			gui_.set_paths(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.stone()) {
				const shortest_path_calculator calc(un->second,current_team,
				                                    visible_units(),teams_,map_,status_);
				const bool can_teleport = un->second.type().teleports();

				const std::set<gamemap::location>* teleports = NULL;

				std::set<gamemap::location> allowed_teleports;
				if(can_teleport) {
					allowed_teleports = vacant_villages(current_team.villages(),units_);
					teleports = &allowed_teleports;
					if(current_team.villages().count(un->first))
						allowed_teleports.insert(un->first);
				}

				current_route_ = a_star_search(selected_hex_, dest, 10000.0, &calc, map_.x(), map_.y(), teleports);

				current_route_.move_left = route_turns_to_complete(un->second,map_,current_route_);

				if(!browse_) {
					gui_.set_route(&current_route_);
				}
			}
		}

		unit_map::iterator un = find_unit(new_hex);

		if(un != units_.end() && un->second.side() != team_num_ &&
		   current_paths_.routes.empty() && !gui_.fogged(un->first.x,un->first.y)) {
			unit_movement_resetter move_reset(un->second);

			const bool ignore_zocs = un->second.type().is_skirmisher();
			const bool teleport = un->second.type().teleports();
			current_paths_ = paths(map_,status_,gameinfo_,units_,new_hex,teams_,
			                   ignore_zocs,teleport,path_turns_);
			gui_.set_paths(&current_paths_);
			enemy_paths_ = true;
		}
	}

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

namespace {

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

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

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

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

//which attack is the better one to select for the player by default
//(the player can change the selected weapon if desired)
class simple_attack_rating
{
public:
	simple_attack_rating() : attacker_weapon_rating_(0), defender_weapon_rating_(0) {}

	simple_attack_rating(const battle_stats& stats) :
		attacker_weapon_rating_(stats.chance_to_hit_defender *
				stats.damage_defender_takes * stats.nattacks),
		defender_weapon_rating_(stats.chance_to_hit_attacker *
				stats.damage_attacker_takes * stats.ndefends) {}

	bool operator<(const simple_attack_rating& a) const
	{
		//if our weapon does less damage, it's worse
		if(attacker_weapon_rating_ < a.attacker_weapon_rating_)
			return true;

		//if both weapons are the same but
		//ours makes the enemy retaliate for more damage, it's worse
		else if(attacker_weapon_rating_ == a.attacker_weapon_rating_ &&
		   defender_weapon_rating_ > a.defender_weapon_rating_)
			return true;

		//otherwise, ours is at least as good a default weapon
		return false;
	}
private:
	int attacker_weapon_rating_, defender_weapon_rating_;
};

} //end anonymous namespace

void turn_info::mouse_press(const SDL_MouseButtonEvent& event)
{
	mouse_motion(event.x,event.y);

	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);
	} else if(is_right_click(event) && event.state == SDL_PRESSED) {
		if(!current_paths_.routes.empty()) {
			selected_hex_ = gamemap::location();
			gui_.select_hex(gamemap::location());
			gui_.set_paths(NULL);
			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(m->items(),event.x,event.y,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);
	}
}

//a class which, when a button is pressed, will display
//how an attack was calculated
namespace {
class attack_calculations_displayer : public gui::dialog_button_action
{
public:
	typedef std::vector< battle_stats_strings > stats_vector;
	attack_calculations_displayer(display &disp, stats_vector const &stats)
		: disp_(disp), stats_(stats)
	{}

	RESULT button_pressed(int selection);
private:
	display &disp_;
	stats_vector const &stats_;
};

gui::dialog_button_action::RESULT attack_calculations_displayer::button_pressed(int selection)
{
	const size_t index = size_t(selection);
	if(index < stats_.size()) {
		battle_stats_strings const &sts = stats_[index];
		std::vector< std::string > sts_att = sts.attack_calculations,
		                           sts_def = sts.defend_calculations,
		                           calcs;
		unsigned sts_att_sz = sts_att.size(),
		         sts_def_sz = sts_def.size(),
		         sts_sz = maximum< unsigned >(sts_att_sz, sts_def_sz);

		std::stringstream str;
		str << _("Attacker") << COLUMN_SEPARATOR << ' ' << COLUMN_SEPARATOR << ' ' << COLUMN_SEPARATOR;
		if (sts_def_sz > 0)
			str << _("Defender");
		calcs.push_back(str.str());

		for(unsigned i = 0; i < sts_sz; ++i) {
			std::stringstream str;
			if (i < sts_att_sz)
				str << sts_att[i];
			else
				str << COLUMN_SEPARATOR << ' ' << COLUMN_SEPARATOR << ' ';

			str << COLUMN_SEPARATOR;

			if (i < sts_def_sz)
				str << sts_def[i];
			else
				str << ' ' << COLUMN_SEPARATOR << ' ' << COLUMN_SEPARATOR << ' ';

			calcs.push_back(str.str());
		}

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

	return NO_EFFECT;
}

}
bool turn_info::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;

	const std::vector<attack_type>& attacks = attacker->second.attacks();
	std::vector<std::string> items;

	int best_weapon_index = -1;
	simple_attack_rating best_weapon_rating;

	attack_calculations_displayer::stats_vector stats;

	for(size_t a = 0; a != attacks.size(); ++a) {

		battle_stats_strings sts;
		battle_stats st = evaluate_battle_stats(map_, attacker_loc, defender_loc,
		                                        a, units_, status_, 0, &sts);
		stats.push_back(sts);

		simple_attack_rating weapon_rating(st);

		if (best_weapon_index < 0 || best_weapon_rating < weapon_rating) {
			best_weapon_index = items.size();
			best_weapon_rating = weapon_rating;
		}

		//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 = (sts.attack_special.empty() && sts.defend_special.empty()) ? "" : " ";

		std::stringstream att;
		att << IMAGE_PREFIX << sts.attack_icon << COLUMN_SEPARATOR
		    << font::BOLD_TEXT << sts.attack_name << "\n" << st.damage_defender_takes << "-"
		    << st.nattacks << " " << sts.range << " (" << st.chance_to_hit_defender << "%)\n"
		    << sts.attack_special << special_pad
		    << COLUMN_SEPARATOR << _("vs") << COLUMN_SEPARATOR
		    << font::BOLD_TEXT << sts.defend_name << "\n" << st.damage_attacker_takes << "-"
		    << st.ndefends << " " << sts.range << " (" << st.chance_to_hit_attacker << "%)\n"
		    << sts.defend_special << special_pad << COLUMN_SEPARATOR
		    << IMAGE_PREFIX << sts.defend_icon;

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

	if (best_weapon_index >= 0) {
		items[best_weapon_index] = DEFAULT_ITEM + items[best_weapon_index];
	}

	//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_calculations_displayer calc_displayer(gui_,stats);
	std::vector<gui::dialog_button> buttons;
	buttons.push_back(gui::dialog_button(&calc_displayer,_("Damage Calculations")));

	int res = 0;

	{
		const events::event_context dialog_events_context;
		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_dialog(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) < attacks.size()) {

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

		current_paths_ = paths();
		gui_.set_paths(NULL);

		game_events::fire("attack",attacker_loc,defender_loc);

		//the event could have killed either the attacker or
		//defender, so we have to make sure they still exist
		attacker = units_.find(attacker_loc);
		defender = units_.find(defender_loc);

		if(attacker == units_.end() || defender == units_.end() || size_t(res) >= attacks.size()) {
			return true;
		}

		gui_.invalidate_all();
		gui_.draw();

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

		recorder.add_attack(attacker_loc,defender_loc,res);

		try {
			attack(gui_,map_,teams_,attacker_loc,defender_loc,res,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;
	}
}

bool turn_info::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_.set_paths(NULL);
	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_.set_paths(&current_paths_);
		}
	}

	return moves == steps.size();
}

void turn_info::left_click(const SDL_MouseButtonEvent& event)
{
	if(commands_disabled) {
		return;
	}

	// 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_ && u != units_.end() && 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
				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.stone()) {
					if(attack_enemy(u,enemy) == false) {
						undo();
						selected_hex_ = src;
						gui_.select_hex(src);
						current_paths_ = orig_paths;
						gui_.set_paths(&current_paths_);
						return;
					}
				}
			}

			if(clear_shroud()) {
				clear_undo_stack();
			}

			return;
		}
	}

	//see if we're trying to attack an enemy
	if(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(!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();
		if(clear_shroud()) {
			clear_undo_stack();
		}
	} else {
		gui_.set_paths(NULL);
		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.type().is_skirmisher();
			const bool teleport = it->second.type().teleports();
			current_paths_ = paths(map_,status_,gameinfo_,units_,hex,teams_,
			                   ignore_zocs,teleport,path_turns_);

			next_unit_ = it->first;

			show_attack_options(it);

			gui_.set_paths(&current_paths_);

			unit u = it->second;
			const gamemap::location go_to = u.get_goto();
			if(map_.on_board(go_to)) {
				const shortest_path_calculator calc(u,current_team(),
				                                    visible_units(),teams_,map_,status_);

				const std::set<gamemap::location>* teleports = NULL;

				std::set<gamemap::location> allowed_teleports;
				if(u.type().teleports()) {
					allowed_teleports = vacant_villages(current_team().villages(),units_);
					teleports = &allowed_teleports;
					if(current_team().villages().count(it->first))
						allowed_teleports.insert(it->first);

				}

				paths::route route = a_star_search(it->first, go_to, 10000.0, &calc, map_.x(), map_.y(), teleports);
				route.move_left = route_turns_to_complete(it->second,map_,route);
				gui_.set_route(&route);
			}
		}
	}
}

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

	if(u == units_.end() || u->second.can_attack() == false)
		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.stone()) {
			current_paths_.routes[target->first] = paths::route();
		}
	}
}

gamemap::location turn_info::current_unit_attacks_from(const gamemap::location& loc, const gamemap::location::DIRECTION preferred, const gamemap::location::DIRECTION second_preferred) const
{
	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;
			int difference = abs(int(preferred - n));
			if(difference > NDIRECTIONS/2) {
				difference = NDIRECTIONS - difference;
			}
			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;
}

void turn_info::move_unit_to_loc(const unit_map::const_iterator& ui, const gamemap::location& target, bool continue_move)
{
	wassert(ui != units_.end());

	unit u = ui->second;
	const shortest_path_calculator calc(u,current_team(),visible_units(),teams_,map_,status_);

	const std::set<gamemap::location>* teleports = NULL;

	std::set<gamemap::location> allowed_teleports;
	if(u.type().teleports()) {
		allowed_teleports = vacant_villages(current_team().villages(),units_);
		teleports = &allowed_teleports;
		if(current_team().villages().count(ui->first))
			allowed_teleports.insert(ui->first);
	}

	paths::route route = a_star_search(ui->first, target, 10000.0, &calc, map_.x(), map_.y(), teleports);
	if(route.steps.empty())
		return;

	wassert(route.steps.front() == ui->first);

	route.move_left = route_turns_to_complete(ui->second,map_,route);
	gui_.set_route(&route);
	move_unit(&gui_,gameinfo_,status_,map_,units_,teams_,route.steps,&recorder,&undos(),NULL,continue_move);
	gui_.invalidate_game_status();
}

void turn_info::start_interactive_turn() {
	LOG_STREAM(info, engine) << "done gotos\n";
	start_ncmd_ = recorder.ncommands();
}

hotkey::ACTION_STATE turn_info::get_action_state(hotkey::HOTKEY_COMMAND command) const
{
	switch(command) {
	case hotkey::HOTKEY_DELAY_SHROUD:
		return current_team().auto_shroud_updates() ? hotkey::ACTION_OFF : hotkey::ACTION_ON;
	default:
		return hotkey::ACTION_STATELESS;
	}
}

// Indicates whether the command should be in the context menu or not.
// Independant of whether or not we can actually execute the command.
bool turn_info::in_context_menu(hotkey::HOTKEY_COMMAND command) const
{
	switch(command) {
	//Only display these if the mouse is over a castle or keep tile
	case hotkey::HOTKEY_RECRUIT:
	case hotkey::HOTKEY_REPEAT_RECRUIT:
	case hotkey::HOTKEY_RECALL: {
		// last_hex_ is set by turn_info::mouse_motion
		// Enable recruit/recall on castle/keep tiles
		const unit_map::const_iterator leader = team_leader(team_num_,units_);
		if (leader != units_.end()) {
			return can_recruit_on(map_, leader->first, last_hex_);
		} else {
			return false;
		}
	}
	default:
		return true;
	}
}

bool turn_info::can_execute_command(hotkey::HOTKEY_COMMAND command) const
{
	switch(command) {

	//commands we can always do
	case hotkey::HOTKEY_LEADER:
	case hotkey::HOTKEY_CYCLE_UNITS:
	case hotkey::HOTKEY_CYCLE_BACK_UNITS:
	case hotkey::HOTKEY_ZOOM_IN:
	case hotkey::HOTKEY_ZOOM_OUT:
	case hotkey::HOTKEY_ZOOM_DEFAULT:
	case hotkey::HOTKEY_FULLSCREEN:
	case hotkey::HOTKEY_SCREENSHOT:
	case hotkey::HOTKEY_ACCELERATED:
	case hotkey::HOTKEY_TOGGLE_GRID:
	case hotkey::HOTKEY_STATUS_TABLE:
	case hotkey::HOTKEY_MUTE:
	case hotkey::HOTKEY_PREFERENCES:
	case hotkey::HOTKEY_OBJECTIVES:
	case hotkey::HOTKEY_UNIT_LIST:
	case hotkey::HOTKEY_STATISTICS:
	case hotkey::HOTKEY_QUIT_GAME:
	case hotkey::HOTKEY_SEARCH:
	case hotkey::HOTKEY_HELP:
	case hotkey::HOTKEY_USER_CMD:
	case hotkey::HOTKEY_SAVE_GAME:
		return true;

	case hotkey::HOTKEY_LOAD_GAME:
		return network::nconnections() == 0; //can only load games if not in a network game

	case hotkey::HOTKEY_SHOW_ENEMY_MOVES:
	case hotkey::HOTKEY_BEST_ENEMY_MOVES:
		return enemies_visible_;

	case hotkey::HOTKEY_SPEAK:
	case hotkey::HOTKEY_SPEAK_ALLY:
	case hotkey::HOTKEY_SPEAK_ALL:
	case hotkey::HOTKEY_CHAT_LOG:
		return network::nconnections() > 0;

	case hotkey::HOTKEY_REDO:
		return !browse_ && !redo_stack_.empty() && !commands_disabled;
	case hotkey::HOTKEY_UNDO:
		return !browse_ && !undo_stack_.empty() && !commands_disabled;

	case hotkey::HOTKEY_CONTINUE_MOVE: {
		if(browse_ || commands_disabled)
			return false;

		if(current_unit() != units_.end() && current_unit()->second.move_interrupted())
			return true;
		const unit_map::const_iterator i = units_.find(selected_hex_);
		if (i == units_.end()) return false;
		return i->second.move_interrupted();
	}

	case hotkey::HOTKEY_DELAY_SHROUD:
		return !browse_ && (current_team().uses_fog() || current_team().uses_shroud());
	case hotkey::HOTKEY_UPDATE_SHROUD:
		return !browse_ && !commands_disabled && current_team().auto_shroud_updates() == false;

	//commands we can only do if we are actually playing, not just viewing
	case hotkey::HOTKEY_UNIT_HOLD_POSITION:
	case hotkey::HOTKEY_END_UNIT_TURN:
	case hotkey::HOTKEY_RECRUIT:
	case hotkey::HOTKEY_REPEAT_RECRUIT:
	case hotkey::HOTKEY_RECALL:
	case hotkey::HOTKEY_ENDTURN:
		return !browse_ && !commands_disabled;

	//commands we can only do if there is an active unit
	case hotkey::HOTKEY_UNIT_DESCRIPTION:
		return current_unit() != units_.end();

	case hotkey::HOTKEY_RENAME_UNIT:
		return !commands_disabled &&
			current_unit() != units_.end() &&
			!(current_unit()->second.unrenamable()) &&
			current_unit()->second.side() == gui_.viewing_team()+1;

	case hotkey::HOTKEY_LABEL_TERRAIN:
		return !commands_disabled && map_.on_board(last_hex_) && !gui_.shrouded(last_hex_.x,last_hex_.y) && !is_observer();

	//commands we can only do if in debug mode
	case hotkey::HOTKEY_CREATE_UNIT:
	case hotkey::HOTKEY_CHANGE_UNIT_SIDE:
		return !commands_disabled && game_config::debug && map_.on_board(last_hex_);

	default:
		return false;
	}
}

namespace {

	struct cannot_execute {
		cannot_execute(const turn_info& info) : info_(info) {}
		bool operator()(const std::string& str) const {
			return !info_.can_execute_command(hotkey::get_hotkey(str).get_id());
		}
	private:
		const turn_info& info_;
	};

	struct not_in_context_menu {
		not_in_context_menu(const turn_info& info) : info_(info) {}
		bool operator()(const std::string& str) const {
			return !info_.in_context_menu(hotkey::get_hotkey(str).get_id());
		}
	private:
		const turn_info& info_;
	};
}

void turn_info::show_menu(const std::vector<std::string>& items_arg, int xloc, int yloc, bool context_menu)
{
	std::vector<std::string> items = items_arg;
	items.erase(std::remove_if(items.begin(),items.end(),cannot_execute(*this)),items.end());
	if(context_menu)
		items.erase(std::remove_if(items.begin(),items.end(),not_in_context_menu(*this)),items.end());
	if(items.empty())
		return;

	//if just one item is passed in, that means we should execute that item
	if(items.size() == 1 && items_arg.size() == 1) {
		hotkey::execute_command(gui_,hotkey::get_hotkey(items.front()).get_id(),this);
		return;
	}

	bool has_image = false;
	std::vector<std::string> menu;
	for(std::vector<std::string>::const_iterator i = items.begin(); i != items.end(); ++i) {
		const hotkey::hotkey_item hk = hotkey::get_hotkey(*i);

		std::stringstream str;
		//see if this menu item has an associated image
		std::string img(get_menu_image(hk.get_id()));
		if(img.empty() == false) {
			has_image = true;
			str << IMAGE_PREFIX << img << COLUMN_SEPARATOR;
		}

		str << hk.get_description() << COLUMN_SEPARATOR << hk.get_name();

		menu.push_back(str.str());
	}
	//If any of the menu items have an image, create an image column
	if(has_image)
		for(std::vector<std::string>::iterator i = menu.begin(); i != menu.end(); ++i)
			if(*(i->begin()) != IMAGE_PREFIX)
				i->insert(i->begin(), COLUMN_SEPARATOR);

	static const std::string style = "menu2";
	const int res = gui::show_dialog(gui_,NULL,"","",
			gui::MESSAGE,&menu,NULL,"",NULL,-1,NULL,NULL,xloc,yloc,&style);
	if (size_t(res) >= items.size())
		return;

	const hotkey::HOTKEY_COMMAND cmd = hotkey::get_hotkey(items[res]).get_id();
	hotkey::execute_command(gui_,cmd,this);
}

bool turn_info::unit_in_cycle(unit_map::const_iterator it) const
{
	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)) {
		const bool is_enemy = current_team().is_enemy(int(gui_.viewing_team()+1));
		return is_enemy == false || it->second.invisible(map_.underlying_terrain(it->first),status_.get_time_of_day().lawful_bonus,it->first,units_,teams_) == false;
	}

	return false;

}

void turn_info::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.type().is_skirmisher();
		const bool teleport = it->second.type().teleports();
		current_paths_ = paths(map_,status_,gameinfo_,units_,it->first,teams_,ignore_zocs,teleport,path_turns_);
		gui_.set_paths(&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);
		show_attack_options(it);
	} else {
		next_unit_ = gamemap::location();
	}
}

void turn_info::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()) {
		for(it = units_.end(); 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.type().is_skirmisher();
		const bool teleport = it->second.type().teleports();
		current_paths_ = paths(map_,status_,gameinfo_,units_,it->first,teams_,ignore_zocs,teleport,path_turns_);
		gui_.set_paths(&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 turn_info::end_turn()
{
	if(browse_)
		return;

	bool unmoved_units = false, partmoved_units = false, some_units_have_moved = false;
	for(unit_map::const_iterator un = units_.begin(); un != units_.end(); ++un) {
		if(un->second.side() == team_num_) {
			if(unit_can_move(un->first,units_,map_,teams_)) {
				if(!un->second.has_moved()) {
					unmoved_units = true;
				}

				partmoved_units = true;
			}
			if(un->second.has_moved() && ! un->second.has_goto()) {
				some_units_have_moved = true;
			}
		}
	}

	//Ask for confirmation if the player hasn't made any moves (other than gotos).
	if(preferences::confirm_no_moves() && ! some_units_have_moved) {
		const int res = gui::show_dialog(gui_,NULL,"",_("You have not started your turn yet. Do you really want to end your turn?"), gui::YES_NO);
		if(res != 0) {
			return;
		}
	}

	// Ask for confirmation if units still have movement left
	if(preferences::yellow_confirm() && partmoved_units) {
		const int res = gui::show_dialog(gui_,NULL,"",_("Some units have movement left. Do you really want to end your turn?"),gui::YES_NO);
		if (res != 0) {
			return;
		}
	} else if (preferences::green_confirm() && unmoved_units) {
		const int res = gui::show_dialog(gui_,NULL,"",_("Some units have movement left. Do you really want to end your turn?"),gui::YES_NO);
		if (res != 0) {
			return;
		}
	}

	//force any pending fog updates
	clear_undo_stack();
	end_turn_ = true;
	gui_.set_route(NULL);

	//auto-save
	config snapshot;
	write_game_snapshot(snapshot);
	try {
		recorder.save_game(_("Auto-Save"), snapshot, state_of_game_.starting_pos);
	} catch(game::save_game_failed&) {
		gui::show_dialog(gui_,NULL,"",_("Could not auto save the game. Please save the game manually."),gui::MESSAGE);
		//do not bother retrying, since the user can just save the game
	}

	recorder.end_turn();
}

void turn_info::goto_leader()
{
	const unit_map::const_iterator i = team_leader(team_num_,units_);
	if(i != units_.end()) {
		clear_shroud();
		gui_.scroll_to_tile(i->first.x,i->first.y,display::WARP);
	}
}

void turn_info::unit_hold_position()
{
	if(browse_)
		return;

	const unit_map::iterator un = units_.find(selected_hex_);
	if(un != units_.end() && un->second.side() == team_num_ && un->second.movement_left() >= 0) {
		un->second.set_hold_position(!un->second.hold_position());
		gui_.draw_tile(selected_hex_.x,selected_hex_.y);

		gui_.set_route(NULL);
		gui_.set_paths(NULL);
		current_paths_ = paths();
		gui_.draw();

		if(un->second.hold_position()) {
			un->second.set_user_end_turn(true);
			cycle_units();
		}
	}
}

void turn_info::end_unit_turn()
{
	if(browse_)
		return;

	const unit_map::iterator un = units_.find(selected_hex_);
	if(un != units_.end() && un->second.side() == team_num_ && un->second.movement_left() >= 0) {
		un->second.set_user_end_turn(!un->second.user_end_turn());
		if(un->second.hold_position() && !un->second.user_end_turn()){
		  un->second.set_hold_position(false);
		}
		gui_.draw_tile(selected_hex_.x,selected_hex_.y);

		gui_.set_route(NULL);
		gui_.set_paths(NULL);
		current_paths_ = paths();
		gui_.draw();

		if(un->second.user_end_turn()) {
			cycle_units();
		}
	}
}

void turn_info::undo()
{
	if(undo_stack_.empty())
		return;

	const command_disabler disable_commands;

	undo_action& action = undo_stack_.back();
	if(action.is_recall()) {
		player_info* const player = state_of_game_.get_player(teams_[team_num_-1].save_id());

		if(player == NULL) {
			LOG_STREAM(err, engine) << "trying to undo a recall for side " << team_num_
				<< ", which has no recall list!\n";
		} else {
			// Undo a recall action
			team& current_team = teams_[team_num_-1];
			if(units_.count(action.recall_loc) == 0) {
				return;
			}

			const unit& un = units_.find(action.recall_loc)->second;
			statistics::un_recall_unit(un);
			current_team.spend_gold(-game_config::recall_cost);

			std::vector<unit>& recall_list = player->available_units;
			recall_list.insert(recall_list.begin()+action.recall_pos,un);
			units_.erase(action.recall_loc);
			gui_.draw_tile(action.recall_loc.x,action.recall_loc.y);
		}
	} else {
		// Undo a move action
		const int starting_moves = action.starting_moves;
		std::vector<gamemap::location> route = action.route;
		std::reverse(route.begin(),route.end());
		const unit_map::iterator u = units_.find(route.front());
		if(u == units_.end()) {
			//this can actually happen if the scenario designer has abused the [allow_undo] command
			LOG_STREAM(err, engine) << "Illegal 'undo' found. Possible abuse of [allow_undo]?\n";
			return;
		}

		if(map_.is_village(route.front())) {
			get_village(route.front(),teams_,action.original_village_owner,units_);
		}

		action.starting_moves = u->second.movement_left();

		unit un = u->second;
		un.set_goto(gamemap::location());

		gui_.hide_unit(u->first,true);
		unit_display::move_unit(gui_,map_,route,un,status_.get_time_of_day(),units_,teams_);
		gui_.hide_unit(gamemap::location());

		units_.erase(u);
		un.set_movement(starting_moves);
		units_.insert(std::pair<gamemap::location,unit>(route.back(),un));
		gui_.draw_tile(route.back().x,route.back().y);
		gui_.update_display();
	}

	gui_.invalidate_unit();
	gui_.invalidate_game_status();

	redo_stack_.push_back(action);
	undo_stack_.pop_back();

	gui_.set_paths(NULL);
	current_paths_ = paths();
	selected_hex_ = gamemap::location();
	current_route_.steps.clear();
	gui_.set_route(NULL);

	recorder.undo();

	const bool shroud_cleared = clear_shroud();

	if(shroud_cleared) {
		gui_.recalculate_minimap();
	} else {
		gui_.redraw_minimap();
	}
}

void turn_info::redo()
{
	if(redo_stack_.empty())
		return;

	const command_disabler disable_commands;

	//clear routes, selected hex, etc
	gui_.set_paths(NULL);
	current_paths_ = paths();
	selected_hex_ = gamemap::location();
	current_route_.steps.clear();
	gui_.set_route(NULL);

	undo_action& action = redo_stack_.back();
	if(action.is_recall()) {
		player_info *player=state_of_game_.get_player(teams_[team_num_-1].save_id());
		if(!player) {
			LOG_STREAM(err, engine) << "trying to redo a recall for side " << team_num_
				<< ", which has no recall list!\n";
		} else {
			// Redo recall
			std::vector<unit>& recall_list = player->available_units;
			unit& un = recall_list[action.recall_pos];

			recorder.add_recall(action.recall_pos,action.recall_loc);
			const std::string& msg = recruit_unit(map_,team_num_,units_,un,action.recall_loc,&gui_);
			if(msg.empty()) {
				statistics::recall_unit(un);
				team& current_team = teams_[team_num_-1];
				current_team.spend_gold(game_config::recall_cost);
				recall_list.erase(recall_list.begin()+action.recall_pos);

				gui_.draw_tile(action.recall_loc.x,action.recall_loc.y);
			} else {
				recorder.undo();
				gui::show_dialog(gui_,NULL,"",msg,gui::OK_ONLY);
			}
		}
	} else {
		// Redo movement action
		const int starting_moves = action.starting_moves;
		std::vector<gamemap::location> route = action.route;
		const unit_map::iterator u = units_.find(route.front());
		if(u == units_.end()) {
			wassert(false);
			return;
		}

		action.starting_moves = u->second.movement_left();

		unit un = u->second;
		un.set_goto(gamemap::location());

		gui_.hide_unit(u->first,true);
		unit_display::move_unit(gui_,map_,route,un,status_.get_time_of_day(),units_,teams_);
		gui_.hide_unit(gamemap::location());

		units_.erase(u);
		un.set_movement(starting_moves);
		units_.insert(std::pair<gamemap::location,unit>(route.back(),un));

		if(map_.is_village(route.back())) {
			get_village(route.back(),teams_,un.side()-1,units_);
		}

		gui_.draw_tile(route.back().x,route.back().y);

		recorder.add_movement(route.front(),route.back());
	}
	gui_.invalidate_unit();
	gui_.invalidate_game_status();

	undo_stack_.push_back(action);
	redo_stack_.pop_back();
}

void turn_info::unit_description()
{
	const unit_map::const_iterator un = current_unit();
	if(un != units_.end()) {
		dialogs::show_unit_description(gui_, un->second);
	}
}

void turn_info::rename_unit()
{
	const unit_map::iterator un = current_unit();
	if(un == units_.end() || gui_.viewing_team()+1 != un->second.side())
		return;
	if(un->second.unrenamable())
		return;

	std::string name = un->second.description();
	const int res = gui::show_dialog(gui_,NULL,_("Rename Unit"),"", gui::OK_CANCEL,NULL,NULL,"",&name);
	if(res == 0) {
		recorder.add_rename(name, un->first);
		un->second.rename(name);
		gui_.invalidate_unit();
	}
}

void turn_info::save_game()
{
	save_game("",gui::OK_CANCEL);
}

void turn_info::load_game()
{
	bool show_replay = false;
	const std::string game = dialogs::load_game_dialog(gui_,terrain_config_,gameinfo_,&show_replay);
	if(game != "") {
		throw game::load_game_exception(game,show_replay);
	}
}

namespace {

bool is_illegal_file_char(char c)
{
	return c == '/' || c == '\\' || c == ':';
}

}

void turn_info::save_game(const std::string& message, gui::DIALOG_TYPE dialog_type)
{
	std::stringstream stream;

	const std::string ellipsed_name = font::make_text_ellipsis(state_of_game_.label,
			font::SIZE_NORMAL, 200);
	stream << ellipsed_name << " " << _("Turn")
	       << " " << status_.turn();
	std::string label = stream.str();
	if(dialog_type == gui::NULL_DIALOG && message != "") {
		label = message;
	}

	label.erase(std::remove_if(label.begin(),label.end(),is_illegal_file_char),label.end());

	const int res = dialog_type == gui::NULL_DIALOG ? 0 : dialogs::get_save_name(gui_,message,_("Name:"),&label,dialog_type);

	if(std::count_if(label.begin(),label.end(),is_illegal_file_char)) {
		gui::show_dialog(gui_,NULL,_("Error"),_("Save names may not contain colons, slashes, or backslashes. Please choose a different name."),gui::OK_ONLY);
		save_game(message,dialog_type);
		return;
	}

	if(res == 0) {
		config snapshot;
		write_game_snapshot(snapshot);
		try {
			recorder.save_game(label, snapshot, state_of_game_.starting_pos);
			if(dialog_type != gui::NULL_DIALOG) {
				gui::show_dialog(gui_,NULL,_("Saved"),_("The game has been saved"), gui::OK_ONLY);
			}
		} catch(game::save_game_failed&) {
			gui::show_dialog(gui_,NULL,_("Error"),_("The game could not be saved"),gui::MESSAGE);
			//do not bother retrying, since the user can just try to save the game again
		};
	}
}

unit_map::const_iterator turn_info::find_unit(const gamemap::location& hex) const
{
	if(gui_.fogged(hex.x,hex.y)) {
		return units_.end();
	}

	return find_visible_unit(units_,hex,map_,status_.get_time_of_day().lawful_bonus,teams_,viewing_team());
}

unit_map::iterator turn_info::find_unit(const gamemap::location& hex)
{
	if(gui_.fogged(hex.x,hex.y)) {
		return units_.end();
	}

	return find_visible_unit(units_,hex,map_,status_.get_time_of_day().lawful_bonus,teams_,viewing_team());
}

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

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

unit_map::const_iterator turn_info::current_unit() const
{
	unit_map::const_iterator res = find_unit(last_hex_);
	if(res != units_.end()) {
		return res;
	} else {
		return find_unit(selected_hex_);
	}
}

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

void turn_info::write_game_snapshot(config& start) const
{
	start.values = level_.values;

	start["snapshot"] = "yes";

	std::stringstream buf;
	buf << gui_.playing_team();
	start["playing_team"] = buf.str();

	for(std::vector<team>::const_iterator t = teams_.begin(); t != teams_.end(); ++t) {
		const int side_num = t - teams_.begin() + 1;

		config& side = start.add_child("side");
		t->write(side);
		side["no_leader"] = "yes";
		buf.str(std::string());
		buf << side_num;
		side["side"] = buf.str();

		for(std::map<gamemap::location,unit>::const_iterator i = units_.begin(); i != units_.end(); ++i) {
			if(i->second.side() == side_num) {
				config& u = side.add_child("unit");
				i->first.write(u);
				i->second.write(u);
			}
		}
	}

	status_.write(start);
	game_events::write_events(start);

	// Write terrain_graphics data in snapshot, too
	const config::child_list& terrains = level_.get_children("terrain_graphics");
	for(config::child_list::const_iterator tg = terrains.begin();
			tg != terrains.end(); ++tg) {

		start.add_child("terrain_graphics", **tg);
	}

	write_game(state_of_game_,start,WRITE_SNAPSHOT_ONLY);

	// Clobber gold values to make sure the snapshot uses the values
	// in [side] instead.
	const config::child_list& players=start.get_children("player");
	for(config::child_list::const_iterator pi=players.begin();
	    pi!=players.end(); ++pi) {
		(**pi)["gold"] = "-1000000";
	}

	//write out the current state of the map
	start["map_data"] = map_.write();

	gui_.labels().write(start);
}

void turn_info::toggle_grid()
{
	preferences::set_grid(!preferences::grid());
	gui_.invalidate_all();
}

void turn_info::status_table()
{
	std::stringstream heading;
	heading << HEADING_PREFIX << _("Leader") << COLUMN_SEPARATOR << ' ' << COLUMN_SEPARATOR
	        << _("Gold") << COLUMN_SEPARATOR
	        << _("Villages") << COLUMN_SEPARATOR
	        << _("Units") << COLUMN_SEPARATOR
	        << _("Upkeep") << COLUMN_SEPARATOR
	        << _("Income");

	gui::menu::basic_sorter sorter;
	sorter.set_redirect_sort(0,1).set_alpha_sort(1).set_numeric_sort(2).set_numeric_sort(3)
	      .set_numeric_sort(4).set_numeric_sort(5).set_numeric_sort(6).set_numeric_sort(7);

	if(game_config::debug)
		heading << COLUMN_SEPARATOR << _("Gold");

	std::vector<std::string> items;
	items.push_back(heading.str());

	const team& viewing_team = teams_[gui_.viewing_team()];

	//if the player is under shroud or fog, they don't get to see
	//details about the other sides, only their own side, allied sides and a ??? is
	//shown to demonstrate lack of information about the other sides
	bool fog = false;
	for(size_t n = 0; n != teams_.size(); ++n) {
		if(teams_[n].is_empty()) {
			continue;
		}

		const bool known = viewing_team.knows_about_team(n);
		const bool enemy = viewing_team.is_enemy(n+1);
		if(!known) {
			fog = true;
			continue;
		}

		const team_data data = calculate_team_data(teams_[n],n+1,units_);

		std::stringstream str;

		const unit_map::const_iterator leader = team_leader(n+1,units_);
		//output the number of the side first, and this will
		//cause it to be displayed in the correct colour
		if(leader != units_.end()) {
			str << IMAGE_PREFIX << leader->second.type().image() << COLUMN_SEPARATOR
			    << "\033[3" << lexical_cast<char, size_t>(n+1) << 'm' << leader->second.description() << COLUMN_SEPARATOR;
		} else {
			str << ' ' << COLUMN_SEPARATOR << "\033[3" << lexical_cast<char, size_t>(n+1) << "m-" << COLUMN_SEPARATOR;
		}

		if(enemy) {
			str << ' ' << COLUMN_SEPARATOR;
		} else {
			str << data.gold << COLUMN_SEPARATOR;
		}
		str << data.villages << COLUMN_SEPARATOR
		    << data.units << COLUMN_SEPARATOR << data.upkeep << COLUMN_SEPARATOR
		    << (data.net_income < 0 ? font::BAD_TEXT : font::NULL_MARKUP) << data.net_income;

		if(game_config::debug)
			str << COLUMN_SEPARATOR << teams_[n].gold();

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

	if(fog)
		items.push_back(IMAGE_PREFIX + std::string("random-enemy.png") + COLUMN_SEPARATOR +
		                IMAGE_PREFIX + "random-enemy.png");

	gui::show_dialog(gui_,NULL,"","",gui::CLOSE_ONLY,&items,
	                 NULL,"",NULL,0,NULL,NULL,-1,-1,NULL,NULL,"",&sorter);
}

void turn_info::recruit()
{
	if(browse_)
		return;

	team& current_team = teams_[team_num_-1];

	std::vector<unit> sample_units;

	gui_.draw(); //clear the old menu
	std::vector<std::string> item_keys;
	std::vector<std::string> items;
	const std::set<std::string>& recruits = current_team.recruits();
	for(std::set<std::string>::const_iterator it = recruits.begin(); it != recruits.end(); ++it) {
		const std::map<std::string,unit_type>::const_iterator
				u_type = gameinfo_.unit_types.find(*it);
		if(u_type == gameinfo_.unit_types.end()) {
			LOG_STREAM(err, engine) << "could not find unit '" << *it << "'\n";
			return;
		}

		item_keys.push_back(*it);

		const unit_type& type = u_type->second;

		//display units that we can't afford to recruit in red
		const char prefix = (type.cost() > current_team.gold() ? font::BAD_TEXT : font::NULL_MARKUP);

		std::stringstream description;

		description << font::IMAGE << type.image() << COLUMN_SEPARATOR << font::LARGE_TEXT
		            << prefix << type.language_name() << "\n"
		            << prefix << type.cost() << " " << sgettext("unit^Gold");
		items.push_back(description.str());
		sample_units.push_back(unit(&type,team_num_));
	}

	if(sample_units.empty()) {
		gui::show_dialog(gui_,NULL,"",_("You have no units available to recruit."),gui::MESSAGE);
		return;
	}

	int recruit_res = 0;

	{
		const events::event_context dialog_events_context;
		dialogs::unit_preview_pane unit_preview(gui_,&map_,sample_units);
		std::vector<gui::preview_pane*> preview_panes;
		preview_panes.push_back(&unit_preview);

		recruit_res = gui::show_dialog(gui_,NULL,_("Recruit"),
				_("Select unit:") + std::string("\n"),
				gui::OK_CANCEL,&items,&preview_panes,"",NULL,-1,NULL,NULL,-1,-1,
				NULL,NULL,"recruit_and_recall");
	}

	if(recruit_res != -1) {
		do_recruit(item_keys[recruit_res]);
	}
}

void turn_info::do_recruit(const std::string& name)
{
	team& current_team = teams_[team_num_-1];

	//search for the unit to be recruited in recruits
	int recruit_num = 0;
	const std::set<std::string>& recruits = current_team.recruits();
	for(std::set<std::string>::const_iterator r = recruits.begin(); ; ++r) {
		if (r == recruits.end()) {
			return;
		}

		if (name == *r) {
			break;
		}
		++recruit_num;
	}

	const std::map<std::string,unit_type>::const_iterator
			u_type = gameinfo_.unit_types.find(name);
	wassert(u_type != gameinfo_.unit_types.end());

	if(u_type->second.cost() > current_team.gold()) {
		gui::show_dialog(gui_,NULL,"",
		     _("You don't have enough gold to recruit that unit"),gui::OK_ONLY);
	} else {
		last_recruit_ = name;

		//create a unit with traits
		recorder.add_recruit(recruit_num,last_hex_);
		unit new_unit(&(u_type->second),team_num_,true);
		gamemap::location loc = last_hex_;
		const std::string& msg = recruit_unit(map_,team_num_,units_,new_unit,loc,&gui_);
		if(msg.empty()) {
			current_team.spend_gold(u_type->second.cost());
			statistics::recruit_unit(new_unit);

			clear_undo_stack();
			redo_stack_.clear();

			clear_shroud();

			gui_.recalculate_minimap();
			gui_.invalidate_game_status();
			gui_.invalidate_all();
		} else {
			recorder.undo();
			gui::show_dialog(gui_,NULL,"",msg,gui::OK_ONLY);
		}
	}
}

void turn_info::repeat_recruit()
{
	if(last_recruit_.empty() == false)
		do_recruit(last_recruit_);
}

//a class to handle deleting an item from the recall list
namespace {

class delete_recall_unit : public gui::dialog_button_action
{
public:
	delete_recall_unit(display& disp, std::vector<unit>& units) : disp_(disp), units_(units) {}
private:
	gui::dialog_button_action::RESULT button_pressed(int menu_selection);

	display& disp_;
	std::vector<unit>& units_;
};

gui::dialog_button_action::RESULT delete_recall_unit::button_pressed(int menu_selection)
{
	const size_t index = size_t(menu_selection);
	if(index < units_.size()) {
		const unit& u = units_[index];

		//if the unit is of level > 1, or is close to advancing, we warn the player
		//about it
		std::string message = "";
		if(u.type().level() > 1) {
			message = _("My lord, this unit is an experienced one, having advanced levels! Do you really want to dismiss $noun?");
		} else if(u.experience() > u.max_experience()/2) {
			message = _("My lord, this unit is close to advancing a level! Do you really want to dismiss $noun?");
		}

		if(message != "") {
			utils::string_map symbols;
			symbols["noun"] = (u.gender() == unit_race::MALE ? _("him") : _("her"));
			message = utils::interpolate_variables_into_string(message, &symbols);

			const int res = gui::show_dialog(disp_,NULL,"",message,gui::YES_NO);
			if(res != 0) {
				return gui::dialog_button_action::NO_EFFECT;
			}
		}

		units_.erase(units_.begin() + index);
		recorder.add_disband(index);
		return gui::dialog_button_action::DELETE_ITEM;
	} else {
		return gui::dialog_button_action::NO_EFFECT;
	}
}

} //end anon namespace

void turn_info::recall()
{
	if(browse_)
		return;

	player_info *player = state_of_game_.get_player(teams_[team_num_-1].save_id());
	if(!player) {
		LOG_STREAM(err, engine) << "cannot recall a unit for side " << team_num_
			<< ", which has no recall list!\n";
		return;
	}

	team& current_team = teams_[team_num_-1];
	std::vector<unit>& recall_list = player->available_units;

	//sort the available units into order by value
	//so that the most valuable units are shown first
	sort_units(recall_list);

	gui_.draw(); //clear the old menu

	if(level_["disallow_recall"] == "yes") {
		gui::show_dialog(gui_,NULL,"",_("You are separated from your soldiers and may not recall them"));
	} else if(recall_list.empty()) {
		gui::show_dialog(gui_,NULL,"",_("There are no troops available to recall\n\
(You must have veteran survivors from a previous scenario)"));
	} else {
		std::vector<std::string> options;

		std::ostringstream heading;
		heading << HEADING_PREFIX << COLUMN_SEPARATOR << _("Type")
			    << COLUMN_SEPARATOR << _("Name")
				<< COLUMN_SEPARATOR << _("Level")
				<< COLUMN_SEPARATOR << _("XP");

		gui::menu::basic_sorter sorter;
		sorter.set_alpha_sort(1).set_alpha_sort(2).set_id_sort(3).set_numeric_sort(4);

		options.push_back(heading.str());

		for(std::vector<unit>::const_iterator u = recall_list.begin(); u != recall_list.end(); ++u) {
			std::stringstream option;
			const std::string& description = u->description().empty() ? "-" : u->description();
			option << IMAGE_PREFIX << u->type().image() << COLUMN_SEPARATOR
			       << u->type().language_name() << COLUMN_SEPARATOR
			       << description << COLUMN_SEPARATOR
			       << u->type().level() << COLUMN_SEPARATOR
			       << u->experience() << "/";

			if(u->can_advance() == false) {
				option << "-";
			} else {
				option << u->max_experience();
			}

			options.push_back(option.str());
		}

		delete_recall_unit recall_deleter(gui_,recall_list);
		gui::dialog_button delete_button(&recall_deleter,_("Dismiss Unit"));
		std::vector<gui::dialog_button> buttons;
		buttons.push_back(delete_button);

		int res = 0;

		{
			const events::event_context dialog_events_context;
			dialogs::unit_preview_pane unit_preview(gui_,&map_,recall_list);
			std::vector<gui::preview_pane*> preview_panes;
			preview_panes.push_back(&unit_preview);

			res = gui::show_dialog(gui_,NULL,_("Recall"),
					_("Select unit:") + std::string("\n"),
					gui::OK_CANCEL,&options,
					&preview_panes,"",NULL,-1,
					NULL,NULL,-1,-1,NULL,&buttons,"",&sorter);
		}

		if(res >= 0) {
			if(current_team.gold() < game_config::recall_cost) {
				std::stringstream msg;
				utils::string_map i18n_symbols;
				i18n_symbols["cost"] = lexical_cast<std::string>(game_config::recall_cost);
				msg << vgettext("You must have at least $cost gold pieces to recall a unit", i18n_symbols);
				gui::show_dialog(gui_,NULL,"",msg.str());
			} else {
				unit& un = recall_list[res];
				gamemap::location loc = last_hex_;
				recorder.add_recall(res,loc);
				const std::string err = recruit_unit(map_,team_num_,units_,un,loc,&gui_);
				if(!err.empty()) {
					recorder.undo();
					gui::show_dialog(gui_,NULL,"",err,gui::OK_ONLY);
				} else {
					statistics::recall_unit(un);
					current_team.spend_gold(game_config::recall_cost);

					undo_stack_.push_back(undo_action(un,loc,res));
					redo_stack_.clear();

					clear_shroud();
	
					recall_list.erase(recall_list.begin()+res);
					gui_.invalidate_game_status();
					gui_.invalidate_all();
				}
			}
		}
	}
}

bool turn_info::has_friends() const
{
	if(is_observer()) {
		return false;
	}

	for(size_t n = 0; n != teams_.size(); ++n) {
		if(n != gui_.viewing_team() && teams_[gui_.viewing_team()].team_name() == teams_[n].team_name() && teams_[n].is_network()) {
			return true;
		}
	}

	return false;
}

void turn_info::speak()
{
	create_textbox(floating_textbox::TEXTBOX_MESSAGE,_("Message:"), has_friends() ? _("Send to allies only") : "", preferences::message_private());
}

void turn_info::do_speak(const std::string& message, bool allies_only)
{
	if(message == "") {
		return;
	}

	config cfg;
	cfg["description"] = preferences::login();
	cfg["message"] = message;

	const int side = is_observer() ? 0 : gui_.viewing_team()+1;
	if(!is_observer()) {
		cfg["side"] = lexical_cast<std::string>(side);
	}

	bool private_message = has_friends() && allies_only;

	if(private_message) {
		cfg["team_name"] = teams_[gui_.viewing_team()].team_name();
	}

	recorder.speak(cfg);
	gui_.add_chat_message(cfg["description"],side,message,
		                  private_message ? display::MESSAGE_PRIVATE : display::MESSAGE_PUBLIC);
}

void turn_info::create_unit()
{
	std::vector<std::string> options;
	std::vector<unit> unit_choices;
	for(game_data::unit_type_map::const_iterator i = gameinfo_.unit_types.begin(); i != gameinfo_.unit_types.end(); ++i) {
		options.push_back(i->second.language_name());
		unit_choices.push_back(unit(&i->second,1,false));
		unit_choices.back().new_turn();
	}

	int choice = 0;

	{
		const events::event_context dialog_events_context;
		dialogs::unit_preview_pane unit_preview(gui_,&map_,unit_choices);
		std::vector<gui::preview_pane*> preview_panes;
		preview_panes.push_back(&unit_preview);

		// FIXME: this relies on the string being in -lib
		// already from its appearance in hotkeys.cpp, since
		// xgettext will ignore dsgettext2 (another hack)
		choice = gui::show_dialog(gui_,NULL,"",dsgettext2(PACKAGE "-lib","Create Unit (Debug!)"),
		                          gui::OK_CANCEL,&options,&preview_panes);
	}

	if (size_t(choice) < unit_choices.size()) {
		units_.erase(last_hex_);
		units_.insert(std::pair<gamemap::location,unit>(last_hex_,unit_choices[choice]));
		gui_.invalidate(last_hex_);
		gui_.invalidate_unit();
	}
}

void turn_info::change_unit_side()
{
	const unit_map::iterator i = units_.find(last_hex_);
	if(i == units_.end()) {
		return;
	}

	int side = i->second.side();
	++side;
	if(side > team::nteams()) {
		side = 1;
	}

	i->second.set_side(side);
}

void turn_info::preferences()
{
	preferences::show_preferences_dialog(gui_,terrain_config_);
	gui_.redraw_everything();
}

void turn_info::objectives()
{
	dialogs::show_objectives(gui_, level_, current_team().objectives());
	current_team().reset_objectives_changed();
}

void turn_info::unit_list()
{
	const std::string heading = std::string(1,HEADING_PREFIX) +
	                            _("Type") + COLUMN_SEPARATOR +
	                            _("Name") + COLUMN_SEPARATOR +
	                            _("HP") + COLUMN_SEPARATOR +
	                            _("XP") + COLUMN_SEPARATOR +
	                            _("Traits") + COLUMN_SEPARATOR +
	                            _("Moves") + COLUMN_SEPARATOR +
	                            _("Location");

	gui::menu::basic_sorter sorter;
	sorter.set_alpha_sort(0).set_alpha_sort(1).set_numeric_sort(2).set_numeric_sort(3)
		  .set_alpha_sort(4).set_numeric_sort(5).set_numeric_sort(6);

	std::vector<std::string> items;
	items.push_back(heading);

	std::vector<gamemap::location> locations_list;
	std::vector<unit> units_list;
	for(unit_map::const_iterator i = units_.begin(); i != units_.end(); ++i) {
		if(i->second.side() != (gui_.viewing_team()+1))
			continue;

		std::stringstream row;
		row << i->second.type().language_name() << COLUMN_SEPARATOR
		    << i->second.description() << COLUMN_SEPARATOR
		    << i->second.hitpoints() << "/" << i->second.max_hitpoints() << COLUMN_SEPARATOR
		    << i->second.experience() << "/";

		if(i->second.can_advance() == false)
			row << "-";
		else
			row << i->second.max_experience();

		row << COLUMN_SEPARATOR
		    << i->second.traits_description() << COLUMN_SEPARATOR
		    << i->second.movement_left() << "/"
		    << i->second.total_movement() << COLUMN_SEPARATOR
		    << i->first;

		items.push_back(row.str());

		locations_list.push_back(i->first);
		units_list.push_back(i->second);
	}

	int selected = 0;

	{
		const events::event_context dialog_events_context;
		dialogs::unit_preview_pane unit_preview(gui_,&map_,units_list);
		std::vector<gui::preview_pane*> preview_panes;
		preview_panes.push_back(&unit_preview);

		selected = gui::show_dialog(gui_,NULL,_("Unit List"),"",
		                            gui::OK_ONLY,&items,&preview_panes,
									"",NULL,0,NULL,NULL,-1,-1,NULL,NULL,"",&sorter);
	}

	if(selected > 0 && selected < int(locations_list.size())) {
		const gamemap::location& loc = locations_list[selected];
		gui_.scroll_to_tile(loc.x,loc.y,display::WARP);
	}
}

std::vector<std::string> turn_info::create_unit_table(const statistics::stats::str_int_map& m)
{
	std::vector<std::string> table;
	for(statistics::stats::str_int_map::const_iterator i = m.begin(); i != m.end(); ++i) {
		const game_data::unit_type_map::const_iterator type = gameinfo_.unit_types.find(i->first);
		if(type == gameinfo_.unit_types.end()) {
			continue;
		}

		std::stringstream str;
		str << IMAGE_PREFIX << type->second.image() << COLUMN_SEPARATOR
		    << type->second.language_name() << COLUMN_SEPARATOR << i->second << "\n";
		table.push_back(str.str());
	}

	return table;
}

void turn_info::show_statistics()
{
	const statistics::stats& stats = statistics::calculate_stats(0,gui_.viewing_team()+1);
	std::vector<std::string> items;

	{
		std::stringstream str;
		str << _("Recruits") << COLUMN_SEPARATOR << statistics::sum_str_int_map(stats.recruits);
		items.push_back(str.str());
	}

	{
		std::stringstream str;
		str << _("Recalls") << COLUMN_SEPARATOR << statistics::sum_str_int_map(stats.recalls);
		items.push_back(str.str());
	}

	{
		std::stringstream str;
		str << _("Advancements") << COLUMN_SEPARATOR << statistics::sum_str_int_map(stats.advanced_to);
		items.push_back(str.str());
	}

	{
		std::stringstream str;
		str << _("Losses") << COLUMN_SEPARATOR << statistics::sum_str_int_map(stats.deaths);
		items.push_back(str.str());
	}

	{
		std::stringstream str;
		str << _("Kills") << COLUMN_SEPARATOR << statistics::sum_str_int_map(stats.killed);
		items.push_back(str.str());
	}

	{
		std::stringstream str;
		str << _("Damage Inflicted") << COLUMN_SEPARATOR << stats.damage_inflicted;
		items.push_back(str.str());
	}

	{
		std::stringstream str;
		str << _("Damage Taken") << COLUMN_SEPARATOR << stats.damage_taken;
		items.push_back(str.str());
	}

	{
		std::stringstream str;
		str << _("Damage Inflicted (EV)") << COLUMN_SEPARATOR
		    << (stats.expected_damage_inflicted / 100.0);
		items.push_back(str.str());
	}

	{
		std::stringstream str;
		str << _("Damage Taken (EV)") <<  COLUMN_SEPARATOR
		    << (stats.expected_damage_taken / 100.0);
		items.push_back(str.str());
	}

	for(;;) {
		const int res = gui::show_dialog(gui_, NULL, _("Statistics"), "", gui::OK_CANCEL, &items);
		std::string title;
		std::vector<std::string> items_sub;

		switch(res) {
		case 0:
			items_sub = create_unit_table(stats.recruits);
			title = _("Recruits");
			break;
		case 1:
			items_sub = create_unit_table(stats.recalls);
			title = _("Recalls");
			break;
		case 2:
			items_sub = create_unit_table(stats.advanced_to);
			title = _("Advancements");
			break;
		case 3:
			items_sub = create_unit_table(stats.deaths);
			title = _("Losses");
			break;
		case 4:
			items_sub = create_unit_table(stats.killed);
			title = _("Kills");
			break;
		default:
			return;
		}

		if (items_sub.empty() == false)
			gui::show_dialog(gui_, NULL, "", title, gui::OK_ONLY, &items_sub);
	}
}

void turn_info::search()
{
	std::ostringstream msg;
	msg << _("Search");
	if(last_search_hit_.valid()) {
		msg << " [" << last_search_ << "]";
	}
	msg << ':';
	create_textbox(floating_textbox::TEXTBOX_SEARCH,msg.str());
}

void turn_info::user_command()
{
	create_textbox(floating_textbox::TEXTBOX_COMMAND,sgettext("prompt^Command:"));
}

void turn_info::show_help()
{
	help::show_help(gui_);
}

void turn_info::show_chat_log()
{
	std::string text = recorder.build_chat_log(teams_[gui_.viewing_team()].team_name());
	gui::show_dialog(gui_,NULL,_("Chat Log"),"",gui::CLOSE_ONLY,NULL,NULL,"",&text);
}

void turn_info::do_search(const std::string& new_search)
{
	if(new_search.empty() == false && new_search != last_search_)
		last_search_ = new_search;

	if(last_search_.empty()) return;

	bool found = false;
	gamemap::location loc = last_search_hit_;
	//If this is a location search, just center on that location.
	std::vector<std::string> args = utils::split(last_search_, ',');
	if(args.size() == 2) {
		int x, y;
		x = lexical_cast_default<int>(args[0], 0)-1;
		y = lexical_cast_default<int>(args[1], 0)-1;
		if(x >= 0 && x < map_.x() && y >= 0 && y < map_.y()) {
			loc = gamemap::location(x,y);
			found = true;
		}
	}
	//Start scanning the game map
	if(loc.valid() == false)
		loc = gamemap::location(map_.x()-1,map_.y()-1);
	gamemap::location start = loc;
	while (!found) {
		//Move to the next location
		loc.x = (loc.x + 1) % map_.x();
		if(loc.x == 0)
			loc.y = (loc.y + 1) % map_.y();

		//Search label
		const std::string label = gui_.labels().get_label(loc);
		if(label.empty() == false) {
			if(std::search(label.begin(), label.end(),
					last_search_.begin(), last_search_.end(),
					chars_equal_insensitive) != label.end()) {
				found = !gui_.shrouded(loc.x, loc.y);
			}
		}
		//Search unit name
		unit_map::const_iterator ui = units_.find(loc);
		if(ui != units_.end()) {
			const std::string name = ui->second.description();
			if(std::search(name.begin(), name.end(),
					last_search_.begin(), last_search_.end(),
					chars_equal_insensitive) != name.end()) {
				found = !gui_.fogged(loc.x, loc.y);
			}
		}
		if(loc == start)
			break;
	}
	if(found) {
		last_search_hit_ = loc;
		gui_.scroll_to_tile(loc.x,loc.y,display::WARP,false);
		gui_.highlight_hex(loc);
	} else {
		last_search_hit_ = gamemap::location();
		//Not found, inform the player
		utils::string_map symbols;
		symbols["search"] = last_search_;
		const std::string msg = utils::interpolate_variables_into_string(
			_("Couldn't find label or unit containing the string '$search'."),&symbols);
		gui::show_dialog(gui_,NULL,"",msg);
	}
}

void turn_info::do_command(const std::string& str)
{
	const std::string::const_iterator i = std::find(str.begin(),str.end(),' ');
	const std::string cmd(str.begin(),i);
	const std::string data(i == str.end() ? str.end() : i+1,str.end());

	if(cmd == "refresh") {
		image::flush_cache();
		gui_.redraw_everything();
	} else if (cmd == "droid") {
		const int side = lexical_cast_default<int>(data, 1);
		const size_t index = static_cast<size_t>(side - 1);
		if (index >= teams_.size() || teams_[index].is_network()) {
			//do nothing
		} else if (teams_[index].is_human()) {
			teams_[index].make_ai();
			close_textbox();
			if(team_num_ == side) {
				throw end_turn_exception(side);
			}
		} else if (teams_[index].is_ai()) {
			teams_[index].make_human();
		}
	} else if (cmd == "theme") {
	        int action = 0;
		std::vector<std::string> options=theme::get_known_themes();
		std::string current_theme=_("Saved Theme Preference: ")+preferences::theme();
		action = gui::show_dialog(gui_,NULL,"",current_theme,gui::OK_CANCEL,&options);
		if(action >-1){
		  preferences::set_theme(options[action]);
		//it would be preferable for the new theme to take effect
		//immediately, however, this will have to do for now.
		  gui::show_dialog(gui_,NULL,"",_("New theme will take effect on next new or loaded game."),gui::MESSAGE);
		}

	} else if(cmd == "ban" || cmd == "kick") {
		config cfg;
		config& ban = cfg.add_child(cmd);
		ban["username"] = data;

		network::send_data(cfg);
	} else if(cmd == "control") {
		const std::string::const_iterator j = std::find(data.begin(),data.end(),' ');
		if(j != data.end()) {
			const std::string side(data.begin(),j);
			const std::string player(j+1,data.end());
			int side_num;
			try {
				side_num = lexical_cast<int, std::string>(side);
			} catch(bad_lexical_cast& bad_lexical) {
				return;
			}
			if(side_num > 0 && side_num <= teams_.size()) {
				if(teams_[static_cast<size_t>(side_num - 1)].is_human()) {
					change_side_controller(side,player,true);
					if(team_num_ == side_num) {
						teams_[static_cast<size_t>(side_num - 1)].make_network();
						close_textbox();
						throw end_turn_exception(side_num);
					}
				} else {
					change_side_controller(side,player);
				}
			}
		}
	} else if(cmd == "clear") {
		gui_.clear_chat_messages();
	} else if(cmd == "w") {
		save_game(data,gui::NULL_DIALOG);
	} else if(cmd == "wq") {
		save_game(data,gui::NULL_DIALOG);
		throw end_level_exception(QUIT);
	} else if(cmd == "q!" || cmd == "q") {
		throw end_level_exception(QUIT);
	} else if(cmd == "ignore_replay_errors") {
		game_config::ignore_replay_errors = (data != "off") ? true : false;
	} else if(cmd == "n" && game_config::debug) {
		throw end_level_exception(VICTORY);
	} else if(cmd == "debug" && network::nconnections() == 0) {
		game_config::debug = true;
	} else if(game_config::debug && cmd == "unit") {
		const unit_map::iterator i = current_unit();
		if(i != units_.end()) {
			const std::string::const_iterator j = std::find(data.begin(),data.end(),'=');
			if(j != data.end()) {
				const std::string name(data.begin(),j);
				const std::string value(j+1,data.end());
				config cfg;
				i->second.write(cfg);
				cfg[name] = value;
				i->second = unit(gameinfo_,cfg);

				gui_.invalidate(i->first);
				gui_.invalidate_unit();
			}
		}
	} else if(game_config::debug && cmd == "create" && map_.on_board(last_hex_)) {
		const game_data::unit_type_map::const_iterator i = gameinfo_.unit_types.find(data);
		if(i == gameinfo_.unit_types.end()) {
			return;
		}

		units_.erase(last_hex_);
		units_.insert(std::pair<gamemap::location,unit>(last_hex_,unit(&i->second,1,false)));
		gui_.invalidate(last_hex_);
		gui_.invalidate_unit();
	} else if(game_config::debug && cmd == "gold") {
		current_team().spend_gold(-lexical_cast_default<int>(data,1000));
		gui_.redraw_everything();
	}
}

void turn_info::change_side_controller(const std::string& side, const std::string& player, bool orphan_side)
{
	config cfg;
	config& change = cfg.add_child("change_controller");
	change["side"] = side;
	change["player"] = player;

	if(orphan_side) {
		change["orphan_side"] = "yes";
	}

	network::send_data(cfg);
}

void turn_info::label_terrain()
{
	if(map_.on_board(last_hex_) == false) {
		return;
	}

	std::string label = gui_.labels().get_label(last_hex_);
	const int res = gui::show_dialog(gui_,NULL,_("Place Label"),"",gui::OK_CANCEL,
	                                 NULL,NULL,_("Label:"),&label,
					 map_labels::get_max_chars());
	if(res == 0) {
		gui_.labels().set_label(last_hex_,label);
		recorder.add_label(label,last_hex_);
	}
}

// Returns true if any enemy units are visible.
bool turn_info::enemies_visible() const
{
	// If we aren't using fog/shroud, this is easy :)
	if(current_team().uses_fog() == false && current_team().uses_shroud() == false)
		return true;

	//See if any enemies are visible
	for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u)
		if(current_team().is_enemy(u->second.side()) && !gui_.fogged(u->first.x,u->first.y))
			return true;

	return false;
}

// Highlights squares that an enemy could move to on their turn
void turn_info::show_enemy_moves(bool ignore_units)
{
	all_paths_ = paths();

	// Compute enemy movement positions
	for(unit_map::iterator u = units_.begin(); u != units_.end(); ++u) {
		if(current_team().is_enemy(u->second.side()) && !gui_.fogged(u->first.x,u->first.y) && !u->second.stone()) {
			const unit_movement_resetter move_reset(u->second);
			const bool is_skirmisher = u->second.type().is_skirmisher();
			const bool teleports = u->second.type().teleports();
			unit_map units;
			units.insert(*u);
			const paths& path = paths(map_,status_,gameinfo_,ignore_units?units:units_,
									  u->first,teams_,is_skirmisher,teleports);

			for (paths::routes_map::const_iterator route = path.routes.begin(); route != path.routes.end(); ++route) {
				// map<...>::operator[](const key_type& key) inserts key into
				// the map with a default instance of value_type
				all_paths_.routes[route->first];
			}
		}
	}

	gui_.set_paths(&all_paths_);
}

void turn_info::toggle_shroud_updates() {
	bool auto_shroud = current_team().auto_shroud_updates();
	// If we're turning automatic shroud updates on, then commit all moves
	if(auto_shroud == false) update_shroud_now();
	current_team().set_auto_shroud_updates(!auto_shroud);
}

bool turn_info::clear_shroud()
{
	bool cleared = current_team().auto_shroud_updates() &&
		::clear_shroud(gui_,status_,map_,gameinfo_,units_,teams_,team_num_-1);
	enemies_visible_ = enemies_visible();
	return cleared;
}

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

void turn_info::update_shroud_now()
{
	clear_undo_stack();
}

void turn_info::continue_move()
{
	unit_map::iterator i = current_unit();
	if(i == units_.end() || i->second.move_interrupted() == false) {
		i = units_.find(selected_hex_);
		if (i == units_.end() || i->second.move_interrupted() == false) return;
	}
	move_unit_to_loc(i,i->second.get_interrupted_move(),true);
}

turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg, network::connection from, std::deque<config>& backlog)
{
	if(cfg.child("observer") != NULL) {
		const config::child_list& observers = cfg.get_children("observer");
		for(config::child_list::const_iterator ob = observers.begin(); ob != observers.end(); ++ob) {
			gui_.add_observer((**ob)["name"]);
		}
	}

	if(cfg.child("observer_quit") != NULL) {
		const config::child_list& observers = cfg.get_children("observer_quit");
		for(config::child_list::const_iterator ob = observers.begin(); ob != observers.end(); ++ob) {
			gui_.remove_observer((**ob)["name"]);
		}
	}

	if(cfg.child("leave_game") != NULL) {
		throw network::error("");
	}

	bool turn_end = false;

	const config::child_list& turns = cfg.get_children("turn");
	if(turns.empty() == false && from != network::null_connection) {
		//forward the data to other peers
		network::send_data_all_except(cfg,from);
	}

	for(config::child_list::const_iterator t = turns.begin(); t != turns.end(); ++t) {

		if(turn_end == false) {
			replay replay_obj(**t);
			replay_obj.start_replay();

			try {
				turn_end = do_replay(gui_,map_,gameinfo_,units_,teams_,
				team_num_,status_,state_of_game_,&replay_obj);
			} catch(replay::error&) {
				//notify remote hosts of out of sync error
				config cfg;
				config& info = cfg.add_child("info");
				info["type"] = "termination";
				info["condition"] = "out of sync";
				network::send_data(cfg);

				save_game(_("The games are out of sync and will have to exit. Do you want to save an error log of your game?"),gui::YES_NO);

				//throw e;
			}

			recorder.add_config(**t,replay::MARK_AS_SENT);
		} else {

			//this turn has finished, so push the remaining moves
			//into the backlog
			backlog.push_back(config());
			backlog.back().add_child("turn",**t);
		}
	}

	if(const config* change= cfg.child("change_controller")) {
		const int side = lexical_cast_default<int>((*change)["side"],1);
		const size_t index = static_cast<size_t>(side-1);

		const std::string& controller = (*change)["controller"];

		if(index < teams_.size()) {
			if(controller == "human") {
				teams_[index].make_human();
				gui_.set_team(index);
			} else if(controller == "network") {
				teams_[index].make_network();
			} else if(controller == "ai") {
				teams_[index].make_ai();
			}

			return PROCESS_RESTART_TURN;
		}
	}

	//if a side has dropped out of the game.
	if(cfg["side_drop"] != "") {
		const size_t side = atoi(cfg["side_drop"].c_str())-1;
		if(side >= teams_.size()) {
			LOG_STREAM(err, network) << "unknown side " << side << " is dropping game\n";
			throw network::error("");
		}

		int action = 0;

		std::vector<std::string> observers;

		//see if the side still has a leader alive. If they have
		//no leader, we assume they just want to be replaced by
		//the AI.
		const unit_map::const_iterator leader = find_leader(units_,side+1);
		if(leader != units_.end()) {
			std::vector<std::string> options;
			options.push_back(_("Replace with AI"));
			options.push_back(_("Replace with local player"));
			options.push_back(_("Abort game"));

			for(std::set<std::string>::const_iterator ob = gui_.observers().begin(); ob != gui_.observers().end(); ++ob) {
				options.push_back(_("Replace with ") + *ob);
				observers.push_back(*ob);
			}

			const std::string msg = leader->second.description() + " " + _("has left the game. What do you want to do?");
			action = gui::show_dialog(gui_,NULL,"",msg,gui::OK_ONLY,&options);
		}

		//make the player an AI, and redo this turn, in case
		//it was the current player's team who has just changed into
		//an AI.
		switch(action) {
			case 0:
				teams_[side].make_ai();
				return PROCESS_RESTART_TURN;
			case 1:
				teams_[side].make_human();
				return PROCESS_RESTART_TURN;
			default:
				if (action > 2) {
					const size_t index = static_cast<size_t>(action - 3);
					if (index < observers.size()) {
						teams_[side].make_network();
						change_side_controller(cfg["side_drop"], observers[index], true /*is orphaned side*/);
					} else {
						teams_[side].make_ai();
					}
					return PROCESS_RESTART_TURN;
				}
				break;
		}
		throw network::error("");
	}

	return turn_end ? PROCESS_END_TURN : PROCESS_CONTINUE;
}

void turn_info::update_textbox_location()
{
	if (textbox_.box == NULL)
		return;

	const SDL_Rect& area = gui_.map_area();

	const int border_size = 10;

	const int ypos = area.y+area.h-30 - (textbox_.check != NULL ? textbox_.check->height() + border_size : 0);

	if (textbox_.label != 0)
		font::remove_floating_label(textbox_.label);

	textbox_.label = font::add_floating_label(textbox_.label_string,font::SIZE_NORMAL,
			font::YELLOW_COLOUR,area.x+border_size,ypos,0,0,-1, area,font::LEFT_ALIGN);

	if (textbox_.label == 0)
		return;

	const SDL_Rect& label_area = font::get_floating_label_rect(textbox_.label);
	const int textbox_width = area.w - label_area.w - border_size*3;

	if(textbox_width <= 0) {
		font::remove_floating_label(textbox_.label);
		return;
	}

	if(textbox_.box != NULL) {
		textbox_.box->set_volatile(true);
		const SDL_Rect rect = {
			area.x + label_area.w + border_size*2, ypos,
			textbox_width, textbox_.box->height()
		};
		textbox_.box->set_location(rect);
	}

	if(textbox_.check != NULL) {
		textbox_.check->set_volatile(true);
		textbox_.check->set_location(textbox_.box->location().x,textbox_.box->location().y + textbox_.box->location().h + border_size);
	}
}

void turn_info::create_textbox(floating_textbox::MODE mode, const std::string& label, const std::string& check_label, bool checked)
{
	close_textbox();

	textbox_.label_string = label;
	textbox_.mode = mode;

	if(check_label != "") {
		textbox_.check.assign(new gui::button(gui_.video(),check_label,gui::button::TYPE_CHECK));
		textbox_.check->set_check(checked);
	}


	textbox_.box.assign(new gui::textbox(gui_.video(),100,"",true,256,0.8,0.6));

	update_textbox_location();
}

void turn_info::close_textbox()
{
	if(textbox_.active() == false) {
		return;
	}
	if(textbox_.check != NULL) {
		if(textbox_.mode == floating_textbox::TEXTBOX_MESSAGE) {
			preferences::set_message_private(textbox_.check->checked());
		}
	}
	textbox_.box.assign(NULL);
	textbox_.check.assign(NULL);
	font::remove_floating_label(textbox_.label);
	textbox_.mode = floating_textbox::TEXTBOX_NONE;
	gui_.invalidate_all();
}

void turn_info::enter_textbox()
{
	if(textbox_.active() == false) {
		return;
	}

	switch(textbox_.mode) {
	case floating_textbox::TEXTBOX_SEARCH:
		do_search(textbox_.box->text());
		break;
	case floating_textbox::TEXTBOX_MESSAGE:
		do_speak(textbox_.box->text(),textbox_.check != NULL ? textbox_.check->checked() : false);
		break;
	case floating_textbox::TEXTBOX_COMMAND:
		do_command(textbox_.box->text());
		break;
	default:
		LOG_STREAM(err, display) << "unknown textbox mode\n";
	}

	close_textbox();
}

namespace {

bool tab_complete(const std::string& partword, const std::string& name)
{
	return name.size() >= partword.size() && std::equal(partword.begin(),partword.end(),name.begin(),chars_equal_insensitive);
}

}

void turn_info::tab_textbox()
{
	if(textbox_.active() == false) {
		return;
	}

	switch(textbox_.mode) {
	case floating_textbox::TEXTBOX_MESSAGE:
	{
		std::string text = textbox_.box->text();
		std::string semiword;
		bool beginning;

		const size_t last_space = text.rfind(" ");

		//if last character is a space return
		if(last_space == text.size() -1) {
			return;
		}

		if(last_space == std::string::npos) {
			beginning = true;
			semiword = text;
		}else{
			beginning = false;
			semiword.assign(text,last_space+1,text.size());
		}

		std::string guess;

		for(size_t n = 0; n != teams_.size(); ++n) {
			if(teams_[n].is_empty()) {
				continue;
			}
			const unit_map::const_iterator leader = team_leader(n+1,units_);
			if(leader != units_.end()) {
				const std::string& name = leader->second.description();
				if(tab_complete(semiword,name)) {
					guess = name;
				}
			}
		}

		if(guess.empty()) {
			const std::set<std::string>& observers = gui_.observers();
			for(std::set<std::string>::const_iterator i = observers.begin(); i != observers.end(); ++i) {
				if(tab_complete(semiword,*i)) {
					guess = *i;
					break;
				}
			}
		}

		if(guess.empty() == false) {
			std::string add = beginning ? ": " : " ";
			text.replace(last_space+1, semiword.size(), guess + add);
			textbox_.box->set_text(text);
		}
		break;
	}
	default:
		LOG_STREAM(err, display) << "unknown textbox mode\n";
	}
}

const unit_map& turn_info::visible_units() const
{
	if(viewing_team().uses_shroud() == false && viewing_team().uses_fog() == false) {
		LOG_STREAM(info, engine) << "all units are visible...\n";
		return units_;
	}

	visible_units_.clear();
	for(unit_map::const_iterator i = units_.begin(); i != units_.end(); ++i) {
		if(gui_.fogged(i->first.x,i->first.y) == false) {
			visible_units_.insert(*i);
		}
	}

	LOG_STREAM(info, engine) << "number of visible units: " << visible_units_.size() << "\n";

	return visible_units_;
}
