<?php

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

include_once("trackers/resource_unlocker.php");

// --------------------------------------------
class ItemDeliveryObjective extends Objective {
	protected $item_list = NULL;
	protected $delivery_done = false;
	protected $require_specific_armory = true;
	protected $tracked_armory_position = "0 0 0"; // as string
	protected $wait_for_try_again_timer = 0;
	protected $reward_per_piece = 100.0;
	protected $reward_for_completion = 100.0;
	protected $starting_delivery_amount = 3;
	protected $delivery_amount = 3;
	protected $friendly_id = 0;
	protected $organizer = NULL;
	protected $unlocker = NULL;
	protected $instructions = "";
	protected $map_text = "";
	protected $thanks_texts = array();

	protected $WAIT_RETRY_TIME = 60.0;

	// TODO:
	// - if a faction loses, it is possible the resources it had can no longer be obtained -- cancel such item missions then

	// ----------------------------------------------------
	function __construct($metagame, $item_list, $organizer, $unlocker, $instructions, $map_text, $thanks, $delivery_amount, $require_specific_armory) {
		parent::__construct($metagame);
		
		$this->item_list = $item_list;

		$this->organizer = $organizer;
		$this->unlocker = $unlocker;

		$this->instructions = $instructions;
		$this->map_text = $map_text;
		$this->thanks = $thanks;

		$this->starting_delivery_amount = $delivery_amount;
		$this->delivery_amount = $delivery_amount;

		$this->require_specific_armory = $require_specific_armory;
	}

	// ----------------------------------------------------
	function get_armory_position() {
		return $this->tracked_armory_position;
	}

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

		$handled = false;

		if ($this->wait_for_try_again_timer <= 0) {
			// announce task

			// check that we have armories available to deliver the item
			$armory_list = $this->get_armory_list();
			if ($this->require_specific_armory) {
				if ($armory_list && $armory_list->length > 0) {
					// make sure the armory doesn't already have another task
					$this->organizer->ensure_armory_availability($armory_list);
				}
			}

			if ($armory_list && $armory_list->length > 0) {
				if ($this->require_specific_armory) {
					// pick one armory which requests the task
					$chosen_index = rand(0, $armory_list->length - 1);
					$list_item = $armory_list->item($chosen_index);

					$this->tracked_armory_position = $list_item->getAttribute("position");
				}

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

				// mark it
				$this->mark_objective();

				$handled = true;
			}

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

		if (!$handled) {
			// 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;
		}
	}

    // ----------------------------------------------------
	// 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_item_task")) {
			$this->metagame->comms->send("say reseting item task");
			if ($this->has_started()) {
				$this->end();
			}
			$this->reset();
		}
		*/
        if (check_command($message, "deliver_item")) {
			// .. create suitcase near local player
			$player_info = $this->metagame->get_player_info($sender_id);
			if ($player_info) {
				$character_info = $this->metagame->get_character_info($player_info->getAttribute("character_id"));
				if ($character_info) {
					$pos = parse_position($character_info->getAttribute("position"));
					$pos[0] += 3.0;
					$p = position_array_to_string($pos);
					$c = "<command class='create_instance' instance_class='carry_item' instance_key='suitcase.carry_item' position='" . $p . "' />";
					$this->metagame->comms->send($c);
				}
			}
		}
	}

	// ----------------------------------------------------
	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

		if ($this->require_specific_armory) {
			// get the list of all armories, the latest one
			$armory_list = $this->get_armory_list();

			// check if our tracked armory is still ok
			$found = false;
			for ($i = 0; $i < $armory_list->length; $i++) {
				$pos = $armory_list->item($i)->getAttribute("position");
				if ($pos == $this->tracked_armory_position) {
					// it's there
					$found = true;
					break;
				}
			}

			if (!$found) {
				// TODO: end or reset?
				$this->end();
			}
		}
	}

	// ----------------------------------------------------
	protected function handle_item_drop_event($doc) {
		// character_id
		// item_class
		// item_type_id
		// item_key 
		// target_container_type_id
		// position

		// don't process if not properly started
		if (!$this->has_started()) return;

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

		$event = $doc->firstChild;

		// ensure it's the wanted item
		$item_key = $event->getAttribute("item_key");
		$target_item = NULL;
		foreach ($this->item_list as $key => $item) {
			if ($item_key === $item->key) {
				// found it, continue
				$target_item = $item;
				break;
			}
		}
		if (!$target_item) return;

		// ensure it's the right place
		if ($this->require_specific_armory) {
			if (!check_range(parse_position($event->getAttribute("position")), parse_position($this->tracked_armory_position), 10.0)) return;
		}

		// correct item, correct place, now give an extra reward
		$id = $event->getAttribute("character_id");

		$player_id = $event->getAttribute("player_id");

		if ($this->thanks != "") {
			// comment about it?
			// RP is after all supposed to reflect 
			// - "what a great guy you are that you're doing these tasks for us", it means we'll do favors for you too, as in give you stuff and respond to your calls
			
			$this->metagame->send_private_message_key($player_id, $this->thanks);
		}

		if ($this->reward_per_piece > 0) {
			$c = "<command class='rp_reward' character_id='" . $id . "' reward='" . $this->reward_per_piece . "' />";
			$this->metagame->comms->send($c);
		}

		_log("delivery_amount = " . $this->delivery_amount);

		if ($this->delivery_amount > 0) {
			// reduce needed amount
			$this->delivery_amount -= 1;

			if ($this->delivery_amount == 0) {
				// all delivered
				$this->delivery_done = true;

				if ($this->reward_for_completion > 0) {
					// give completion reward
					$c = "<command class='rp_reward' character_id='" . $id . "' reward='" . $this->reward_for_completion . "' />";
					$this->metagame->comms->send($c);
				}

				{
					// comment about it?
					//$text = "objective completed, " . $this->item_name . " unlocked";

					$this->metagame->send_faction_message_key(0, "item delivered");
				}

				if ($this->unlocker) {
					$this->unlocker->handle_item_delivery_completed($target_item);
				}

				$this->end();
			} else {
				// items still wanted, update marker
				$this->mark_objective();
			}
		} else {
			// delivery amount is negative or 0, means it doesn't end ever
			// each piece delivey can unlock something
			if ($this->unlocker) {
				$this->unlocker->handle_item_delivery_completed($target_item);
			}
		}
	}

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

		$this->unmark_objective();

		$this->tracked_armory_position = "0 0 0";
		$this->delivery_done = false;

		// inform organizer as well about ending
		$this->organizer->objective_ended($this);
	}

	// -------------------------------------------------------
	function get_armory_list() {
		// query about item containers
		$list_query_doc = new DOMDocument();
		$command = 
			"<command class='make_query' id='item_container_query_" . $this->get_uid() . "'>\n" .
			"	<data class='item_containers' 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("item_container");

			// 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) {
				$node = $armory_list->item($i);
				$type_id = $node->getAttribute("type_id");
				$pos = $node->getAttribute("position");
				// 1 is armory, 3 is personal stash
				if ($type_id == 1) {
					_log("including " . $pos);
				} else {
					_log("ruling out " . $pos);
					// remove this
					$node->parentNode->removeChild($node);
					// one step back
					$i--;
				}
			}
			_log("* " . $armory_list->length . " armories found");
		}

		return $armory_list;
	}

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

		$a = array();
		if ($this->require_specific_armory) {
			// figure out more details -- armory base, region direction
			// - if really close to base center, it could be "in base_name"
			// - if not, it could be "near base_name" or e.g. "in southwest from base_name"

			// let's go with the simple one 
			$distance = 0.0;
			$base = $this->metagame->get_closest_base(parse_position($this->get_armory_position()), $distance);
			$location_text = ".. somewhere";
			if ($base) {
				$base_name = $base->getAttribute("name");
				$location_text = $base_name;
				/*
				$location_text = "in " . $base_name;
				if ($distance > 50.0) {
					$location_text = "near " . $base_name;
				}
				*/
			}

			//$text = str_replace("%location", $location_text, $this->instructions);

			$a = array("%base_name" => $location_text);
		} else {
			// any armory is fine, announce it
		}

		if ($player_id == -1) {
			$this->metagame->send_faction_message_key(0, $this->instructions, $a);
		} else {
			$this->metagame->send_private_message_key($player_id, $this->instructions, $a);
		}
	}

	// --------------------------------------------------------
	function mark_objective() {
		// don't mark it unless it's specific armory
		if (!$this->require_specific_armory) return;

		$offset = 1000;
		$text_key = "";
		if ($this->delivery_amount > 0) {
			$text_key = $this->map_text; // disabled, if text key system is to be used, marker text needs replacement support for these: . " " . $this->delivery_amount . "/" . $this->starting_delivery_amount;
		} else {
			$text_key = $this->map_text;
		}
		$position = $this->tracked_armory_position;
		$color = "#FFFFFF";

		$command = "<command class='set_marker' id='" . ($offset + $this->get_uid()) . "' faction_id='" . $this->friendly_id . "' atlas_index='0' text_key='" . $text_key . "' position='" . $position . "' color='" . $color . "' />\n";

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

	// --------------------------------------------------------
	function unmark_objective() {
		$offset = 1000;
		$command = "<command class='set_marker' id='" . ($offset + $this->get_uid()) . "' enabled='0' />\n";
		$this->metagame->comms->send($command);
	}


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


	// --------------------------------------------------------
	/*
	function pick_item_for_delivery() {
		$type = "weapon";
		$type_plural = "weapons";

		$own = $this->get_faction_deliverables($this->friendly_id, $type, $type_plural);
		if (!$own) return false;

		// get enemy weapons
		$others = array();
		foreach ($this->metagame->get_factions() as $key => $value) {
			if ($key == $this->friendly_id) continue;
			$other = $this->get_faction_deliverables($key, $type, $type_plural);
			if (!$other) break;

			// go through the list and only leave the ones in we're interested of, those which we don't have yet
			for ($i = 0; $i < $other->length; ++$i) {
				$node = $other->item($i);
				$key = $node->getAttribute("key");
				// check if we have this key
				for ($j = 0; $j < $own->length; ++$j) {
					$own_node = $own->item($j);
					$own_key = $own_node->getAttribute("key");
					if ($key !== $own_key) {
						// no match, continue searching
					} else {
						// matches, rule it out
						$node->parentNode->removeChild($node);
						// one step back
						$i--;
						// no need to look further
						break;
					}
				}
			}

			// also, remove items we are already demanding related to another delivery objective
			if ($other->length > 0) {
				$this->organizer->ensure_item_availability($other);
			}

			// only consider this faction for delivery if there are still weapons left in the container
			if ($other->length > 0) {
				$others[] = $other;
			}
		}

		// now others has weapons per enemy faction that we could want to have
		// first randomly choose the faction, then randomly choose the weapon
		if (count($others) > 0) {
			$faction_index = rand(0, count($others) - 1);
			$other = $others[$faction_index];
			
			_log("possible weapons " . $other->length);
			$weapon_index = rand(0, $other->length - 1);
			$weapon = $other->item($weapon_index);

			$this->item = ResourceRef($type, $weapon->getAttribute("key"));
			$this->item_name = $weapon->getAttribute("name");

			return true;
		}

		return false;
	}
	*/

}

// --------------------------------------------
class ItemDeliveryOrganizer {
	protected $metagame = NULL;
	protected $objectives = array();

	// ----------------------------------------------------
	function __construct($metagame) {
		$this->metagame = $metagame;

		// we'll just fire 3 possible item delivery objectives 
		// - they'll attempt to try themselves every once in a while, there's no big harm if they always can't
		//$this->objectives[] = new ItemDeliveryObjective($this->metagame, $this, $this->unlocker);
		//$this->objectives[] = new ItemDeliveryObjective($this->metagame, $this, $this->unlocker);
		{
			$delivery_list = array();
			{
				 $delivery_list[] = new ResourceRef("suitcase.carry_item", "carry_item");
				 // TODO: mystery suitcase to be delivered to same location, but setup different rewards
			}

			$unlock_list = array();
			{
				$target = "suitcase.carry_item";
				$list = $this->metagame->get_unlock_weapon_list();
				// TODO: list here what we can give as an unlock for suitcase delivery
				$unlock_list[$target] = $list;
			}
			$unlocker = new ResourceUnlocker($metagame, $unlock_list);

			$instructions = "item objective instruction";
			$map_text = "item objective map text";
			$thanks = "item objective thanks";

			$this->objectives[] = new ItemDeliveryObjective($this->metagame, $delivery_list, $this, $unlocker, $instructions, $map_text, $thanks, -1 /* loop */, false /* specific armory not required */);
		}

		foreach ($this->objectives as $key => &$objective) {
			$this->metagame->add_tracker($objective);
		}
	}

	// ----------------------------------------------------
	function objective_ended(&$objective) {
		// remove ended objective and start a new one
		$key = array_search($objective, $this->objectives, TRUE);
		if ($key !== FALSE) {
			_log("removing item objective " . $key);
			unset($this->objectives[$key]);

			// now start new
			//$this->objectives[] = new ItemDeliveryObjective($this->metagame, $this, $this->unlocker);

			// these days, item delivery objectives don't end
			// - helps in keeping the same armory for same type items

		} else {
			_log("WARNING, ended item objective not found in organizer list of objectives");
		}
	}

	// ----------------------------------------------------
	// armory_list is DOMNodeList
	function ensure_armory_availability($armory_list) {
		// traverse through existing objectives
		// - if an objective already is associated with an armory in the list, remove it
		_log("checking armories, " . $armory_list->length);
		_log("objectives, " . count($this->objectives));
		for ($i = 0; $i < $armory_list->length; $i++) {
			$node = $armory_list->item($i);
			foreach ($this->objectives as $key => &$objective) {
				_log("  " . $objective->get_armory_position() . " (in objective) vs " . $node->getAttribute("position"));
				if ($objective->get_armory_position() === $node->getAttribute("position")) {
					_log("  already used, ruled out");
					// already used, rule out
					$node->parentNode->removeChild($node);
					// one step back
					$i--;
					break;
				}
			}
		}
	}

	/*
	// ----------------------------------------------------
	// item_list is DOMNodeList
	function ensure_item_availability($item_list) {
		// traverse through existing objectives
		// - if an objective already is associated with a weapon in the list, remove it
		_log("checking items, " . $item_list->length);
		_log("objectives, " . count($this->objectives));
		for ($i = 0; $i < $item_list->length; $i++) {
			$node = $item_list->item($i);
			foreach ($this->objectives as $key => &$objective) {
				_log("  " . $objective->get_item_key() . " (in objective) vs " . $node->getAttribute("key"));
				if ($objective->get_item_key() === $node->getAttribute("key")) {
					_log("  already used, ruled out");
					// already used, rule out
					$node->parentNode->removeChild($node);
					// one step back
					$i--;
					break;
				} else {
					_log("  ok");
				}
			}
		}
	}
	*/
}

?>