<?php

include_once("internal/tracker.php");
include_once("internal/map_info.php");
include_once("internal/log.php");

include_once("internal/announce_task.php");
include_once("internal/generic_call_task.php");
include_once("internal/time_announcer_task.php");

include_once("trackers/map_rotator.php");

include_once("trackers/peaceful_last_base.php");
include_once("trackers/overtime.php");

include_once("trackers/stage.php");
include_once("trackers/world.php");


// --------------------------------------------
// --------------------------------------------
// --------------------------------------------
// --------------------------------------------
// --------------------------------------------
// --------------------------------------------

class MapRotatorInvasion extends MapRotator {
	protected $stages = array();
	protected $loop = true;
	protected $world = NULL;
	protected $friendly_id = 0;

	// --------------------------------------------
	function set_loop($loop) {
		$this->loop = $loop;
	}

	// --------------------------------------------
	function init() {
		global $_common_stage_settings;
		global $_faction_configs;

		$this->world = new World($this->metagame);

		$_common_stage_settings->savegame = $this->metagame->save_name;
		$_common_stage_settings->username = $this->metagame->username;

		$_common_stage_settings->fellow_ai_accuracy = $this->metagame->fellow_ai_accuracy_factor;
		$_common_stage_settings->enemy_ai_accuracy  = $this->metagame->enemy_ai_accuracy_factor;

		$_common_stage_settings->fellow_capacity_factor = $this->metagame->fellow_capacity_factor;
		$_common_stage_settings->enemy_capacity_factor  = $this->metagame->enemy_capacity_factor;

		$_common_stage_settings->xp_multiplier = $this->metagame->xp_factor;
		$_common_stage_settings->rp_multiplier = $this->metagame->rp_factor;

		// handle anything that needs to happen after loading data
		// but before changing into and starting the current map

		// TODO:
		// - set up predefined stages, according to settings

		// --------------------------------------------

		// in this Invasion, we define all stages statically here
		// - the other choice would be to alter settings dynamically based on logic
		//   and outcome from previous matches

		// these days, we can input custom map_configs in the change_map command,
		// and it works fine with clients as long as we don't refer to additional
		// files the clients don't have

		// in invasion, as persistent profiles are used, we need to have all
		// weapons, throwables and items loaded in, as it's possible people carry
		// items from other maps that could potentially have items other maps don't have
		// - it is still possible to alter item commonness between maps, if wanted, here

		// TODO: we are using all vehicles for all maps, even if not all maps
		// have spawnpoints for all vehicles --> waste of resources

		// ------------------------------------------------------------------------------------------------
		{
			$stage = new Stage();
			$stage->map_info->name = "Keepsake Bay";
			$stage->map_info->path = "media/packages/vanilla/maps/map2";

			$stage->trackers[] = "PeacefulLastBase";
			$stage->max_soldiers = 12 * 7;

			$stage->soldier_capacity_variance = 0.3;

			{
				$f = new Faction($_faction_configs[0], create_fellow_commander_ai_command(0));
				$f->over_capacity = 0;
				$f->capacity_offset = 5; // radar truck
				$f->capacity_multiplier = 1.0;
				$f->bases = 1;
				$stage->factions[] = $f;
			}
			{
				$f = new Faction($_faction_configs[1], create_commander_ai_command(1));
				$f->capacity_offset = 5; // radar truck
				$stage->factions[] = $f; 
			}

			// metadata
			$stage->primary_objective = "capture";

			$this->stages[] = $stage;
		}

		// ------------------------------------------------------------------------------------------------
		{
			$stage = new Stage();
			$stage->map_info->name = "Fridge Valley";
			$stage->map_info->flipped = true;
			$stage->map_info->path = "media/packages/vanilla.winter/maps/map4";

			$stage->trackers[] = "PeacefulLastBase";
			$stage->max_soldiers = 17 * 6;

			{
				$f = new Faction($_faction_configs[0], create_fellow_commander_ai_command(0));
				$f->over_capacity = 0;
				$f->bases = 1;
				$stage->factions[] = $f;
			}
			{
				$f = new Faction($_faction_configs[1], create_commander_ai_command(1));
				$stage->factions[] = $f; 
			}

			// metadata
			$stage->primary_objective = "capture";

			$this->stages[] = $stage;
		}

		// ------------------------------------------------------------------------------------------------
		{
			$stage = new Stage();
			$stage->map_info->name = "Old Fort Creek";
			$stage->map_info->path = "media/packages/vanilla/maps/map3";

			$stage->trackers[] = "PeacefulLastBase";
			$stage->max_soldiers = 14 * 8;

			$stage->soldier_capacity_variance = 0.31;

			{
				$f = new Faction($_faction_configs[0], create_fellow_commander_ai_command(0));
				$f->capacity_offset = 5; // radar truck
				$f->over_capacity = 0;
				$f->bases = 1;
				$stage->factions[] = $f;
			}
			{
				$f = new Faction($_faction_configs[2], create_commander_ai_command(1));
				$f->capacity_offset = 5; // radar truck
				$stage->factions[] = $f; 
			}

			// metadata
			$stage->primary_objective = "capture";

			$this->stages[] = $stage;
		}

		// ------------------------------------------------------------------------------------------------
		{
			$stage = new Stage();
			$stage->map_info->name = "Power Junction";
			$stage->map_info->flipped = true;
			$stage->map_info->path = "media/packages/vanilla/maps/map7";

			$stage->trackers[] = "Overtime";

			$stage->max_soldiers = 30 * 3;
			$stage->defense_win_time = 400;

			{
				$f = new Faction($_faction_configs[0], create_fellow_commander_ai_command(0, 0.1, 0.1));
				$stage->factions[] = $f;
			}
			{
				$f = new Faction($_faction_configs[2], create_commander_ai_command(2, 0.1, 0.1));
				$stage->factions[] = $f;
			}
			{
				$f = new Faction($_faction_configs[1], create_commander_ai_command(1, 0.1, 0.1));
				$stage->factions[] = $f;
			}
			{
				// neutral
				$f = new Faction($_faction_configs[3], create_commander_ai_command(3));
				$f->capacity_multiplier = 0.0;
				$stage->factions[] = $f;
			}

			// metadata
			$stage->primary_objective = "koth";
			$stage->koth_target_base = "Power Plant";

			$this->stages[] = $stage;
		} 

		// ------------------------------------------------------------------------------------------------
		{
			$stage = new Stage();
			$stage->map_info->name = "Moorland Trenches";
			$stage->map_info->path = "media/packages/vanilla/maps/map1";

			$stage->max_soldiers = 18 * 14;

			$stage->soldier_capacity_variance = 0.4; // map1 is a big map; using high variance helps keep the attack group not growing super large when more bases are captured

			$stage->trackers[] = "PeacefulLastBase";

			{
				$f = new Faction($_faction_configs[0], create_fellow_commander_ai_command(0));
				$f->over_capacity = 6;
				$f->bases = 1;
				$f->capacity_multiplier = 0.8; // by default, green has 120%, this will make it a bit less so in map1
				$stage->factions[] = $f;
			}
			{
				$f = new Faction($_faction_configs[1], create_commander_ai_command(1));
				$stage->factions[] = $f;
			}
			{
				$f = new Faction($_faction_configs[2], create_commander_ai_command(2));
				$stage->factions[] = $f;
			}

			// metadata
			$stage->primary_objective = "capture";

			$this->stages[] = $stage;
		}

		// ------------------------------------------------------------------------------------------------
		{
			$stage = new Stage();
			$stage->map_info->name = "Bootleg Islands";
			$stage->map_info->flipped = true;
			$stage->map_info->path = "media/packages/vanilla/maps/map5";

			$stage->max_soldiers = 11 * 10;

			$stage->trackers[] = "PeacefulLastBase";

			{
				$f = new Faction($_faction_configs[0], create_fellow_commander_ai_command(0));
				$f->over_capacity = 20;
				$f->bases = 1;
				// seems to be quite hard at times, try to favor greens a bit
				$f->capacity_multiplier = 1.1;
				$stage->factions[] = $f;
			}
			{
				$f = new Faction($_faction_configs[2], create_commander_ai_command(2));
				$stage->factions[] = $f;
			}
			{
				$f = new Faction($_faction_configs[1], create_commander_ai_command(1));
				$stage->factions[] = $f;
			}

			// metadata
			$stage->primary_objective = "capture";

			$this->stages[] = $stage;
		} 

		// ------------------------------------------------------------------------------------------------
		{
			$stage = new Stage();
			$stage->map_info->name = "Rattlesnake Crescent";
			$stage->map_info->flipped = true;
			$stage->map_info->path = "media/packages/vanilla/maps/map6";

			$stage->max_soldiers = 17 * 7;

			$stage->trackers[] = "PeacefulLastBase";

			{
				// greens will push a bit harder here
				$f = new Faction($_faction_configs[0], create_fellow_commander_ai_command(0, 0.3, 0.12));
				$f->over_capacity = 0;
				$f->capacity_offset = 10; // radar tower
				$f->bases = 1;
				$stage->factions[] = $f;
			}
			{
				$f = new Faction($_faction_configs[2], create_commander_ai_command(1));
				$f->over_capacity = 70;
        $f->capacity_offset = 20; // radar tower
				$stage->factions[] = $f;
			}

			/*
			// TODO:
			// green doesn't have comms truck
			$stage->extra_commands[] =
				"<command class='faction_resources' faction_id='0'>\n" .
				"	<vehicle key='radar_truck.vehicle' enabled='0'/>\n" .
				"</command>";
			*/

			// metadata
			$stage->primary_objective = "capture";
			$stage->radio_objective_present = false;

			$this->stages[] = $stage;
		} 

		// ------------------------------------------------------------------------------------------------
		{
			$stage = new Stage();
			$stage->map_info->name = "Vigil Island";
			$stage->map_info->flipped = true;
			$stage->map_info->path = "media/packages/vanilla/maps/map8";

			$stage->trackers[] = "Overtime";

			$stage->max_soldiers = 33 * 3;
			$stage->soldier_capacity_model = "constant";
			$stage->defense_win_time = 600.0;

			{
				$f = new Faction($_faction_configs[0], create_fellow_commander_ai_command(0, 0.2, 0.1));
				$stage->factions[] = $f;
			}
			{
				$f = new Faction($_faction_configs[1], create_commander_ai_command(1, 0.2, 0.1));
				$f->capacity_multiplier = 1.4;
				$f->over_capacity = 50;
				$stage->factions[] = $f;
			}
			{
				// neutral
				$f = new Faction($_faction_configs[3], create_commander_ai_command(2));
				$f->capacity_multiplier = 0.0;
				$stage->factions[] = $f;
			}

			// metadata
			$stage->primary_objective = "koth";
			$stage->koth_target_base = "Airfield";
			$stage->radio_objective_present = false;

			$this->stages[] = $stage;
		}
	}

	// --------------------------------------------
	protected function handle_faction_lost($doc) {
		// if green lost a battle, start over

		$faction_id = -1;

		$elements = $doc->getElementsByTagName("lose_condition");
		if ($elements->length >= 1) {
			$lose_condition = $elements->item(0);
			$faction_id = $lose_condition->getAttribute("faction_id");
		} else {
			_log("couldn't find lose_condition tag");
		}

		if ($faction_id == 0) {
			// greens lost, restart the map
			$this->wait_and_start(10, false);		
		}
	}

	// -------------------------------------------
	protected function wait_and_start($time = 30, $say_countdown = true) {
		$previous_stage_index = $this->current_stage_index;

		// share some information with the server (and thus clients)
		$index = $this->get_next_stage_index();
		$map_name = $this->get_map_name($index);

		_log("previous stage index " . $previous_stage_index . ", next stage index " . $index);
		if ($previous_stage_index != $index) {
			// show appropriate transport arrows in map now, if map is about to change
			$this->world->set_advance($previous_stage_index, $index);

			// announce map advance in dialog
			$this->announce_map_advance($index);
			
		} else {
			// same map
		}

		// wait a while, and let server announce a few things
		$this->metagame->task_sequencer->add(new TimeAnnouncerTask($this->metagame, $time, $say_countdown));

		if ($previous_stage_index != $index) {
			// start new map
			$this->metagame->task_sequencer->add(new GenericCallTask($this, "start_map", array($index))); // --> translates to $this->start_map($index) as delayed call
		} else {
			// restart same map
			$this->metagame->task_sequencer->add(new GenericCallTask($this, "restart_map", array($index)));
		}
	}

	// --------------------------------------------
	protected function handle_match_end($doc) {
		// prepare for lost battle, grey won
		$faction_id = 1;

		$elements = $doc->getElementsByTagName("win_condition");
		if ($elements->length >= 1) {
			$win_condition = $elements->item(0);
			$faction_id = $win_condition->getAttribute("faction_id");
		} else {
			_log("couldn't find win_condition tag");
			// just restart the map
		}

		_log("faction " . $faction_id . " won");

		if ($faction_id == 0) {
			$campaign_completed = false;
			// green won, advance to next map
			_log("advance");

			// not real data to add about it, is there a "set" in php?
			$this->stages_completed[$this->current_stage_index] = true;

			// now, update world view, declare the area ours
			$this->world->refresh($this->stages, $this->stages_completed);

			// clear offender visuals
			$this->world->clear_offender_visuals();

			$campaign_completed = count($this->stages_completed) == $this->get_stage_count();
			if ($campaign_completed) {
				_log("campaign completed");
				if ($this->loop) {
					_log("looping -> resetting");
					$this->stages_completed = array();
					$campaign_completed = false;
				}
			}

			if (!$campaign_completed) {
				$this->ready_to_advance();

			} else {
				// campaign completed and not looping, game over, let user handle from here on
			}

		} else {
			// no need to do anything here, we should've received faction_lost event as well 
			// and restarted the map because of that
		}
	}

	// --------------------------------------------
	protected function ready_to_advance() {
		$this->wait_and_start();		
	}

	// --------------------------------------------
	protected function get_stage_count() {
		return count($this->stages);
	}

	// --------------------------------------------
	protected function get_map_name($index) {
		return $this->stages[$index]->map_info->name;
	}

	// --------------------------------------------
	protected function get_change_map_command($index) {
		return $this->stages[$index]->get_change_map_command();
	}

	// --------------------------------------------
	protected function get_start_game_command($index) {
		return $this->stages[$index]->get_start_game_command($this->get_completion_percentage());
	}

	// --------------------------------------------
	protected function get_next_stage_index() {
		// in invasion, do it linearly
		return count($this->stages_completed);
	}

	// --------------------------------------------
	protected function get_completion_percentage() {
		return (double)(count($this->stages_completed)) / count($this->stages);
	}

	// --------------------------------------------------------
	protected function is_stage_completed($index) {
		return array_key_exists($index, $this->stages_completed);
	}

	// --------------------------------------------
	// --------------------------------------------
	// DIALOGUE
	// --------------------------------------------
	// --------------------------------------------
	// --------------------------------------------
	protected function announce_map_start() {
		// happens when a map has changed and the match starts

		// two main cases,
		// a hostile map
		// a completed map

		if (!$this->is_stage_completed($this->current_stage_index)) {
			// hostile

			// two more cases:
			// a new map, we've never been here before, practically means we just "captured" our first base when entering the map
			// a map, where we have been before

			// we don't have this info around, at the moment

			// so just map the case of owning just one base, covers most of situations and isn't entirely
			// wrong even in the case when player retreats and comes back
			$stage = $this->stages[$this->current_stage_index];

			// assuming 0th faction is us
			// TODO: get starting base
			$base_name = "one of the bases";
			{
				$base = $this->metagame->get_starting_base(0);
				if ($base) {
					$base_name = $base->getAttribute("name");
				}
			}
			
			$a = array("%map_name" => $stage->map_info->name, 
					   "%base_name" => $base_name, 
					   "%number_of_bases" => count($stage->factions[0]->owned_bases));

			if (count($stage->factions[0]->owned_bases) <= 1) {
				// first time here

				// commander says something
				$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 2.0, 0, "map start with 1 base, part 1", $a));
				$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 4.0, 0, "map start with 1 base, part 2", $a));
				$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 5.0, 0, "map start with 1 base, part 3", $a));

				// capture map?
				if ($stage->is_capture()) {
					$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 6.0, 0, "map start with 1 base, capture", $a));

				} else if ($stage->is_koth()) {
					// koth map?
					$a["%target_base_name"] = $stage->koth_target_base;
					$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 6.0, 0, "map start with 1 base, koth", $a));
				}

			} else {
				// doing a resume

				// commander says something
				$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 5.0, 0, "map start with more bases, part 1", $a));
				$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 5.0, 0, "map start with more bases, part 2", $a));
			}

			// side objectives?
			if ($stage->has_side_objectives()) {
				$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 5.0, 0, "side objectives", $a));
			}

			// loot / cargo trucks?
			if ($stage->has_loot_objective()) {
				$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 5.0, 0, "loot objective", $a));
			}

			// radio tower / truck?
			if ($stage->has_radio_objective()) {
				$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 5.0, 0, "radio tower or truck objective", $a));
			}

			$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 0.0, 0, "map start, ending", $a));

		} else {
			// completed, assuming friendly for now
			$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 0.0, 0, "map start with completed map", $a));
		}
	}

	// --------------------------------------------
	protected function announce_map_advance($index) {
		// happens when a map is about to change, i.e. in adventure mode the player hits the hitbox
		// TODO: would it actually be better to have a second bigger hitbox?
		// - this chat could happen when player enters the bigger hitbox
		// - and actual map advance moment wouldn't do anymore chatting, being more immediate

		$stage = $this->stages[$index];

		$enemy_names = array();
		// fill enemy names
		foreach ($stage->factions as $key => $f) {
			if ($f->config->index != 0 &&
				// skip neutral
				!$f->is_neutral()) {

				$enemy_names[] = $f->config->name;
			}
		}
		$region_name = $this->get_map_name($index);

		// two main cases:
		// - the map has enemies
		// - the map has friendlies

		// commander says something
		if (count($enemy_names) > 0) {
			// if there are enemies, report it
			$a = array("%map_name" => $region_name, "%faction_name" => $enemy_names[0]);
			$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 3.0, 0, "map advance, held by enemy", $a));

			if (count($enemy_names) > 1) {
				$a = array("%faction_name" => $enemy_names[1]);
				$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 2.0, 0, "map advance, another enemy present", $a));
			}

			if (count($stage->factions[0]->owned_bases) <= 1) {
				// we haven't been there yet, basically
				$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 2.0, 0, "map advance, new map"));

			} else {
				// we already own some bases
				$a = array("%number_of_bases" => count($stage->factions[0]->owned_bases));
				$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 1.0, 0, "map advance, been there before", $a));
			}

		} else {
			// just friendlies in this map
			$a = array("%map_name" => $region_name, "%faction_name" => $stage->factions[0]->config->name);
			$this->metagame->task_sequencer->add(new AnnounceTask($this->metagame, 2.0, 0, "map advance, friendly map", $a));
		}
	}

	// --------------------------------------------
    public function start_map($index, $begin_only = false) {
		// in invasion, store the faction settings in gamemode
		// so that we can juggle with them when the match is on
		// and reset them back to defaults when needed
		$this->metagame->set_factions($this->stages[$index]->factions);

		//parent::start_map($index, $begin_only);
		// extra commands need to happen before the correct game is started
		// so that faction resource configs are in before mass spawning

		$this->current_stage_index = $index;

		if (!$begin_only) {
			// change map 
			$change_map_command = $this->get_change_map_command($index);
			$this->metagame->comms->send($change_map_command);

			// as we change the map, we also need to set the faction resources
			// but only then -- otherwise we continue with whatever
			// was set in the match already

			{
				$commands = $this->stages[$index]->get_extra_commands($this->get_completion_percentage(), $this->friendly_id);
				foreach ($commands as $key => $value) {
					$this->metagame->comms->send($value);
				}
			}

			// stage extra commands will only have default stage settings
			// - also feed in the unlocks now 
		}

		$this->metagame->pre_begin_match();

		if (!$begin_only) {
			// start game 
			$start_game_command = $this->get_start_game_command($index);
			$this->metagame->comms->send($start_game_command);

			// wait here 
			// - make a query, the game will serve it as soon as it can (->marks that resources have been changed and the match has been started)
			// only then announce map start
			$query_doc = new DOMDocument();
			$command = "<command class='make_query' id='sync_query_1'/>\n";
			$query_doc->loadXML($command);

			$doc = $this->metagame->comms->query($query_doc);
		}
		
		// initialize world view in game
		$this->world->init($this->stages, $this->stages_completed, $index);

		if (!$begin_only) {
			// --> now announce map start
			$this->announce_map_start();
		}

		$this->metagame->post_begin_match();

		// create stage specific trackers:
		foreach ($this->stages[$index]->trackers as $key => $value) {
			$custom_class = $value;
			$this->metagame->add_tracker(new $custom_class($this->metagame));
		}
	}

	// --------------------------------------------
    public function restart_map() {
		$index = $this->current_stage_index;

		$this->metagame->set_factions($this->stages[$index]->factions);

		$this->metagame->pre_begin_match();

		// start game 
		$start_game_command = $this->get_start_game_command($index);
		$this->metagame->comms->send($start_game_command);

		// initialize world view in game
		$this->world->init($this->stages, $this->stages_completed, $index);

		//$this->announce_map_start();

		$this->metagame->post_begin_match();

		// create stage specific trackers:
		foreach ($this->stages[$index]->trackers as $key => $value) {
			$custom_class = $value;
			$this->metagame->add_tracker(new $custom_class($this->metagame));
		}
	}

	// --------------------------------------------
	public function save(&$doc, &$root) {
		$parent = $root;
		$subroot = $doc->createElement("map_rotator");
		foreach ($this->stages_completed as $key => $value) {
			$e = $doc->createElement("map_completed");
			$e->setAttribute("index", $key);
			$subroot->appendChild($e);
		}
		$parent->appendChild($subroot);
	}

	// --------------------------------------------
	public function load(&$doc, &$root) {
		_log("loading map rotator");
		$this->stages_completed = array();

		$subroot = $root->getElementsByTagName("map_rotator")->item(0);
		if ($subroot) {
			_log("map_rotator found");
			$list = $subroot->getElementsByTagName("map_completed");
			for ($i = 0; $i < $list->length; $i++) {
				$e = $list->item($i);
				$index = $e->getAttribute("index");
				_log("index=" . $index);
				$this->stages_completed[$index] = true;
			}
		} else {
			_log("map_rotator not found?");
		}
	}

}


?>