<?php
namespace App\Amadeus\Storage;

/**
 * Class hadling each individual transaction in the AIR file
 * @author Lucian
 *
 */
class Operation
{
	/**
	 * The transaction index type in the file
	 * @var string
	 */
	protected $indexType = 0;

	/**
	 * The transaction index in the file
	 * @var object
	 */
	protected $index = null;

	/**
	 * A string describing the transaction type
	 * @var string
	 */
	protected $transactionType = '';

	/**
	 * The parent processor
	 * @var \App\Amadeus\StructureParser\Processor
	 */
	protected $processor = null;

	/**
	 * Internal log messages
	 */
	protected $generatedLogMessages = [];

	/**
	 * Internal cache, to keep track of instantiated objects
	 * @var array
	 */
	protected $CACHE = [];

	/**
	 * Non-structured associated data. Used to carry various information between parsing steps/plugins
	 * DO NOT USE AS A DATA PLACEHOLDER
	 * @var array
	 */
	protected $nonStructuredData = [];


	public function __construct($processor, $indexType, $index)
	{
		$this->processor = 			$processor;
		$this->indexType = 			$indexType;

		if ($index) {
			$idx = new OperationReferences();
			foreach ($index as $k=>$v) {
				$idx->{$k} = $v;
			}
			$this->index = 				$idx;
		}
		else {
			$this->index = null;
		}
	}


	/**
	 * Cleanup any allocated memory, to avoid memory leaks
	 */
	public function destroy()
	{
		foreach ($this->CACHE as $k=>$obj) {
			$obj->destroy();
			unset($this->CACHE[$k]);
		}
		$this->index = null;
		$this->indexType = null;
		$this->processor = null;
	}

	/**
	 * Returns the internal CACHE variable. Useful in case we need to rewrite the output of getDocument() for example
	 */
	public function getInternalCache()
	{
		return $this->CACHE;
	}

	/**
	 * Fills the internal CACHE variable. Useful in case we need to rewrite the output of getDocument() for example
	 */
	public function setInternalCache($CACHE)
	{
		$this->CACHE = $CACHE;
		return $this;
	}

	/**
	 * Get the used \App\Amadeus\StructureParser\Processor
	 */
	public function getProcessor()
	{
		return $this->processor;
	}

	/**
	 * Get the parsed structure
	 * @return array
	 */
	public function getParsedStructure()
	{
		return $this->processor->getParsedStructure();
	}

	/**
	 * Get the transaction index
	 * @return object
	 */
	public function getIndex()
	{
		return $this->index;
	}

	/**
	 * Get the index type (H - ticketed, U - unticketed)
	 * @return string
	 */
	public function getIndexType()
	{
		return $this->indexType;
	}

	/**
	 * Get the type of this transaction(ticketing, insurance, etc)
	 * 	This function is supposed to know how to detect all transaction types.
	 * 	Each transaction will have an extra isValid() function, but thats suipposed to always return true, unless this function fails to detect it properly
	 * @throws Exception
	 * @return string
	 */
	public function getType()
	{
		if ($this->transactionType) {
			return $this->transactionType;
		}
		$parsedStructure = $this->processor->getParsedStructure();
		$index = $this->getIndex();

		// detect ticketing
		if (!$this->transactionType && ($this->indexType=='H' || $this->indexType=='H:reissue') && isset($parsedStructure['I'])) {
			// check that all passengers in this structure have a ticket, if not, then we might not have a ticketing operation
			$type = 'ticketing';
			foreach ($parsedStructure['I'] as $I) {
				if (!isset($I['T']) && !isset($I['TMC'])) {
					$type = null;
					break; // optimization
				}
			}
			$this->transactionType = $type;
		}


		// look into U lines
		if (!$this->transactionType && $this->indexType=='U' && isset($parsedStructure['U'])) {
			$U = $parsedStructure['U'][$index->segment];	// pick the first index... not sure its correct, and i dont know how this will handle the case where 2 different unticketed segments are present in the file

			/**
			 * Check for insurance
			 *  check if we have a policy number & the tag is correct
			 */
			if (	!$this->transactionType && $U['_type']=='21') {
				$this->transactionType = 'insurance';
			}

			/**
			 * Check for hotel(manual segment)
			 *  check that the tag is correct
			 */
			if (	!$this->transactionType && $U['_type']=='05') {
				$this->transactionType = 'hotel';
			}

			/**
			 * Check for hotel(auto segment)
			 *  check that the tag is correct
			 */
			if (	!$this->transactionType && $U['_type']=='06') {
				$this->transactionType = 'hotel';
			}

			/**
			 * Check for manual car
			 *  check that the tag is correct
			 */
			if (	!$this->transactionType && $U['_type']=='08') {
				$this->transactionType = 'car';
			}

			/**
			 * Check for automatic car segment
			 *  check that the tag is correct
			 */
			if (	!$this->transactionType && $U['_type']=='09') {
				$this->transactionType = 'car';
			}

			/**
			 * Check for ground transportation
			 *  check that the tag is correct
			 */
			if (	!$this->transactionType && $U['_type']=='10') {
				$this->transactionType = 'groundTransportation';
			}

			/**
			 * Check for Miscellaneous Segment, Transfer service
			 *  check that the tag is correct
			 */
			if (	!$this->transactionType && $U['_type']=='17' && $U['FREE_'] && $U['FREE_']['_type']=='transfer') {
				$this->transactionType = 'transfer';
			}

			/**
			 * Check for Miscellaneous Segment, Transfer service
			 *  check that the tag is correct
			 */
			if (	!$this->transactionType && $U['_type']=='17' && $U['FREE_'] && $U['FREE_']['_type']=='train') {
				$this->transactionType = 'train';
			}

			/**
			 * Check for Miscellaneous Segment, Transfer service
			 *  check that the tag is correct
			 */
			if (	!$this->transactionType && $U['_type']=='17' && $U['FREE_'] && $U['FREE_']['_type']=='hotel') {
				$this->transactionType = 'hotel';
			}

			/**
			 * Check for Miscellaneous Segment
			 *  check that the tag is correct
			 */
			if (	!$this->transactionType && $U['_type']=='17') {
				$this->transactionType = 'misc';
			}

			/**
			 * Check for U-MCO Segment
			 *  check that the tag is correct
			 */
			if (	!$this->transactionType && $U['_type']=='18') {
				$this->transactionType = 'MCO';
			}

			/**
			 * Check for misc segments(EMD)
			 *  check that the tag is correct
			 */
			if (	!$this->transactionType && $U['_type']=='19') {
				$this->transactionType = 'EMD';
			}

			/**
			 * Check for unticketed ticket
			 *  - this should never happen at this point, all U:ticketing segments should be handled in the next block of code
			 */
			if (	!$this->transactionType && $U['_type']=='01') {
				throw new \Exception("We found a mis-labeled U:ticketing line of code. this should not happen, all U lines should be marked as needed");
			}
		}

		// lookup U ticketing lines
		if (!$this->transactionType && $this->indexType=='U:ticketing' && isset($parsedStructure['U'])) {
			$UIndexArr = $parsedStructure['assoc:U:pax-segment'][$index->passenger];
			foreach ($UIndexArr as $segIdx) {
				$U = $parsedStructure['U'][$segIdx];

				/**
				 * Look for ticketing segments, stop when found
				 */
				if (	!$this->transactionType && $U['_type']=='01') {
					$this->transactionType = 'ticketing';
					break;
				}
			}
		}

		// look into IVD lines
		if (!$this->transactionType && $this->indexType=='IVD' && $parsedStructure['IVD']) {
			$this->transactionType = 'insurance';
		}

		// look into EMD lines
		if (!$this->transactionType && $this->indexType=='EMD' && $parsedStructure['EMD']) {
			$this->transactionType = 'EMD';
		}

		// look into MCO lines
		if (!$this->transactionType && $this->indexType=='MCO' && $parsedStructure['MCO']) {
			$this->transactionType = 'MCO';
		}

		// look into RFND lines
		if (!$this->transactionType && $this->indexType=='RFD' && $parsedStructure['RFD']) {
			$this->transactionType = 'refund';
		}

		// lookup ticketed VOID-ed segments
		if (!$this->transactionType && $this->indexType=='VOID' && $parsedStructure['AMD']['AMD']['ACTTAG']=='VOID') {
			$this->transactionType = 'void';
		}
		// void for an insurance
		if (!$this->transactionType && $this->indexType=='VOID:insurance' && $parsedStructure['IVD']) {
			$this->transactionType = 'void';
		}

		if (!$this->transactionType) {
			throw new \App\Amadeus\StructureParser\StructureException("Unknown file type detected...");
		}


		return $this->transactionType;
	}

	/**
	 * Allow forse-setting the internal transaction type
	 */
	public function setType($transactionType)
	{
		$this->transactionType = $transactionType;
		return $this;
	}

	/**
	 * Get an object that will be able to extract the passenger in this transaction
	 */
	public function getPassenger()
	{
		if ($this->CACHE[__METHOD__]) return $this->CACHE[__METHOD__];
		$obj = \App\Amadeus\StructureParser\ClassBuilder::getPassengers($this, $this->getType());
		if ($obj->isValid()) {
			$this->CACHE[__METHOD__] = $obj;
			return $obj;
		}

		// we'll get here when we assume we're on one type of transaction, but the underliying processor says its invalid... dont really know what to do in this case
		throw new \Exception(__METHOD__.": How should we handle the case when we're detecting the segment as one type, but the processor says that its not?");
	}

	/**
	 * Get an object that will be able to extract the itinerary in this transaction
	 */
	public function getItinerary()
	{
		if ($this->CACHE[__METHOD__]) return $this->CACHE[__METHOD__];

		$obj = \App\Amadeus\StructureParser\ClassBuilder::getItinerary($this, $this->getType());
		if ($obj->isValid()) {
			$this->CACHE[__METHOD__] = $obj;
			return $obj;
		}

		throw new \Exception(__METHOD__.": How should we handle the case when we're detecting the segment as one type, but the processor says that its not?");
	}

	/**
	 * Get an object that will be able to extract the price in this transaction
	 */
	public function getPrice()
	{
		if ($this->CACHE[__METHOD__]) return $this->CACHE[__METHOD__];

		$obj = \App\Amadeus\StructureParser\ClassBuilder::getPrice($this, $this->getType());
		if ($obj->isValid()) {
			$this->CACHE[__METHOD__] = $obj;
			return $obj;
		}

		throw new \Exception(__METHOD__.": How should we handle the case when we're detecting the segment as one type, but the processor says that its not?");
	}

	/**
	 * Get an object that will be able to extract the service details
	 */
	public function getServiceDetails()
	{
		if ($this->CACHE[__METHOD__]) return $this->CACHE[__METHOD__];

		$obj = \App\Amadeus\StructureParser\ClassBuilder::getServiceDetails($this, $this->getType());
		if ($obj->isValid()) {
			$this->CACHE[__METHOD__] = $obj;
			return $obj;
		}

		throw new \Exception(__METHOD__.": How should we handle the case when we're detecting the segment as one type, but the processor says that its not?");
	}

	/**
	 * Get an object that will be able to extract the user/office details
	 */
	public function getUser()
	{
		if ($this->CACHE[__METHOD__]) return $this->CACHE[__METHOD__];

		$obj = \App\Amadeus\StructureParser\ClassBuilder::getUser($this, $this->getType());
		if ($obj->isValid()) {
			$this->CACHE[__METHOD__] = $obj;
			return $obj;
		}

		throw new \Exception(__METHOD__.": How should we handle the case when we're detecting the segment as one type, but the processor says that its not?");
	}

	/**
	 * Get an object that will be able to extract the document details
	 */
	public function getDocument()
	{
		if ($this->CACHE[__METHOD__]) return $this->CACHE[__METHOD__];

		$obj = \App\Amadeus\StructureParser\ClassBuilder::getDocument($this, $this->getType());
		if ($obj->isValid()) {
			$this->CACHE[__METHOD__] = $obj;
			return $obj;
		}

		throw new \Exception(__METHOD__.": How should we handle the case when we're detecting the segment as one type, but the processor says that its not?");
	}

	/**
	 * Check if transaction is ticketed(has docNumber set)
	 * @return bool
	 */
	public function isTicketed()
	{
		return (bool)$this->getDocument()->document()->docNumber;
	}

	/*-----------------------------------------------------------------*/
	/** LOGS **/

	/**
	 * Add a message to the parser log
	 * @param string $message
	 */
	public function addLog($message)
	{
		$this->generatedLogMessages[] = $message;
	}

	/**
	 * Get the list of messages in the log
	 * @return array:string
	 */
	public function getLogArr()
	{
		return $this->generatedLogMessages;
	}

	/**
	 * Set an element in the nsd (non-structured-data) list
	 * @param string $key
	 * @param mixed $value
	 */
	public function nonStructuredDataSet($key, $value)
	{
		$this->nonStructuredData[$key] = $value;
		return $this;
	}

	/**
	 * Append an element in the nsd (non-structured-data) list
	 * @param string $key
	 * @param mixed $value
	 */
	public function nonStructuredDataAppend($key, $value)
	{
		if (!array_key_exists($key, $this->nonStructuredData)) {
			$this->nonStructuredData[$key] = [];
		}

		$this->nonStructuredData[$key][] = $value;

		return $this;
	}

	/**
	 * Get a nsd value
	 * @return mixed
	 */
	public function nonStructuredDataGet($key)
	{
		return $this->nonStructuredData[$key];
	}

	/**
	 * Check if a key exists in the nsd list
	 */
	public function nonStructuredDataIsSet($key)
	{
		return isset($this->nonStructuredData[$key]);
	}

}
