<?php

include_once("internal/objective.php");
include_once("internal/admin.php");
include_once("internal/log.php");

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

class VehicleDeliveryObjective extends Objective {
	protected $vehicle_key = "cargo_truck.vehicle";
	protected $vehicle_name = "Cargo truck";
	protected $enemy_name = "Enemy";
	protected $tracked_vehicle_id = -1;
	protected $vehicle_info = NULL;
	protected $protector_times = array();
	protected $latest_intel_text = "";
	protected $delivery_done = false;
	protected $tracked_hitboxes = array();
	protected $friendly_id = 0;
	protected $enemy_id = 1;
	protected $wait_for_new_intel_timer = 0;
	protected $total_reward = 800.0;
	protected $unlocker = NULL;

	protected $WAIT_RETRY_TIME = 60.0;
	protected $MIN_WAIT_INTEL_TIME = 120.0;
	protected $MAX_WAIT_INTEL_TIME = 300.0;
	protected $REWARD_TRACKING_TIME = 10.0;

	// delay starting vehicle objective
	protected $wait_for_try_again_timer = 30.0;

	// ----------------------------------------------------
	function __construct($metagame, $vehicle_key, $vehicle_name, $enemy_id, $unlocker = NULL) {
		parent::__construct($metagame);
		$this->vehicle_key = $vehicle_key;
		$this->vehicle_name = $vehicle_name;
		$this->enemy_id = $enemy_id;
		$this->unlocker = $unlocker;
	}

	// ----------------------------------------------------
	// for convenience, we may have to re-create the tracker once it completes, so it's fairly convenient
	// to be able to ask for the enemy_id here
	function get_enemy_id() {
		return $this->enemy_id;
	}
	function get_unlocker() {
		return $this->unlocker;
	}

	// ----------------------------------------------------
	function start() {
		Objective::start();

		$handled = false;

		if ($this->wait_for_try_again_timer <= 0) {
			// vehicle_list is DOMNodeList
			{
				$vehicle_list = $this->get_vehicle_list($this->enemy_id, $this->vehicle_key);
				if ($vehicle_list && $vehicle_list->length > 0) {

					// check that we have armories available to deliver the cargo truck
					$armory_list = $this->get_armory_hitbox_list();
					if ($armory_list && $armory_list->length > 0) {

						// with cargo truck, the vehicle has been set to spawn once per faction, just pick the first vehicle for tracking
						$chosen_index = 0;
						$vehicle_list_item = $vehicle_list->item($chosen_index);	

						// get additional info about the vehicle, we need to know its health
						$id = $vehicle_list_item->getAttribute("id");
						$vehicle_info = $this->get_vehicle_info($id);

						if ($vehicle_info) {
							// check health, don't make a broken vehicle an objective
							$health = $vehicle_info->getAttribute("health");
							if ($health > 0) {
								// we'll store vehicle id to track events coming in; the events will have the id too for matching
								$this->tracked_vehicle_id = $id;
								_log("tracking vehicle id " . $this->tracked_vehicle_id);

								// get enemy name
								$enemy_info = $this->get_faction_info($this->enemy_id);
								if ($enemy_info) {
									$this->enemy_name = $enemy_info->getAttribute("name");
								}

								// clear any previous reward tracking
								$this->protector_times = array();

								$this->reward_tracking_timer = 0;

								$this->latest_intel_text = $this->get_intel_text($vehicle_info);

								// associate with hitboxes to get notification from game when the vehicle touches the hitbox
								$this->associate_hitboxes($armory_list, $this->tracked_vehicle_id);

								// send a notification about the objective to players
								$this->instruct_objective();

								$handled = true;


							} else {
								_log("vehicle has 0 health, can't start objective");
							}
						}
					}
				}
			}

			if (!$handled) {
				$this->wait_for_try_again_timer = $this->WAIT_RETRY_TIME;
			}
		}

		if (!$handled) {
			// no vehicles where to choose from, cancel objective
			//$this->end();

			// we'll keep the objective active, in not started and not ended state
			// which will make the system try starting it again

			$this->reset();
		}
	}

    // ----------------------------------------------------
	public function update($time) {
		if ($this->wait_for_try_again_timer > 0) {
			$this->wait_for_try_again_timer -= $time;
		}

		if ($this->has_started() && !$this->delivery_done && $this->tracked_vehicle_id >= 0) {
			_log(" * delivery, intel timer: " . $this->wait_for_new_intel_timer);
			$this->wait_for_new_intel_timer -= $time;
			if ($this->wait_for_new_intel_timer < 0.0) {
				// resets the timer
				$vehicle_info = $this->get_vehicle_info($this->tracked_vehicle_id);
				$this->latest_intel_text = $this->get_intel_text($vehicle_info);
				$this->instruct_objective();
			}

			// track rewarding:
			// - periodically check our troops near the target vehicle, track how long have they been near it
			$this->reward_tracking_timer -= $time;
			if ($this->reward_tracking_timer < 0.0) {
				// - query friendly soldiers near the vehicle
				$characters = $this->get_characters_near_vehicle($this->tracked_vehicle_id);
				if ($characters) {
					// check the character we already had at tracking, but which no longer was returned:
					// start reducing their time 

					// copy
					$previous = $this->protector_times;
					for ($i = 0; $i < $characters->length; $i++) {
						$info = $characters->item($i);
						$id = $info->getAttribute("id");
						if (array_key_exists($id, $previous)) {
							// erase from previous
							unset($previous[$id]);
						}
					}
					// this leaves us with the previous ids whose characters are no longer near the vehicle
					foreach ($previous as $id => $value) {
						$this->protector_times[$id] -= $this->REWARD_TRACKING_TIME;
						if ($this->protector_times[$id] <= 0.0) {
							// haven't been near the vehicle for a while, remove from protectors, guy's probably dead
							_log(" * previously escorting character " . $id . " not near vehicle, removing");
							unset($this->protector_times[$id]);
						}
					}

					for ($i = 0; $i < $characters->length; $i++) {
						$info = $characters->item($i);
						$id = $info->getAttribute("id");
						// grant whole tracking time step for this character
						if (!array_key_exists($id, $this->protector_times)) {
							// initialise the value
							$this->protector_times[$id] = 0.0;
						}
						$this->protector_times[$id] += $this->REWARD_TRACKING_TIME;					
						_log(" * character " . $id . " escorts the vehicle, time near so far: " . $this->protector_times[$id] . "s");
					}
				}

				$this->reward_tracking_timer = $this->REWARD_TRACKING_TIME;
			}
		}

	}

    // ----------------------------------------------------
	// debugging tools
    protected function handle_chat_event($doc) {
		// player_id
		// player_name
		// message
		// global
		$event = $doc->firstChild;

		$message = $event->getAttribute("message");
		// for the most part, chat events aren't commands, so check that first
		if (!starts_with($message, "/")) {
				return;
		}

		$sender = $event->getAttribute("player_name");
		$sender_id = $event->getAttribute("player_id");
		if (check_command($message, "help")) {
			// don't process if not properly started
			if ($this->has_started()) {
				$this->instruct_objective($sender_id);
			}
		}

		if (!is_admin($sender)) {
			return;
		}

        if (check_command($message, "reset")) {
            $this->metagame->comms->send("say reseting objective");
			if ($this->has_started()) {
				$this->end();
			}
			$this->reset();
			$this->wait_for_try_again_cycles = 0;
		}
	}

	// ----------------------------------------------------
	protected function handle_hitbox_event($doc) {
		// don't process if not properly started
		if (!$this->has_started()) return;

		// if already delivered, skip
		if ($this->delivery_done) {
			_log("delivery done already, still receiving hitbox events?");
			return;
		}

		// hitbox_id
		// instance_type (vehicle/character)
		// instance_id
		$event = $doc->firstChild;

		if ($event->getAttribute("instance_type") == "vehicle" &&
			$event->getAttribute("instance_id") == $this->tracked_vehicle_id) {

			// this event concerns our vehicle

			// done, clear hitbox tracking
			$this->clear_hitbox_associations();

			// who to reward?
			// everyone in the car?
			// just the driver?
			/*
			$vehicle_info = $this->get_vehicle_info($this->tracked_vehicle_id);
			$character_list = $vehicle_info->getElementsByTagName("character");
			for ($i = 0; $i < $character_list->length; ++$i) {
				$character = $character_list->item($i);
				$slot_type = $character->getAttribute("slot_type");
				// driver, 0
				if ($slot_type == 0) {
					$id = $character->getAttribute("id");
					$c = "<command class='rp_reward' character_id='" . $id . "' reward='400.0' />";
					$this->metagame->comms->send($c);
				}
			}*/
			// reward protectors, dead ones will be ignored by game
			$total_reward = $this->total_reward;
			// split reward among all protectors, weighing with amount of time spent near the truck
			$total_time = 0.0;
			foreach ($this->protector_times as $id => $value) {
				_log("* character " . $id . " spent " . $value . " s near the truck");
				$total_time += $value;
			}
			_log("* combined time " . $total_time . " s");

			foreach ($this->protector_times as $id => $value) {
				$reward = $total_reward * ($value / $total_time);
				$c = "<command class='rp_reward' character_id='" . $id . "' reward='" . $reward . "' />";
				$this->metagame->comms->send($c);
			}

			{
				$a = array("%vehicle_name" => $this->vehicle_name); 
				$this->metagame->send_faction_message_key(0, "vehicle delivered", $a);
			}

			// lock vehicle, pushes everyone out from it denying access
			$this->lock_vehicle($this->tracked_vehicle_id);

			// remove vehicle -- would be nicer if this would happen after a while only
			$this->remove_vehicle($this->tracked_vehicle_id);

			// reward with an unlock, if possible
			if ($this->unlocker) {
				$resource = new ResourceRef($this->vehicle_key, "vehicle", $this->vehicle_name);
				$this->unlocker->handle_item_delivery_completed($resource);
			}

			//$this->end();
			$this->delivery_done = true;
		}
	}

	// ----------------------------------------------------
	protected function handle_vehicle_destroyed_event($doc) {
		// don't process if not properly started
		if (!$this->has_started()) return;

		// vehicle_id
		// character_id
		$event = $doc->firstChild;

		if ($event->getAttribute("vehicle_id") == $this->tracked_vehicle_id) {
			// this event concerns our vehicle

			if (!$this->delivery_done) {
				// not delivered and 
				// our vehicle was just destroyed, cancel objective
				{
					$a = array("%vehicle_name" => $this->vehicle_name); 
					$this->metagame->send_faction_message_key(0, "vehicle objective cancelled, vehicle destroyed", $a);
				}

				$this->end();
			} else {
				// already delivered, now release the objective and vehicle
				$this->end();
			}
		}
	}

	// ----------------------------------------------------
	protected function handle_base_owner_change_event($doc) {
		// don't process if not properly started
		if (!$this->has_started()) return;

		// if already delivered, don't care
		if ($this->delivery_done) return;

		// base_id
		// owner_id (faction)

		// it's possible we lost/gained an armory, handle it

		// get the list of all armories, the latest one
		$armory_list = $this->get_armory_hitbox_list();

		// re-associate, will only add/remove the appropriate ones
		$this->associate_hitboxes($armory_list, $this->tracked_vehicle_id);

		// TODO:
		// - it's possible we end up without armories, what to do then?
		// a) cancel objective (bad if we already have the truck, and we might recapture a base again with armory soon)
		// b) keep the objective running
	}

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

	// ----------------------------------------------------
	function end() {
		Objective::end();

		$this->clear_hitbox_associations();

		$this->tracked_vehicle_id = -1;
		$this->delivery_done = false;
	}

	// --------------------------------------------------------
	// helper functions
	// --------------------------------------------------------
	function get_vehicle_list($faction_id, $vehicle_key) {
		$list_query_doc = new DOMDocument();

		// query about vehicles
		$command = 
			"<command class='make_query' id='arms_truck_query_1_" . $this->enemy_id . "'>\n" . 
			"	<data class='vehicles' faction_id='" . $faction_id . "' key='" . $vehicle_key . "' />\n" .
			"</command>";
		$list_query_doc->loadXML($command);

		// send the query command now
		// -- using query-call, it will block here until the response is got from the server
		$list_doc = $this->metagame->comms->query($list_query_doc);
		$vehicle_list = NULL;
		if ($list_doc) {
			$vehicle_list = $list_doc->getElementsByTagName("vehicle");
			_log("* " . $vehicle_list->length . " vehicles found with key " . $vehicle_key . " in faction " . $faction_id);
		}

		return $vehicle_list;
	}

	// --------------------------------------------------------
	function get_vehicle_info($vehicle_id) {
		$vehicle_query_doc = new DOMDocument();
		$command =
			"<command class='make_query' id='arms_truck_query_2_" . $this->enemy_id . "'>\n" .
			"	<data class='vehicle' id='" . $vehicle_id . "' />\n" .
			"</command>";
		$vehicle_query_doc->loadXML($command);

		// again, using query here
		$vehicle_doc = $this->metagame->comms->query($vehicle_query_doc);

		$vehicle = NULL;
		if ($vehicle_doc) {
			$vehicle = $vehicle_doc->getElementsByTagName("vehicle")->item(0);
		}

		return $vehicle;
	}

	// --------------------------------------------------------
	function get_faction_info($faction_id) {
		$query_doc = new DOMDocument();
		$command =
			"<command class='make_query' id='arms_truck_query_10_" . $this->enemy_id . "'>\n" .
			"	<data class='faction' id='" . $faction_id . "' />\n" .
			"</command>";
		$query_doc->loadXML($command);

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

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

		return $faction;
	}

	// -------------------------------------------------------
	function get_armory_hitbox_list() {
		// query about hitboxes
		$list_query_doc = new DOMDocument();
		$command = 
			"<command class='make_query' id='arms_truck_hitbox_query_1_" . $this->enemy_id . "'>\n" .
			"	<data class='hitboxes' faction_id='" . $this->friendly_id . "' />\n" .
			"</command>";
		$list_query_doc->loadXML($command);

		// send the query command now
		// -- using query-call, it will block here until the response is got from the server
		$list_doc = $this->metagame->comms->query($list_query_doc);

		$armory_list = NULL;
		if ($list_doc) {
			$armory_list = $list_doc->getElementsByTagName("hitbox");

			// go through the list and only leave the ones in we're interested of, i.e. weapon racks = armories
			for ($i = 0; $i < $armory_list->length; ++$i) {
				$hitbox_node = $armory_list->item($i);
				$id = $hitbox_node->getAttribute("id");
				//if (strpos($id, "weapon_rack") === false) {
				// rule out those that have stash in id
				// - every specifically made up hitbox and weapon_rack hitbox are allowed
				if (strpos($id, "stash") === false &&
					strpos($id, "extraction") === false) {
					_log("including " . $id);
				} else {
					_log("ruling out " . $id);
					// remove this
					$hitbox_node->parentNode->removeChild($hitbox_node);
					// one step back
					$i--;
				}
			}
			_log("* " . $armory_list->length . " armories found");
		}

		return $armory_list;
	}

	// -------------------------------------------------------
	function associate_hitboxes($armory_list, $vehicle_id) {
		$this->metagame->associate_hitboxes($armory_list, "vehicle", $vehicle_id, $this->tracked_hitboxes);
	}

	/*
	function associate_hitboxes($armory_list, $vehicle_id) {
		// check against already associated hitboxes
		// and determine which need to be added and which to removed

		// prepare to remove all hitboxes
		$add_ids = array();
		$remove_ids = $this->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='vehicle' instance_id='" . $vehicle_id . "' />";
			$this->metagame->comms->send($command);
		}

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

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

	// --------------------------------------------------------
	function instruct_objective($player_id = -1) {
		// announce the message now

		$a = array("%vehicle_name" => $this->vehicle_name, "%faction_name" => $this->enemy_name, "%location" => $this->latest_intel_text); 

		if ($player_id == -1) {
			$this->metagame->send_faction_message_key(0, "vehicle objective instruction", $a);
			$this->wait_for_new_intel_timer = mt_rand($this->MIN_WAIT_INTEL_TIME, $this->MAX_WAIT_INTEL_TIME);
		} else {
			$this->metagame->send_private_message_key($player_id, "vehicle objective instruction", $a);
		}
	}

	// --------------------------------------------------------
	function get_characters_near_vehicle($vehicle_id) {
		$vehicle_info = $this->get_vehicle_info($vehicle_id);
		if ($vehicle_info) {
			$position = $vehicle_info->getAttribute("position");

			$query_doc = new DOMDocument();
			$command =
					"<command class='make_query' id='vehicle_protectors_query_1'>\n" .
					"       <data class='characters' faction_id='" . $this->friendly_id . "' position='" . $position . "' range='80.0'/>\n" .
					"</command>";
			$query_doc->loadXML($command);

			$characters_doc = $this->metagame->comms->query($query_doc);

			if ($characters_doc) {
				$characters = $characters_doc->getElementsByTagName("character");
				return $characters;
			}
		} else {
			_log("WARNING, getting vehicle info for " . $vehicle_id . " failed");
		}

		return NULL;
	}

	// --------------------------------------------------------
	function get_intel_text($vehicle_info) {
		$pos = parse_position($vehicle_info->getAttribute("position"));
		$region_text = $this->metagame->get_region($pos);

		return $region_text . " region";
	}

	// --------------------------------------------------------
	function lock_vehicle($vehicle_id) {
		$command =
			"<command class='update_vehicle' id='" . $vehicle_id . "' locked='1'>\n" .
			"</command>";
		$this->metagame->comms->send($command);
	}

	// --------------------------------------------------------
	function remove_vehicle($vehicle_id) {
		$command =
			"<command class='remove_vehicle' id='" . $vehicle_id . "'>\n" .
			"</command>";
		$this->metagame->comms->send($command);
	}

}


?>