/* $Id: playlevel.cpp 8865 2005-11-17 04:18:36Z brunowolff $ */
/*
   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 "ai_interface.hpp"
#include "cursor.hpp"
#include "dialogs.hpp"
#include "events.hpp"
#include "filesystem.hpp"
#include "game_errors.hpp"
#include "gamestatus.hpp"
#include "gettext.hpp"
#include "game_events.hpp"
#include "halo.hpp"
#include "help.hpp"
#include "hotkeys.hpp"
#include "intro.hpp"
#include "log.hpp"
#include "mapgen.hpp"
#include "map_create.hpp"
#include "network.hpp"
#include "playlevel.hpp"
#include "playturn.hpp"
#include "preferences.hpp"
#include "random.hpp"
#include "replay.hpp"
#include "scoped_resource.hpp"
#include "sound.hpp"
#include "statistics.hpp"
#include "tooltips.hpp"
#include "unit_display.hpp"
#include "util.hpp"
#include "video.hpp"

#include <iostream>
#include <iterator>

#define LOG_NG LOG_STREAM(info, engine)

namespace {
	int placing_score(const config& side, const gamemap& map, const gamemap::location& pos)
	{
		int positions = 0, liked = 0;
		const std::string& terrain_liked = side["terrain_liked"];
		for(int i = pos.x-8; i != pos.x+8; ++i) {
			for(int j = pos.y-8; j != pos.y+8; ++j) {
				const gamemap::location pos(i,j);
				if(map.on_board(pos)) {
					++positions;
					if(std::count(terrain_liked.begin(),terrain_liked.end(),map[i][j])) {
						++liked;
					}
				}
			}
		}

		return (100*liked)/positions;
	}

	struct placing_info {
		int side, score;
		gamemap::location pos;
	};

	bool operator<(const placing_info& a, const placing_info& b) { return a.score > b.score; }
	bool operator==(const placing_info& a, const placing_info& b) { return a.score == b.score; }

	void place_sides_in_preferred_locations(gamemap& map, const config::child_list& sides)
	{
		std::vector<placing_info> placings;

		const int num_pos = map.num_valid_starting_positions();

		for(config::child_list::const_iterator s = sides.begin(); s != sides.end(); ++s) {
			const int side_num = s - sides.begin() + 1;
			for(int p = 1; p <= num_pos; ++p) {
				const gamemap::location& pos = map.starting_position(p);
				const int score = placing_score(**s,map,pos);
				placing_info obj;
				obj.side = side_num;
				obj.score = score;
				obj.pos = pos;
				placings.push_back(obj);
			}
		}

		std::sort(placings.begin(),placings.end());
		std::set<int> placed;
		std::set<gamemap::location> positions_taken;

		for(std::vector<placing_info>::const_iterator i = placings.begin(); i != placings.end() && placed.size() != sides.size(); ++i) {
			if(placed.count(i->side) == 0 && positions_taken.count(i->pos) == 0) {
				placed.insert(i->side);
				positions_taken.insert(i->pos);
				map.set_starting_position(i->side,i->pos);
				LOG_NG << "placing side " << i->side << " at " << i->pos << '\n';
			}
		}
	}

	bool is_observer(const std::vector<team>& teams)
	{
		for(std::vector<team>::const_iterator i = teams.begin(); i != teams.end(); ++i) {
			if(i->is_human() || i->is_persistent()) {
				return false;
			}
		}

		return true;
	}
}

LEVEL_RESULT play_level(const game_data& gameinfo, const config& game_config,
		config const* level, CVideo& video,
		game_state& state_of_game,
		const std::vector<config*>& story)
{
	//if the recorder has no event, adds an "game start" event to the
	//recorder, whose only goal is to initialize the RNG
	if(recorder.empty()) {
		recorder.add_start();
	} else {
		recorder.pre_replay();
	}

	const set_random_generator generator_setter(&recorder);

	//guarantee the cursor goes back to 'normal' at the end of the level
	const cursor::setter cursor_setter(cursor::NORMAL);

	const int ticks = SDL_GetTicks();
	LOG_NG << "in play_level()...\n";

	//if the entire scenario should be randomly generated
	if((*level)["scenario_generation"] != "") {
		LOG_NG << "randomly generating scenario...\n";
		const cursor::setter cursor_setter(cursor::WAIT);

		static config scenario;
		scenario = random_generate_scenario((*level)["scenario_generation"],level->child("generator"));
		level = &scenario;

		state_of_game.starting_pos = scenario;
	}

	std::string map_data = (*level)["map_data"];
	if(map_data == "" && (*level)["map"] != "") {
		map_data = read_map((*level)["map"]);
	}

	//if the map should be randomly generated
	if(map_data == "" && (*level)["map_generation"] != "") {
		const cursor::setter cursor_setter(cursor::WAIT);
		map_data = random_generate_map((*level)["map_generation"],level->child("generator"));

		//since we've had to generate the map, make sure that when we save the game,
		//it will not ask for the map to be generated again on reload
		static config new_level;
		new_level = *level;
		new_level.values["map_data"] = map_data;
		level = &new_level;

		state_of_game.starting_pos = new_level;
	}

	const config& lvl = *level;

	LOG_NG << "generated map " << (SDL_GetTicks() - ticks) << "\n";

	const statistics::scenario_context statistics_context(lvl["name"]);

	const int num_turns = atoi(lvl["turns"].c_str());
	gamestatus status(*level,num_turns);

	gamemap map(game_config,map_data);

	LOG_NG << "created objects... " << (SDL_GetTicks() - ticks) << "\n";

	CKey key;
	unit_map units;

	const verification_manager verify_manager(units);

	const int xp_modifier = atoi(lvl["experience_modifier"].c_str());
	const unit_type::experience_accelerator xp_mod(xp_modifier > 0 ? xp_modifier : 100);

	std::vector<team> teams;

	const teams_manager team_manager(teams);

	int first_human_team = -1;

	const config::child_list& unit_cfg = level->get_children("side");

	if(lvl["modify_placing"] == "true") {
		LOG_NG << "modifying placing...\n";
		place_sides_in_preferred_locations(map,unit_cfg);
	}

	LOG_NG << "initializing teams..." << unit_cfg.size() << "\n";;
	LOG_NG << (SDL_GetTicks() - ticks) << "\n";

	std::set<std::string> seen_save_ids;

	for(config::child_list::const_iterator ui = unit_cfg.begin(); ui != unit_cfg.end(); ++ui) {
		std::string save_id = (**ui)["save_id"];

		if(save_id.empty()) {
			save_id=(**ui)["description"];
		}

		if(save_id.empty()) {
			save_id="Unknown";
		}

		//make sure the 'save_id' is unique
		while(seen_save_ids.count(save_id)) {
			save_id += "_";
		}

		seen_save_ids.insert(save_id);

		player_info *player = NULL;

		if((**ui)["controller"] == "human" ||
		   (**ui)["controller"] == "network" ||
		   (**ui)["persistent"] == "1") {
			player = state_of_game.get_player(save_id);

			if(player == NULL && !save_id.empty()) {
				player = &state_of_game.players[save_id];
			}
		}

		LOG_NG << "initializing team...\n";

		const std::string& controller = (**ui)["controller"];
		if (controller == preferences::client_type() && (**ui)["description"] == preferences::login()) {
			first_human_team = ui - unit_cfg.begin();
		} else if(first_human_team == -1 && ((**ui)["controller"] == "human" || (**ui)["persistent"] == "1")) {
			first_human_team = ui - unit_cfg.begin();
		}

		std::string gold = (**ui)["gold"];
		if(gold.empty())
			gold = "100";

		LOG_NG << "found gold: '" << gold << "'\n";

		int ngold = lexical_cast_default<int>(gold);
		if(player != NULL && player->gold >= ngold) {
			ngold = player->gold;
		}

		LOG_NG << "set gold to '" << ngold << "'\n";

		teams.push_back(team(**ui,ngold));

		//update/fix the recall list for this side, by setting the
		//"side" of each unit in it to be the "side" of the player.
		int side = lexical_cast_default<int>((**ui)["side"], 1);
		if(player != NULL) {
			for(std::vector<unit>::iterator it = player->available_units.begin(); it != player->available_units.end(); ++it) {
				it->set_side(side);
			}
		}

		//if this team has no objectives, set its objectives to the
		//level-global "objectives"
		if(teams.back().objectives().empty())
			teams.back().set_objectives((*level)["objectives"]);

		//if this side tag describes the leader of the side
		if((**ui)["no_leader"] != "yes" && (**ui)["controller"] != "null") {
			unit new_unit(gameinfo, **ui);

			//search the recall list for leader units, and if there is
			//one, use it in place of the config-described unit
			if(player != NULL) {
				for(std::vector<unit>::iterator it = player->available_units.begin(); it != player->available_units.end(); ++it) {
					if(it->can_recruit()) {
						new_unit = *it;
						player->available_units.erase(it);
						break;
					}
				}
			}

			//see if the side specifies its location. Otherwise start it at the map-given
			//starting position
			gamemap::location start_pos(**ui);

			if(map.empty()) {
				throw game::load_game_failed("Map not found");
			}

			if((**ui)["x"].empty() && (**ui)["y"].empty()) {
				start_pos = map.starting_position(side);
			}

			if(!start_pos.valid() || !map.on_board(start_pos)) {
				throw game::load_game_failed(
					"Invalid starting position (" +
					lexical_cast<std::string>(start_pos.x+1) +
					"," + lexical_cast<std::string>(start_pos.y+1) +
					") for the leader of side " +
					lexical_cast<std::string>(side) + ".");
			}

			new_unit.new_turn();
			units.insert(std::pair<gamemap::location,unit>(
						map.starting_position(new_unit.side()), new_unit));
			LOG_NG << "initializing side '" << (**ui)["side"] << "' at "
			       << start_pos << '\n';
		}

		//if the game state specifies units that can be recruited for the player
		//then add them
		if(player != NULL && player->can_recruit.empty() == false) {
			std::copy(player->can_recruit.begin(),player->can_recruit.end(),
					std::inserter(teams.back().recruits(),teams.back().recruits().end()));
		}

		if(player != NULL) {
			player->can_recruit = teams.back().recruits();
		}

		//if there are additional starting units on this side
		const config::child_list& starting_units = (*ui)->get_children("unit");
		for(config::child_list::const_iterator su = starting_units.begin(); su != starting_units.end(); ++su) {
			unit new_unit(gameinfo,**su);

			new_unit.set_side(side);

			const std::string& x = (**su)["x"];
			const std::string& y = (**su)["y"];

			const gamemap::location loc(**su);
			if(x.empty() && y.empty()) {
				if(player) {
					player->available_units.push_back(new_unit);
					LOG_NG << "inserting unit on recall list for side " << new_unit.side() << "\n";
				} else {
					throw game::load_game_failed(
						"Attempt to create a unit on the recall list for side " +
						lexical_cast<std::string>(side) +
						", which does not have a recall list.");
				}
			} else if(!loc.valid() || !map.on_board(loc)) {
				throw game::load_game_failed(
					"Invalid starting position (" +
					lexical_cast<std::string>(loc.x+1) +
					"," + lexical_cast<std::string>(loc.y+1) +
					") for a unit on side " +
					lexical_cast<std::string>(side) + ".");
			} else {
				units.insert(std::pair<gamemap::location,unit>(loc,new_unit));
				LOG_NG << "inserting unit for side " << new_unit.side() << "\n";
			}
		}

	}

	// Add all recruitable units as encountered so that information
	// about them are displayed to the user in the help system.
	for (std::vector<team>::const_iterator help_team_it = teams.begin();
		 help_team_it != teams.end(); help_team_it++) {
		LOG_NG << "Adding help units for team '" << help_team_it->name() << "'\n";
		const std::set<std::string> &recruitable = help_team_it->recruits();
		std::set<std::string> &enc_units = preferences::encountered_units();
		LOG_NG << "Adding recruitable units: \n";
		for (std::set<std::string>::const_iterator it = recruitable.begin();
			 it != recruitable.end(); it++) {
			LOG_NG << *it << std::endl;
		}
		LOG_NG << "Added all recruitable units\n";
		std::copy(recruitable.begin(), recruitable.end(),
				  std::inserter(enc_units, enc_units.begin()));
	}

	// Add all units that exist at the start to the encountered units so
	// that information about them are displayed to the user in the help
	// system.
	for (unit_map::const_iterator help_unit_it = units.begin();
		 help_unit_it != units.end(); help_unit_it++) {
		const std::string name = help_unit_it->second.type().id();
		preferences::encountered_units().insert(name);
	}

	// Add all units that are recallable as encountred units.
	for(std::map<std::string, player_info>::iterator pi = state_of_game.players.begin(); pi!=state_of_game.players.end(); ++pi) {
		for(std::vector<unit>::iterator help_recall_it = pi->second.available_units.begin(); help_recall_it != pi->second.available_units.end(); help_recall_it++) {
			preferences::encountered_units().insert(help_recall_it->type().id());
		}
	}

	// Add all terrains on the map as encountered terrains.
	for (int map_x = 0; map_x < map.x(); map_x++) {
		for (int map_y = 0; map_y < map.y(); map_y++) {
			const gamemap::TERRAIN t = map.get_terrain(gamemap::location(map_x, map_y));
			std::string s;
			s += t;
			preferences::encountered_terrains().insert(s);
		}
	}
	LOG_NG << "initialized teams... " << (SDL_GetTicks() - ticks) << "\n";

	const config* theme_cfg = NULL;
	if(lvl["theme"] != "") {
		theme_cfg = game_config.find_child("theme","name",lvl["theme"]);
	}

	if(theme_cfg == NULL) {
		theme_cfg = game_config.find_child("theme","name",preferences::theme());
	}

	LOG_NG << "initializing display... " << (SDL_GetTicks() - ticks) << "\n";
	const config dummy_cfg;
	display gui(units,video,map,status,teams,theme_cfg != NULL ? *theme_cfg : dummy_cfg, game_config, *level);
	theme::set_known_themes(&game_config);

	LOG_NG << "done initializing display... " << (SDL_GetTicks() - ticks) << "\n";

	LOG_NG << "a... " << (SDL_GetTicks() - ticks) << "\n";

	if(first_human_team != -1) {
		gui.set_team(first_human_team);
	}

	const preferences::display_manager prefs_disp_manager(&gui);
	const tooltips::manager tooltips_manager(gui.video());

	LOG_NG << "b... " << (SDL_GetTicks() - ticks) << "\n";

	//this *needs* to be created before the show_intro and show_map_scene
	//as that functions use the manager state_of_game
	game_events::manager events_manager(*level,gui,map,units,teams,
	                                    state_of_game,status,gameinfo);

	if(recorder.skipping() == false) {
		for(std::vector<config*>::const_iterator story_i = story.begin(); story_i != story.end(); ++story_i) {

			show_intro(gui,**story_i, *level);
		}
	}

	//object that will make sure that labels are removed at the end of the scenario
	const font::floating_label_context labels_manager;
	const halo::manager halo_manager(gui);
	gui.labels().read(*level);
	LOG_NG << "c... " << (SDL_GetTicks() - ticks) << "\n";

	const std::string& music = lvl["music"];
	if(music != "") {
		sound::play_music(music);
	}

	LOG_NG << "d... " << (SDL_GetTicks() - ticks) << "\n";

	victory_conditions::set_victory_when_enemies_defeated(
						lvl["victory_when_enemies_defeated"] != "no");

	LOG_NG << "initializing events manager... " << (SDL_GetTicks() - ticks) << "\n";

	help::help_manager help_manager(&game_config, &gameinfo, &map);

	//find a list of 'items' (i.e. overlays) on the level, and add them
	const config::child_list& overlays = level->get_children("item");
	for(config::child_list::const_iterator overlay = overlays.begin(); overlay != overlays.end(); ++overlay) {
		gui.add_overlay(gamemap::location(**overlay),(**overlay)["image"], (**overlay)["halo"]);
	}

	int turn = 1, player_number = 0;

	turn_info::floating_textbox textbox_info;

	LOG_NG << "entering try... " << (SDL_GetTicks() - ticks) << "\n";

	replay_network_sender replay_sender(recorder);

	try {
		//if a team is specified whose turn it is, it means we're loading a game
		//instead of starting a fresh one
		const bool loading_game = lvl["playing_team"].empty() == false;

		//pre-start events must be executed before any GUI operation,
		//as those may cause the display to be refreshed.
		if(!loading_game) {
			update_locker lock_display(gui.video());
			game_events::fire("prestart");
		}

		gui.begin_game();
		gui.adjust_colours(0,0,0);

		LOG_NG << "scrolling... " << (SDL_GetTicks() - ticks) << "\n";
		if(first_human_team != -1) {
			LOG_NG << "b " << (SDL_GetTicks() - ticks) << "\n";
			gui.scroll_to_tile(map.starting_position(first_human_team + 1).x,
			                   map.starting_position(first_human_team + 1).y, display::WARP);
			LOG_NG << "c " << (SDL_GetTicks() - ticks) << "\n";
		}
		gui.scroll_to_tile(map.starting_position(1).x,map.starting_position(1).y,display::WARP);
		LOG_NG << "done scrolling... " << (SDL_GetTicks() - ticks) << "\n";

		bool replaying = (recorder.at_end() == false);

		int first_player = atoi(lvl["playing_team"].c_str());
		if(first_player < 0 || first_player >= int(teams.size())) {
			first_player = 0;
		}

		for(std::vector<team>::iterator t = teams.begin(); t != teams.end(); ++t) {
			clear_shroud(gui,status,map,gameinfo,units,teams,(t-teams.begin()));
		}

		std::deque<config> data_backlog;

		LOG_NG << "starting main loop\n" << (SDL_GetTicks() - ticks) << "\n";
		for(bool first_time = true; true; first_time = false, first_player = 0) {
			if(first_time) {
				const hotkey::basic_handler key_events_handler(&gui);

				LOG_NG << "first_time..." << (recorder.skipping() ? "skipping" : "no skip") << "\n";
				update_locker lock_display(gui.video(),recorder.skipping());
				events::raise_draw_event();
				gui.draw();
				for(std::vector<team>::iterator t = teams.begin(); t != teams.end(); ++t) {
					clear_shroud(gui,status,map,gameinfo,units,teams,(t-teams.begin()));
				}

				if(!loading_game) {
					game_events::fire("start");
					state_of_game.set_variable("turn_number", "1");
				}

				gui.recalculate_minimap();
			}
			player_number = 0;

			gui.new_turn();
			gui.invalidate_game_status();
			events::raise_draw_event();

			LOG_NG << "turn: " << turn++ << "\n";

			for(std::vector<team>::iterator team_it = teams.begin()+first_player; team_it != teams.end(); ++team_it) {
				log_scope("player turn");
				player_number = (team_it - teams.begin()) + 1;

				//if a side is dead, don't do their turn
				if(team_it->is_empty() || team_units(units,player_number) == 0) {
					continue;
				}

				if(is_observer(teams)) {
					gui.set_team(size_t(player_number-1));
				}

				std::stringstream player_number_str;
				player_number_str << player_number;
				state_of_game.set_variable("side_number",player_number_str.str());

				//fire side turn event only if real side change occurs not counting changes from void to a side
				if (team_it != teams.begin()+first_player || !first_time) {
					game_events::fire("side turn");
				}

				//we want to work out if units for this player should get healed, and the
				//player should get income now. healing/income happen if it's not the first
				//turn of processing, or if we are loading a game, and this is not the
				//player it started with.
				const bool turn_refresh = !first_time || loading_game && team_it != teams.begin()+first_player;

				if(turn_refresh) {
					for(unit_map::iterator i = units.begin(); i != units.end(); ++i) {
						if(i->second.side() == player_number) {
							i->second.new_turn();
						}
					}

					team_it->new_turn();

					//if the expense is less than the number of villages owned,
					//then we don't have to pay anything at all
					const int expense = team_upkeep(units,player_number) -
										team_it->villages().size();
					if(expense > 0) {
						team_it->spend_gold(expense);
					}

					calculate_healing(gui,status,map,units,player_number,teams);
				}

				team_it->set_time_of_day(int(status.turn()),status.get_time_of_day());

				gui.set_playing_team(size_t(player_number-1));

				clear_shroud(gui,status,map,gameinfo,units,teams,player_number-1);

				//scroll the map to the leader
				const unit_map::iterator leader = find_leader(units,player_number);

				if(leader != units.end() && !recorder.skipping()) {
					const hotkey::basic_handler key_events_handler(&gui);
					gui.scroll_to_tile(leader->first.x,leader->first.y);
				}

				if(replaying) {
					const hotkey::basic_handler key_events_handler(&gui);
					LOG_NG << "doing replay " << player_number << "\n";
					try {
						replaying = do_replay(gui,map,gameinfo,units,teams,
						                      player_number,status,state_of_game);
					} catch(replay::error&) {
						gui::show_dialog(gui,NULL,"",_("The file you have tried to load is corrupt"),gui::OK_ONLY);

						replaying = false;
					}
					LOG_NG << "result of replay: " << (replaying?"true":"false") << "\n";
				}

				if(!replaying && team_it->music().empty() == false) {
					LOG_NG << "playing music: '" << team_it->music() << "'\n";
					sound::play_music(team_it->music());
				}

//goto this label if the type of a team (human/ai/networked) has changed mid-turn
redo_turn:

				if(!replaying && team_it->is_human()) {
					LOG_NG << "is human...\n";

					try {
						play_turn(gameinfo,state_of_game,status,game_config,
						          *level, key, gui, map, teams, player_number,
						          units, textbox_info, replay_sender);
					} catch(end_turn_exception& end_turn) {
						if (end_turn.redo == player_number)
							goto redo_turn;
					}

					if(game_config::debug)
						display::clear_debug_highlights();

					LOG_NG << "human finished turn...\n";

				} else if(!replaying && team_it->is_ai()) {
					LOG_NG << "is ai...\n";
					gui.recalculate_minimap();

					const cursor::setter cursor_setter(cursor::WAIT);

					turn_info turn_data(gameinfo,state_of_game,status,
						                game_config,*level,key,gui,
						                map,teams,player_number,units,
								turn_info::BROWSE_AI,textbox_info,replay_sender);

					ai_interface::info ai_info(gui,map,gameinfo,units,teams,player_number,status,turn_data);
					util::scoped_ptr<ai_interface> ai_obj(create_ai(team_it->ai_algorithm(),ai_info));
					ai_obj->play_turn();
					recorder.end_turn();
					ai_obj->sync_network();

					gui.recalculate_minimap();
					clear_shroud(gui,status,map,gameinfo,units,teams,player_number-1);
					gui.invalidate_unit();
					gui.invalidate_game_status();
					gui.invalidate_all();
					gui.draw();
					SDL_Delay(500);
				} else if(!replaying && team_it->is_network()) {
					LOG_NG << "is networked...\n";

					turn_info turn_data(gameinfo,state_of_game,status,
					                    game_config,*level,key,gui,
							    map,teams,player_number,units,
							    turn_info::BROWSE_NETWORKED,
							    textbox_info,replay_sender);

					for(;;) {

						bool have_data = false;
						config cfg;

						network::connection from = network::null_connection;

						if(data_backlog.empty() == false) {
							have_data = true;
							cfg = data_backlog.front();
							data_backlog.pop_front();
						} else {
							from = network::receive_data(cfg);
							have_data = from != network::null_connection;
						}

						if(have_data) {
							const turn_info::PROCESS_DATA_RESULT result = turn_data.process_network_data(cfg,from,data_backlog);
							if(result == turn_info::PROCESS_RESTART_TURN) {
								goto redo_turn;
							} else if(result == turn_info::PROCESS_END_TURN) {
								break;
							}
						}

						turn_data.turn_slice();
						turn_data.send_data();
						gui.draw();
					}

					LOG_NG << "finished networked...\n";
				}

				for(unit_map::iterator uit = units.begin(); uit != units.end(); ++uit) {
					if(uit->second.side() == player_number)
						uit->second.end_turn();
				}

				//This implements "delayed map sharing." It's meant as an alternative to shared vision.
				if(team_it->copy_ally_shroud()) {
					gui.recalculate_minimap();
					gui.invalidate_all();
				}

				game_events::pump();

				check_victory(units,teams);
			}

			//time has run out
			if(!status.next_turn()) {

				if(non_interactive()) {
					std::cout << "time over (draw)\n";
				}

				LOG_NG << "firing time over event...\n";
				game_events::fire("time over");
				LOG_NG << "done firing time over event...\n";

				throw end_level_exception(DEFEAT);
			}

			std::stringstream event_stream;
			event_stream << status.turn();

			{
				LOG_NG << "turn event..." << (recorder.skipping() ? "skipping" : "no skip") << "\n";
				update_locker lock_display(gui.video(),recorder.skipping());
				const std::string turn_num = event_stream.str();
				state_of_game.set_variable("turn_number",turn_num);
				game_events::fire("turn " + turn_num);
				game_events::fire("new turn");
			}
		} //end for loop

	} catch(end_level_exception& end_level) {
		bool obs = is_observer(teams);
		if (end_level.result == DEFEAT || end_level.result == VICTORY) {
			// if we're a player, and the result is victory/defeat, then send a message to notify
			// the server of the reason for the game ending
			if (!obs) {
				config cfg;
				config& info = cfg.add_child("info");
				info["type"] = "termination";
				info["condition"] = "game over";
				network::send_data(cfg);
			} else {
				gui::show_dialog(gui, NULL, _("Game Over"),
				                 _("The game is over."), gui::OK_ONLY);
				return QUIT;
			}
		}

		if(end_level.result == QUIT) {
			return end_level.result;
		} else if(end_level.result == DEFEAT) {
			try {
				game_events::fire("defeat");
			} catch(end_level_exception&) {
			}

			if (!obs)
				return DEFEAT;
			else
				return QUIT;
		} else if (end_level.result == VICTORY || end_level.result == LEVEL_CONTINUE ||
		           end_level.result == LEVEL_CONTINUE_NO_SAVE) {
			try {
				game_events::fire("victory");
			} catch(end_level_exception&) {
			}

			if(state_of_game.scenario == (*level)["id"]) {
				state_of_game.scenario = (*level)["next_scenario"];
			}

			const bool has_next_scenario = !state_of_game.scenario.empty() &&
			                               state_of_game.scenario != "null";

			//add all the units that survived the scenario
			for(std::map<gamemap::location,unit>::iterator un = units.begin(); un != units.end(); ++un) {
				player_info *player=state_of_game.get_player(teams[un->second.side()-1].save_id());

				if(player) {
					un->second.new_turn();
					un->second.new_level();
					player->available_units.push_back(un->second);
				}
			}

			//'continue' is like a victory, except it doesn't announce victory,
			//and the player retains 100% of gold.
			if(end_level.result == LEVEL_CONTINUE || end_level.result == LEVEL_CONTINUE_NO_SAVE) {
				for(std::vector<team>::iterator i=teams.begin(); i!=teams.end(); ++i) {
					player_info *player=state_of_game.get_player(i->save_id());
					if(player) {
						player->gold = i->gold();
					}
				}

				return end_level.result == LEVEL_CONTINUE_NO_SAVE ? LEVEL_CONTINUE_NO_SAVE : VICTORY;
			}


			std::stringstream report;

			for(std::vector<team>::iterator i=teams.begin(); i!=teams.end(); ++i) {
				if (!i->is_persistent())
					continue;

				player_info *player=state_of_game.get_player(i->save_id());

				const int remaining_gold = i->gold();
				const int finishing_bonus_per_turn =
				             map.villages().size() * game_config::village_income +
				             game_config::base_income;
				const int turns_left = maximum<int>(0,status.number_of_turns() - status.turn());
				const int finishing_bonus = end_level.gold_bonus ?
				             (finishing_bonus_per_turn * turns_left) : 0;

				if(player) {
					player->gold = ((remaining_gold + finishing_bonus) * 80) / 100;

					if(state_of_game.players.size()>1) {
						if(i!=teams.begin()) {
							report << "\n";
						}

						report << font::BOLD_TEXT << i->save_id() << "\n";
					}

					report << _("Remaining gold: ")
					       << remaining_gold << "\n";
					if(end_level.gold_bonus) {
						report << _("Early finish bonus: ")
						       << finishing_bonus_per_turn
						       << " " << _("per turn") << "\n"
						       << _("Turns finished early: ")
						       << turns_left << "\n"
						       << _("Bonus: ")
						       << finishing_bonus << "\n"
						       << _("Gold: ")
						       << (remaining_gold+finishing_bonus);
					}

					// xgettext:no-c-format
					report << '\n' << _("80% of gold is retained for the next scenario") << '\n'
					       << _("Retained Gold: ") << player->gold;
				}
			}

			if (!obs)
				gui::show_dialog(gui, NULL, _("Victory"),
				                 _("You have emerged victorious!"), gui::OK_ONLY);

			if (state_of_game.players.size() > 0 && has_next_scenario ||
					state_of_game.campaign_type == "test")
				gui::show_dialog(gui, NULL, _("Scenario Report"), report.str(), gui::OK_ONLY);

			return VICTORY;
		}
	} //end catch
	catch(replay::error&) {
		gui::show_dialog(gui,NULL,"",_("The file you have tried to load is corrupt"),
		                 gui::OK_ONLY);
		return QUIT;
	}
	catch(network::error& e) {
		bool disconnect = false;
		if(e.socket) {
			e.disconnect();
			disconnect = true;
		}

		turn_info turn_data(gameinfo,state_of_game,status,
				game_config,*level,key,gui,
				map,teams,player_number,units,turn_info::BROWSE_NETWORKED,textbox_info,replay_sender);

		turn_data.save_game(_("A network disconnection has occured, and the game cannot continue. Do you want to save the game?"),gui::YES_NO);
		if(disconnect) {
			throw network::error();
		} else {
			return QUIT;
		}
	}

	return QUIT;
}
