<?php

chdir(dirname(__FILE__));

include_once("server_settings_invasion.php");
include_once("internal/metagame.php");
include_once("internal/map_info.php");
include_once("internal/log.php");

// available tracker implementations
include_once("trackers/vehicle_delivery_objective.php");
include_once("trackers/map_rotator_invasion.php");
include_once("trackers/map_rotator_adventure.php");
include_once("trackers/basic_command_handler.php");
include_once("trackers/peaceful_last_base.php");
include_once("trackers/item_delivery_objective.php");
include_once("trackers/unlock_manager.php");
include_once("trackers/overtime.php");
include_once("trackers/autosaver.php");

// --------------------------------------------
class GameMode extends MetaGame {
	protected $map_rotator = NULL;
	protected $unlock_manager = NULL;

	protected $factions = array();
	protected $map_info = NULL;
	protected $settings_xml = "";
	protected $item_delivery_organizer = NULL;
	public $save_name = ""; // map rotator needs to feed this back to game
	public $game_map_path = "";
	public $username = "Single player";
	public $fellow_capacity_factor = 1.05;
	public $fellow_ai_accuracy_factor = 0.94;
	public $enemy_capacity_factor = 1.0;
	public $enemy_ai_accuracy_factor = 0.94;
	public $xp_factor = 0.27;
	public $rp_factor = 1.0;


	// --------------------------------------------
	public function init() {
		parent::init();

		$this->map_info = new MapInfo();

		//$this->map_rotator = new MapRotatorInvasion($this);
		// trigger map change right now

		global $argv;
		if (test_parameter($argv, "single_player")) {
			// put map rotator in non-looping mode
			//$this->map_rotator->set_loop(false);

			// these days, single player invasion is in adventure mode
			$this->map_rotator = new MapRotatorAdventure($this);
		} else {

			$this->map_rotator = new MapRotatorInvasion($this);
		}

		$this->unlock_manager = new UnlockManager($this);

		if (test_parameter($argv, "continue")) {
			_log("* restoring old game");
			// if loading, load metagame
			$this->update_general_info();
			$this->load();
			// note, load handles initing map rotator at appropriate time

			$this->map_rotator->start(true);
			$this->unlock_manager->start();
		} else {
			// starting the invasion for the first time now, 
			// pick user settings from command line
			$settings_marker = 'settings=';
			$settings_found = false;
			foreach ($argv as $key => $value) {
				_log("parameter: " . $value);
				// strip quotes - parameters with spaces are delivered in double quotes
				$value = trim($value, '"');
				if (starts_with($value, $settings_marker)) {
					_log("found " . $settings_marker);
					$xml = substr($value, strlen($settings_marker));

					// add default encoding
					global $INCOMING_GAME_ENCODING;
					$xml = "<?xml version='1.0' encoding='" . $INCOMING_GAME_ENCODING . "'?>\n" . $xml;
					_log("xml part is: " . $xml);

					// get settings to store it for reading them again when loading and continuing
					$this->settings_xml = $xml;

					$doc = new DOMDocument();
					$doc->loadXML($xml);

					$settings = $doc->getElementsByTagName("settings")->item(0);
					$this->extract_settings($settings);
					$settings_found = true;
					break;
				}
			}
			if (!$settings_found) {
				_log("WARNING, settings not found");
			}

			_log(" * using savegame name: " . $this->save_name);
			_log(" * using username: " . $this->username);

			// we can use this to provide difficulty settings, user faction, etc
			_log(" * using fellow capacity: " . $this->fellow_capacity_factor);
			_log(" * using fellow ai accuracy: " . $this->fellow_ai_accuracy_factor);
			_log(" * using enemy capacity: " . $this->enemy_capacity_factor);
			_log(" * using enemy ai accuracy: " . $this->enemy_ai_accuracy_factor);
			_log(" * using xp factor: " . $this->xp_factor);
			_log(" * using rp factor: " . $this->rp_factor);

			// - init sets up map rotator according to settings
			// - settings are stored in metagame savegame data
			$this->map_rotator->init();
			$this->map_rotator->start();

			$this->unlock_manager->init();
			$this->unlock_manager->start();
		}

	}

	// --------------------------------------------
	public function uninit() {
		// save before parent uninit, parent uninitializes comms
		$this->save();

		parent::uninit();
	}

	// --------------------------------------------
	protected function update_general_info() {
		$general = $this->get_general_info();
		if ($general) {
			$this->game_map_path = $general->getAttribute("map");
		}
	}

	// --------------------------------------------
	public function get_unlock_weapon_list() {
		$list = array();
		// TODO: list here what we can give as an unlock for cargo truck delivery
		$list[] = new ResourceRef("l85a2.weapon", "weapon", "L85A2");
		$list[] = new ResourceRef("famasg1.weapon", "weapon", "FAMAS G1");
		$list[] = new ResourceRef("sg552.weapon", "weapon", "SG 552");
		$list[] = new ResourceRef("m79.weapon", "weapon", "M79");
		return $list;
	}

	// --------------------------------------------
	// MapRotator calls here when a battle is about to be started
	public function pre_begin_match() {
		// all trackers are cleared when match is about to begin
		parent::pre_begin_match();

		// map rotator needs to be added before match starts
		// - adventure mode needs to listen for player spawning which happens right when match starts
		// - usually it's good to add trackers only after match has started
		$this->add_tracker($this->map_rotator);
	}

	// --------------------------------------------
	// MapRotator calls here when a battle has started
	public function post_begin_match() {
		parent::post_begin_match();

		// query for basic match data -- we mostly need the savegame location
		{
			$this->update_general_info();
			$this->save();
		}

		// add tracker for time, unlock manager will drop unlocks some time after their activation
		$this->add_tracker($this->unlock_manager);
		{
			// re-feed unlocks into game now,
			// doesn't matter if we are loading a game and
			// the unlocks have been already stored and loaded
			// in the game, no harm done
			$this->unlock_manager->apply_unlocks();
		}

		// add a vehicle theft objective in the list of trackers
		// the objective works so that it'll try to start itself
		// (it might not be able, as there might not be a suitable vehicle for theft)
		$friendly_id = 0;
		foreach ($this->factions as $key => $value) {
			if ($key == $friendly_id) continue;

			_log("adding vehicle delivery objective");

			{
				$target = "cargo_truck.vehicle";
				$unlock_list = array();
				{
					$unlock_list[$target] = $this->get_unlock_weapon_list();
					// TODO: add any cargo truck reward items here
				}
				$unlocker = new ResourceUnlocker($this, $unlock_list);

				$this->add_tracker(new VehicleDeliveryObjective($this, $target, "Cargo truck", $key, $unlocker));
			}
		}

		$this->add_tracker(new AutoSaver($this));

		$this->add_tracker(new BasicCommandHandler($this));

		// we have multiple simultaneous enemy weapon delivery objectives handled by an organizer
		$this->item_delivery_organizer = new ItemDeliveryOrganizer($this);
	}

	// --------------------------------------------
	protected function update($time) {
		parent::update($time);
	}

	// --------------------------------------------
	protected function tracker_completed($tracker) {
		// for now, in Invasion, always, when a tracker completes
		// and is removed from the list of trackers
		// we create another one of similar type
		
		// just hack it now
		if (get_class($tracker) === "VehicleDeliveryObjective") {
			_log("re-adding vehicle delivery objective");
			$id = $tracker->get_enemy_id();
			$unlocker = $tracker->get_unlocker();
			$this->add_tracker(new VehicleDeliveryObjective($this, "cargo_truck.vehicle", "Cargo truck", $id, $unlocker));
		}

		// it would be possible to e.g. sequence objectives here if it matters
		// or pick one objective at a time randomly from a pool
	}

	// --------------------------------------------
	// map rotator is the one that actually defines which factions are in the game and which default values are used,
	// it will feed us the faction data
	public function set_factions($factions) {
		$this->factions = $factions;
	}


	// --------------------------------------------
	// map rotator lets us know some specific map related information
	// we need for handling position mapping 
	public function set_map_info($info) {
		$this->map_info = $info;
	}


	// --------------------------------------------
	// trackers may need to alter things about faction settings and be able to reset them back to defaults,
	// we'll provide the data from here
	public function get_factions() {
		return $this->factions;
	}

	// --------------------------------------------
	public function item_unlocked($resource) {
		$this->unlock_manager->add($resource);
	}

	// --------------------------------------------
	public function save() {
		// save metagame status now:
		_log("saving metagame through game");

		$doc = new DOMDocument();
		$command_root = $doc->createElement("command");
		$command_root->setAttribute("class", "save_data");

		$doc->appendChild($command_root);

		$root = $doc->createElement("saved_metagame");
		$command_root->appendChild($root);

		$this->map_rotator->save($doc, $root);

		$this->unlock_manager->save($doc, $root);

		// append user-settings in too
		if ($this->settings_xml != "") {
			$settings_doc = new DOMDocument();
			$settings_doc->loadXML($this->settings_xml);
			$node = $doc->importNode($settings_doc->firstChild, true);
			$root->appendChild($node);
		}

		// as additional data, save path to metagame log
		{
			global $_log_file;
			$info = $doc->createElement("extra_info");
			// not sure if utf8_decode is really necessary, 
			// 
			$info->setAttribute("log_path", utf8_decode($_log_file));
			$root->appendChild($info);
		}

		$xml = $doc->saveXML($command_root);

		// save through game
		$this->comms->send($xml);
	}

	// --------------------------------------------
	function extract_settings($settings) {
		// careful with user provided input here:
		// - the incoming data from game is not utf, but once it's loaded into DOMDocument, it is
		// - comparison will therefore fail unless proper conversions are made
		$this->save_name = utf_to_game($settings->getAttribute("savegame"));
		$this->username = utf_to_game($settings->getAttribute("username"));

		$this->fellow_capacity_factor = $settings->getAttribute("fellow_capacity");
		$this->fellow_ai_accuracy_factor = $settings->getAttribute("fellow_ai_accuracy");
		$this->enemy_capacity_factor = $settings->getAttribute("enemy_capacity");
		$this->enemy_ai_accuracy_factor = $settings->getAttribute("enemy_ai_accuracy");
		$this->xp_factor = $settings->getAttribute("xp_factor");
		$this->rp_factor = $settings->getAttribute("rp_factor");
	}

	// --------------------------------------------
	public function load() {
		// load metagame status now:
		_log("loading metagame");

		$query_doc = new DOMDocument();
		$command =
				"<command class='make_query' id='load_data_" . $this->comms->get_query_uid() . "'>\n" .
				"       <data class='saved_data' />\n" .
				"</command>";
		$query_doc->loadXML($command);

		$doc = $this->comms->query($query_doc);

		if ($doc && $doc->firstChild) {
			$root = $doc->firstChild;
			// TODO:
			// read user-settings too, have them around separately..
			if ($doc->getElementsByTagName("settings")->length > 0) {
				$e = $doc->getElementsByTagName("settings")->item(0);
				if ($e) {
					$settings_doc = new DOMDocument();
					$node = $settings_doc->importNode($e, true);
					$settings_doc->appendChild($node);
					$this->settings_xml = $settings_doc->saveXML();
					_log("settings are: " . $this->settings_xml);

					$this->extract_settings($e);
				}
			}

			$this->map_rotator->init();
			$this->map_rotator->load($doc, $root);

			$this->unlock_manager->init();
			$this->unlock_manager->load($doc, $root);

			_log("loaded");
		} else {
			_log("load failed");
			$this->map_rotator->init();
			$this->unlock_manager->init();
		}

	}

	// --------------------------------------------
	// --------------------------------------------
	// --------------------------------------------
	// --------------------------------------------
	// --------------------------------------------
	// --------------------------------------------
	// --------------------------------------------
	public function get_map_size() {
		return $this->map_info->size;
	}

	// --------------------------------------------
	public function is_map_flipped() {
		return $this->map_info->flipped;
	}

	// --------------------------------------------
	// pos is array of 3 elements, x,y,z
	public function get_region($pos) {
        $x = $pos[0];
        $z = $pos[2];

        $size = $this->get_map_size();
        $x = $x / $size;
        $z = $z / $size;

		// if not flipped (the old way)
		// x = 0 = east, x = lot = west
		// z = 0 = south, z = lot = north

		// flipped flips this
		// map4, map5, map6 
		// x = 0 = west, x = lot = east
		// z = 0 = north, z = lot = south

        if ($this->is_map_flipped()) {
			$x = 1.0 - $x;
            $z = 1.0 - $z;
        }

		$cell_x = min(floor(3 * $x), 2);
		$cell_z = min(floor(3 * $z), 2);

		$grid = array(
			array("southeast", "east", "northeast"),
			array("south", "center", "north"),
			array("southwest", "west", "northwest"));

		$text = $grid[$cell_x][$cell_z];

		_log("get_region, x=" . $x . " z=" . $z . " (" . $cell_x . "," . $cell_z . ") " . $text);

		return $text;
	}

	// --------------------------------------------------------
	function get_player_info($player_id) {
		$query_doc = new DOMDocument();
		$command =
				"<command class='make_query' id='player_query_" . $this->comms->get_query_uid() . "'>\n" .
				"       <data class='players' />\n" .
				"</command>";
		$query_doc->loadXML($command);

		$players_doc = $this->comms->query($query_doc);

		$player = NULL;
		if ($players_doc) {
				$players = $players_doc->getElementsByTagName("player");
				for ($i = 0; $i < $players->length; $i++) {
						$info = $players->item($i);
						if ($info->getAttribute("player_id") == $player_id) {
								$player = $info;
								break;
						}
				}
		}

		return $player;
	}

	// --------------------------------------------------------
	function query_local_player() {
		$query_doc = new DOMDocument();
		$command =
				"<command class='make_query' id='player_query_" . $this->comms->get_query_uid() . "'>\n" .
				"       <data class='players' />\n" .
				"</command>";
		$query_doc->loadXML($command);

		$players_doc = $this->comms->query($query_doc);

		$player = NULL;
		if ($players_doc) {
			$players = $players_doc->getElementsByTagName("player");
			for ($i = 0; $i < $players->length; $i++) {
				$info = $players->item($i);

				$name = utf_to_game($info->getAttribute("name"));

				_log("player: " . $name . ", target player is " . $this->username);
				if ($name == $this->username) {
					_log("ok");
					$player = $info;
					break;
				} else {
					_log("no match");
				}			
			}
		}
		return $player;
	}

	// --------------------------------------------------------
	function get_general_info() {
		$query_doc = new DOMDocument();
		$command =
				"<command class='make_query' id='general_query_1'>\n" .
				"       <data class='general' />\n" .
				"</command>";
		$query_doc->loadXML($command);

		$general_doc = $this->comms->query($query_doc);

		$info = NULL;
		if ($general_doc) {
				$info = $general_doc->getElementsByTagName("general")->item(0);
		}

		return $info;
	}

	// --------------------------------------------------------
	function get_character_info($character_id) {
		$query_doc = new DOMDocument();
		$command =
				"<command class='make_query' id='character_info_query_1'>\n" .
				"       <data class='character' id='" . $character_id . "' />\n" .
				"</command>";
		$query_doc->loadXML($command);

		$doc = $this->comms->query($query_doc);

		$character = NULL;
		if ($doc) {
				$character = $doc->getElementsByTagName("character")->item(0);
		}

		return $character;
	}

	// -------------------------------------------------------
	function send_private_message($player_id, $text, $pos = "") {
		_log(" * private message to " . $player_id . ": " . $text);
		$command =
				"<command class='chat'\n" .
				"  text='" . $text . "'\n" .
				"  player_id='" . $player_id . "'\n";
		if ($pos != "") {
			$command .=
				"  position='" . $pos . "'\n";
		}

		$command .= 
				">\n" .
				"</command>";

		$this->comms->send($command);
	}

	// -------------------------------------------------------
	function send_faction_message($faction_id, $text) {
		_log(" * faction message to " . $faction_id . ": " . $text);
		$command =
				"<command class='chat'\n" .
				"  text='" . $text . "'\n" .
				"  faction_id='" . $faction_id . "'>\n" .
				"</command>";

		$this->comms->send($command);
	}

	// -------------------------------------------------------
	function send_faction_message_key($faction_id, $key, $replacements = array()) {
		_log(" * faction message to " . $faction_id . ": " . $key);
		$command =
			"<command class='chat'\n" .
			"  key='" . $key . "'\n" .
			"  faction_id='" . $faction_id . "'>\n";

		foreach ($replacements as $k => $t) {
			$command = $command . 
				"<replacement key='" . $k . "' text='" . $t . "' />";
		}

		$command = $command . 
			"</command>";

		$this->comms->send($command);
	}

	// -------------------------------------------------------
	function send_private_message_key($player_id, $key, $replacements = array(), $pos = "") {
		_log(" * private message to " . $player_id . ": " . $key);
		$command =
				"<command class='chat'\n" .
				"  key='" . $key . "'\n" .
				"  faction_id='0'\n" .
				"  player_id='" . $player_id . "'\n";
		if ($pos != "") {
			$command .=
				"  position='" . $pos . "'\n";
		}
		$command .= 
				">\n";

		foreach ($replacements as $k => $t) {
			$command = $command . 
				"<replacement key='" . $k . "' text='" . $t . "' />";
		}

		$command .= 
				"</command>";

		$this->comms->send($command);
	}

	// -------------------------------------------------------
	function associate_hitboxes($armory_list, $instance_type, $instance_id, &$tracked_hitboxes) {
		$add_ids = array();
		$this->associate_hitboxes_ex($armory_list, $instance_type, $instance_id, $tracked_hitboxes, $add_ids);
	}

	function associate_hitboxes_ex($armory_list, $instance_type, $instance_id, &$tracked_hitboxes, &$add_ids) {
		if ($instance_id < 0) return;

		// check against already associated hitboxes
		// and determine which need to be added and which to removed

		// prepare to remove all hitboxes
		$remove_ids = $tracked_hitboxes;

		for ($i = 0; $i < $armory_list->length; ++$i) {
			$armory = $armory_list->item($i);
			$armory_id = $armory->getAttribute("id");

			$key = array_search($armory_id, $remove_ids);
			if ($key !== false) {
				// already tracked and still needed
				// remove from ids to remove
				unset($remove_ids[$key]);
			} else {
				// not yet tracked, needs to be added
				$add_ids[] = $armory_id;
			}
		}

		foreach ($remove_ids as $key => $id) {
			$command = "<command class='remove_hitbox_check' id='" . $id . "' instance_type='" . $instance_type . "' instance_id='" . $instance_id . "' />";
			$this->comms->send($command);
			unset($tracked_hitboxes[$key]);
		}

		foreach ($add_ids as $key => $id) {
			$command = "<command class='add_hitbox_check' id='" . $id . "' instance_type='" . $instance_type . "' instance_id='" . $instance_id . "' />";
			$this->comms->send($command);

			// remember we've set out to track this armory 
			$tracked_hitboxes[] = $id;
		}
	}

	// ----------------------------------------------------
	function clear_hitbox_associations($instance_type, $instance_id, &$tracked_hitboxes) {
		if ($instance_id < 0) return;

		// remove all tracked hitboxes
		foreach ($tracked_hitboxes as $key => $id) {
			$command = "<command class='remove_hitbox_check' id='" . $id . "' instance_type='" . $instance_type . "' instance_id='" . $instance_id . "' />";
			$this->comms->send($command);
		}
		$tracked_hitboxes = array();
	}

	// --------------------------------------------------------
	// $type_str1 = "weapon", "grenade", "carry_item" ("vehicle", "call") 
	// $type_str2 = "weapons", "grenades", "carry_items" ("vehicles", "calls") 
	function get_faction_deliverables($faction_id, $type_str1, $type_str2) {
		$list_query_doc = new DOMDocument();

		// query about vehicles
		$command = 
			"<command class='make_query' id='item_query_" . $this->comms->get_query_uid() . "_" . $faction_id . "'>\n" . 
			"	<data class='resources' faction_id='" . $faction_id . "' " . $type_str2 . "='1'/>\n" .
			"</command>";
		$list_query_doc->loadXML($command);

		// send the query command now
		$list_doc = $this->comms->query($list_query_doc);
		$list = NULL;
		if ($list_doc) {
			$list = $list_doc->getElementsByTagName($type_str1);
			_log("* " . $list->length . " " . $type_str1 . " resources found in faction " . $faction_id);
		}

		return $list;
	}

	// --------------------------------------------------------
	// position as array
	// returns base as DOM element
	function get_closest_base($position, &$out_distance) {
        $list_query_doc = new DOMDocument();

		// query about bases
        $command =
                "<command class='make_query' id='closest_base_query_" . $this->comms->get_query_uid() . "'>\n" .
                "       <data class='bases'/>\n" .
                "</command>";
        $list_query_doc->loadXML($command);

		$result = NULL;

        // send the query command now
        $list_doc = $this->comms->query($list_query_doc);
        if ($list_doc) {
            $base_list = $list_doc->getElementsByTagName("base");
			$closest_base_index = 0;
			$closest_distance = -1.0;
			for ($i = 0; $i < $base_list->length; $i++) {
				$base = $base_list->item($i);
				$base_position = parse_position($base->getAttribute("position"));
				$distance = get_position_distance($position, $base_position);
				if ($closest_distance < 0.0 || $distance < $closest_distance) {
					$closest_distance = $distance;
					$closest_base_index = $i;
				}
			}
			$out_distance = $closest_distance;
			$result = $base_list->item($closest_base_index);
        }

		return $result;
	}

	// --------------------------------------------------------
	// returns base as DOM element
	function get_starting_base($faction_id) {
        $list_query_doc = new DOMDocument();

		// query about bases
        $command =
                "<command class='make_query' id='bases_owned_query_" . $this->comms->get_query_uid() . "'>\n" .
                "       <data class='bases'/>\n" .
                "</command>";
        $list_query_doc->loadXML($command);

		$result = NULL;

        // send the query command now
        $list_doc = $this->comms->query($list_query_doc);
        if ($list_doc) {
            $base_list = $list_doc->getElementsByTagName("base");
			for ($i = 0; $i < $base_list->length; $i++) {
				$base = $base_list->item($i);
				$owner_id = $base->getAttribute("owner_id");
				if ($owner_id == $faction_id) {
					// assume the first base is the one we are looking for
					return $base;
				}
			}
        }

		return NULL;
	}
}


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

$metagame = new GameMode();

$metagame->init();
// execution blocks here at run until comms from server is lost
$metagame->run();
$metagame->uninit();

_log("ending execution");

?>