<?php

include_once("internal/helpers.php");
include_once("internal/tracker.php");
include_once("internal/log.php");
include_once("comms_settings.php");

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

$ENCODED_LENGTH_SIZE = 4;
$INCOMING_GAME_ENCODING = "iso-8859-1";

function utf_to_game($str) {
	global $INCOMING_GAME_ENCODING;
	$result = iconv("utf-8", $INCOMING_GAME_ENCODING, $str);

	return $result;
}

$_quit_signal_received = false;

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

class QueryTracker extends Tracker {
	protected $outgoing_query_id = "id_not_set";
	protected $ok = false;

	function init($id) {
		$this->outgoing_query_id = $id;
	}

	protected function handle_query_result($doc) {
		$incoming_query_id = $doc->firstChild->getAttribute("query_id");
		// check if this is the result for our query, and process it 
		if ($incoming_query_id == $this->outgoing_query_id) {
			// expected response got
			$this->ok = true;
		}
	}

	function is_ok() {
		return $this->ok;
	}

	function has_started() {
		return true;
	}
	function has_ended() {
		return $this->ok;
	}
}

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

class Comms {
	public $out_fifo = 0;
	public $in_fifo = 0;
	// queue
	public $received_messages = array();
	public $last_query_uid;

	function init() {
		global $fifo_out_path;
		global $fifo_in_path;

		if (is_windows()) {
			_log("opening out fifo " . $fifo_out_path);
			$this->out_fifo = fopen($fifo_out_path, 'w');
			if (!$this->out_fifo) {
				_log("failed to open " . $fifo_out_path . " -- start rwr server first?");
				die;
			}

			_log("opening in fifo " . $fifo_in_path);
			$this->in_fifo = fopen($fifo_in_path, 'r');
			if (!$this->in_fifo) {
				// open failed, abort
				_log("failed to open " . $fifo_in_path . " -- start rwr server first?");
				die;
			}

		} else {
			// linux
			// make sure the fifos exist
			posix_mkfifo($fifo_out_path, 0666);
			posix_mkfifo($fifo_in_path, 0666);

			_log("opening out fifo " . $fifo_out_path);
			// note, having r/w+ allows us to not block here in Linux
			$this->out_fifo = fopen($fifo_out_path, 'w+');
			if (!$this->out_fifo) {
				_log("failed to open " . $fifo_out_path . " -- start rwr server first?");
				die;
			}

			_log("opening in fifo " . $fifo_in_path);
			$this->in_fifo = fopen($fifo_in_path, 'r+');
			if (!$this->in_fifo) {
				// open failed, abort
				_log("failed to open " . $fifo_in_path . " -- start rwr server first?");
				die;
			}
		}
	}

	// --------------------------------------------
	// send data to the server
	// --------------------------------------------
	function send($message) {
		global $ENCODED_LENGTH_SIZE;

		_log("  sending: " . $message);
			
		$outgoing_length = strlen($message);

		if ($outgoing_length >= pow(10, $ENCODED_LENGTH_SIZE)) {
			_log("ERROR, too large message, will fail, limit is " . (pow(10, $ENCODED_LENGTH_SIZE)));
			die();
		}
		$outgoing_length = str_pad($outgoing_length, $ENCODED_LENGTH_SIZE, " ", STR_PAD_LEFT);

		//fwrite($this->out_fifo, $outgoing_length);
		//fwrite($this->out_fifo, $message);

		fwrite($this->out_fifo, $outgoing_length . $message);
	}

	// --------------------------------------------
	// wait for data from the server
	// - execution blocks here until data arrives
	// --------------------------------------------
	function receive() {
		// it's possible the comms interface has queued some data to be handled
		// when the public receive gets called

		// consume this queue first in such case
		if (count($this->received_messages) > 0) {
			$data = array_shift($this->received_messages);
			return $data;
		}

		// if no more queued messages, receive new data from the pipe

		// (this is done to allow query-response to handle things synchronously
		// and collect missed messages not related to the query, to be served
		// at a later time by the Comms user who is handling all other messages)

		return $this->_receive();
	}

	// actual pipe receiving handler
	protected function _receive() {
		global $ENCODED_LENGTH_SIZE;
		
		// read data from rwr_server
		$incoming_length = fread($this->in_fifo, $ENCODED_LENGTH_SIZE);
		if ($incoming_length === false || strlen($incoming_length) == 0) {
			return false;
		}
		$data = fread($this->in_fifo, $incoming_length);

		//_log("  received:");
		if (trim($data) == "<dummy_event />") {
		} else {
			global $INCOMING_GAME_ENCODING;
			$data = "<?xml version='1.0' encoding='" . $INCOMING_GAME_ENCODING . "'?>\n" . $data;
			_log($data);
		}

		return $data;
	}

	// --------------------------------------------
	// two way query-response, for convenience
	// - accepts query DOMDocument as input
	// - execution blocks here until response is received
	// - returns the response DOMDocument or NULL if failed
	// --------------------------------------------
	function query($query_doc) {
		global $_quit_signal_received;
		// if quit was received, don't handle any more queries, no one's going to respond
		if ($_quit_signal_received) {
			_log("  aborting query");
			return NULL;
		}

		_log("  sending query");

		// create in-place Tracker for matching with the incoming data
		$tracker = new QueryTracker();

		// store query id in tracker for matching with the response
		$query_id = $query_doc->getElementsByTagName("command")->item(0)->getAttribute("id");
		$tracker->init($query_id);

		// send the query now
		$this->send($query_doc->saveXML());

		$result = NULL;

		// keep receiving data until the response arrives
		while (true) {
			_log("  waiting for response for the query...");
			// using internal receiving call, bypass any queued messages
			$data = $this->_receive();

			// something went horribly wrong:
			if ($data === false) {
				_log("query failed, couldn't receive data");
				break;
			}

			// check if it's response to what we asked
			if (!empty($data)) { 
				//_log("  verifying data");
				$doc = new DOMDocument();
				$doc->loadXML($data);

				if ($doc->firstChild->localName == "user_quit_event") {
					// a bit messy, but go for it for now
					_log("user quit signal detected, aborting query");

					$_quit_signal_received = true;

					// push it in to re-handle it on metagame level
					array_push($this->received_messages, $data);
					break;
				}

				$tracker->handle_event($doc);
				if ($tracker->is_ok()) {
					_log("  response received");
					// correct response received, return to caller as XML
					$result = $doc;
					break;
				} else {
					_log("  received something else than the response, queueing for later processing");
					// if another event comes in, we must not lose the data,
					// store these in the internal comms data queue, and let them 
					// come out one by one on subsequent external receive calls
					// until they have been discarded or processed
					array_push($this->received_messages, $data);
				}
			}
		}

		if ($result == NULL) {
			_log("WARNING, invalid query result");
		}

		return $result;
	}

	// --------------------------------------------
	function clear_queue() {
		$this->received_messages = array();
		// also clear infifo
		$data = $this->receive();
	}

	// --------------------------------------------
	function uninit() {
		fclose($this->in_fifo);
		fclose($this->out_fifo);
	}

	// --------------------------------------------
	function get_query_uid() {
		$uid = $this->last_query_uid;
		$this->last_query_uid++;
		return $uid;
	}

}

?>