<?php
namespace App\Utils\BaseParser\Utils;

/**
 * Parse fare calculation raw data and extract taxes from it
 * Example of input: '  SBZ LH X/MUC LH X/OSL SK BGO205.46/-OSL LH X/MUC LH SBZ205.46NUC410.92END ROE0.893096  XT12.00RO5.00DC180.00RA37.00G277.00ZN';
 * This class is used by Sabre and Galileo
 * @author Oana Nicolau
 *
 */
class FareCalculationExtractor
{
	use \App\Utils\BaseParser\SingletonTrait;

	/**
	 * Extract as array fare calculation
	 * @param string $fareCalculationRawData
	 * @return array
	 */
	public function extract($fareCalculationRawData)
	{
		$ret = [];

		$extractor = new \App\Utils\BaseParser\Utils\Extractor\Single();

		$fareCalculationRawDataArr = array_filter(explode("###", $fareCalculationRawData));

		$ret = [
			'unknownFormat' => true,
		];

		$ret[] = $this->doExtract($fareCalculationRawDataArr[0], $extractor);

		// I only care about the 1'st element following the expected format, the rest can have an invalid format, as they're not used for anything atm
		foreach (array_slice($fareCalculationRawDataArr, 1) as $fareCalculation) {
			try {
				$ret[] = $this->doExtract($fareCalculation, $extractor);
			}
			catch (\App\Utils\BaseParser\TextParser\Exception\LineParser $ex) {
				$ret[] = [
					'unknownFormat' => true,
					'raw' => $fareCalculation,
				];
			}
		}

		return $ret;
	}

	/**
	 * Do the actually extract of fare calculation
	 * @param String $fareCalculationRawData
	 * @param \App\Utils\BaseParser\Utils\Extractor\Single $extractor
	 * @throws \App\Utils\BaseParser\TextParser\Exception\LineParser
	 * @return array
	 */
	private function doExtract($fareCalculationRawData, \App\Utils\BaseParser\Utils\Extractor\Single $extractor)
	{

		$ret = [];

		$ret['sliceFmt'] = null;
		// this format is US-specific
		// @see: TD-5888: ignore the information after XF tax for tickets with destination in USA
		if (    $slices = $extractor->findAndStore(
				"/^(?P<slice_with_spaces>[ ]{2,4})?".
				"(?P<slice_with_CC_data>[A-Z]{2}[0-9]{8}.{82})?".
				"(?P<slice_with_segment_prices>.*)".
				"(?P<slice_with_ROE>END([\s]?ROE[\s]?[0-9\.]+)?( ZP[A-Z]+[0-9]*)?)".
				"(?P<slice_with_taxes>[\s]*([A-Z]{2,4}).*XF)".
				"(?P<slice_after_taxes>[A-Z]{3}[A-Z0-9\.]+)$/i",
			$fareCalculationRawData)) {
			$ret['sliceFmt'] = 'US';
		}
		// this is for the rest of the world, except US
		elseif( $slices = $extractor->findAndStore(
			"/^(?P<slice_with_spaces>[ ]{2,4})?".
			"(?P<slice_with_CC_data>[A-Z]{2}[0-9]{8}.{82})?".
			"(?P<slice_with_segment_prices>.*)".
			"(?P<slice_with_ROE>END([\s]?ROE[\s]?[0-9\.]+)?( ZP[A-Z]+[0-9]*)?)".
			"(?P<slice_with_taxes>[\s]*([A-Z]{2,4}).*)?$/i",
		$fareCalculationRawData)) {
			$ret['sliceFmt'] = 'default';
		}
		else {
			throw new \App\Utils\BaseParser\TextParser\Exception\LineParser(sprintf("Unknown format for the FareCalculation field"));
		}

		$ret['slice_with_CC_data']['unknownFormat'] = true;	// this gets overritten below
		if ($slices->slice_with_CC_data) {
			$ret['slice_with_CC_data'] = [
				'raw' => $slices->slice_with_CC_data,
			];
		}


		$ret['slice_with_segment_prices']['unknownFormat'] = true;	// this gets overritten below
		if ($slices->slice_with_segment_prices) {
			/*
			 * don't split, this format has only the itinerary
			 * format:
			 * 	MOW TK AYTM/IT
			 * */
			$xtrSegments = $extractor->find("/^"
				."(?P<slice1_airports>[^0-9]+)"
				."$/i", $slices->slice_with_segment_prices);
			if ($ret['slice_with_segment_prices']['unknownFormat']  && $xtrSegments->hasValue()) {
				$ret['slice_with_segment_prices'] = [];
				$ret['slice_with_segment_prices']['slice1_airports'] = $this->extractItineraryDataFrom($xtrSegments->slice1_airports);
			}

			/*
			 * don't split, this format has only one segment of the itinerary
			 * format:
			 * 	PPS PR MNL2668PHP2668
			 * */
			$xtrSegments = $extractor->find("/^"
				."(?P<slice1_airports>[^0-9]+)(?P<slice1_price>[0-9\.]+)"
				."(?P<currency>[A-Z]{3})(?P<total>[0-9\.]+)"
				."$/i", $slices->slice_with_segment_prices);

			if ($ret['slice_with_segment_prices']['unknownFormat'] && $xtrSegments->hasValue()) {
				$ret['slice_with_segment_prices'] = [];
				$ret['slice_with_segment_prices']['slice1_airports'] = 	$this->extractItineraryDataFrom($xtrSegments->slice1_airports);
				$ret['slice_with_segment_prices']['slice1_price'] = 	(float)$xtrSegments->slice1_price;
				$ret['slice_with_segment_prices']['currency'] = 		$xtrSegments->currency;
				$ret['slice_with_segment_prices']['total'] = 			(float)$xtrSegments->total;
			}

			/*
			 * split this in 3 slices, slice1 will contain the first price, slice2 the 2'nd price, slice3 the total price
			 * this is the most common/default case
			 * */
			$xtrSegments = $extractor->find("/^"
				."(?P<slice1_airports>[^0-9]+)(?P<slice1_price>[0-9\.]+)"
				."(?P<slice2_airports>[^0-9]+)(?P<slice2_price>[0-9\.]+)"
				."(?P<currency>[A-Z]{3})(?P<total>[0-9\.]+)"
				."$/i", $slices->slice_with_segment_prices);

			if ($ret['slice_with_segment_prices']['unknownFormat'] && $xtrSegments->hasValue()) {
				$ret['slice_with_segment_prices'] = [];
				$ret['slice_with_segment_prices']['slice1_airports'] = 	$this->extractItineraryDataFrom($xtrSegments->slice1_airports);
				$ret['slice_with_segment_prices']['slice1_price'] = 	(float)$xtrSegments->slice1_price;
				$ret['slice_with_segment_prices']['slice2_airports'] = 	$this->extractItineraryDataFrom($xtrSegments->slice2_airports);
				$ret['slice_with_segment_prices']['slice2_price'] = 	(float)$xtrSegments->slice2_price;
				$ret['slice_with_segment_prices']['currency'] = 		$xtrSegments->currency;
				$ret['slice_with_segment_prices']['total'] = 			(float)$xtrSegments->total;
			}

			/*
			 * unknown format....
			 * format:
			 * 	RUH SV CAI191.69SV MED106.82SV RUH44.32NUC342.83
			 * */
			$xtrSegments = $extractor->find("/^"
				."(?P<slice1_airports>.+)"
				."$/i", $slices->slice_with_segment_prices);
			if ($ret['slice_with_segment_prices']['unknownFormat']  && $xtrSegments->hasValue()) {
				$ret['slice_with_segment_prices'] = [];
				$ret['slice_with_segment_prices']['slice1_airports'] = $this->extractItineraryDataFrom($xtrSegments->slice1_airports);
			}
		}

		/**
		 * NUC is the abbreviation for Neutral Unit of Construction.
		 * 	It is a fictitious currency equivalent in value to the United States Dollar (USD).
		 * 	An industry-wide term that carriers and governments use for fare construction purposes and air fare calculations.
		 *
		 * ROE = IATA Rate of Exchange (ROE) for converting NUCs to local currency
		 */
		$ret['slice_with_ROE']['unknownFormat'] = true;	// this gets overritten below
		if ($slices->slice_with_ROE) {
			// this might contain the used NUC->currency currency rate
			$xtrROE = $extractor->find("/^END[\s]?"	// ROE = rate of exchange
				."(ROE[\s]?(?P<ROE>[0-9\.]+))?"
				."(ZP(?P<ZP>[A-Z]+[0-9]*))?"
				."$/i", $slices->slice_with_ROE);

			$ret['slice_with_ROE'] = [];

			if ($xtrROE->ROE) {
				$ret['slice_with_ROE']['ROE'] = (float)$xtrROE->ROE;
			}

			if ($xtrROE->ZP) {
				$ret['slice_with_ROE']['ZP'] = str_split($xtrROE->ZP, 3);	// this contains a list of passed airports
			}
		}

		$ret['slice_with_taxes'] = [];	// this gets overritten below
		if ($slices->slice_with_taxes) {
			$ret['slice_with_taxes']['unknownFormat'] = true;	// this gets overritten below

			$maxTaxes = 15;
			$regexp = "/^[\s]*"
				."(?P<hasPD>PD)?"
				.$extractor->getRegexpForRecursiveMatch("([\s]*(?P<PDtaxAmount{__REGEXP_ITERATION__}>([0-9\.]+|[\s]*EXEMPT[\s]*))(?P<PDtaxCode{__REGEXP_ITERATION__}>([A-Z]{2}|[A-Z][0-9])){__REGEXP_SEED__})?", $maxTaxes)
				."[\s]*"
				."(?P<hasXT>XT)?"
				.$extractor->getRegexpForRecursiveMatch("([\s]*(?P<XTtaxAmount{__REGEXP_ITERATION__}>([0-9\.]+|[\s]*EXEMPT[\s]*))(?P<XTtaxCode{__REGEXP_ITERATION__}>([A-Z]{2}|[A-Z][0-9])){__REGEXP_SEED__})?", $maxTaxes)
				."$/i";

				$xtrTaxes = $extractor->find($regexp, $slices->slice_with_taxes);

				if ($xtrTaxes->hasValue()) {
					$taxes = [];
					for ($i=0; $i<$maxTaxes; $i++) {
						if (!($xtrTaxes->get("PDtaxAmount{$i}") && $xtrTaxes->get("PDtaxCode{$i}"))) {
							break;
						}

						if ($xtrTaxes->get("PDtaxAmount{$i}")=='EXEMPT') {
							$taxes[]= [
								'amount' => null,
								'code' =>   $xtrTaxes->get("PDtaxCode{$i}"),
								'exempt' => true,
								'section' => 'PD',
							];
						}
						else {
							$taxes[]= [
								'amount' => (float)$xtrTaxes->get("PDtaxAmount{$i}"),
								'code' =>   $xtrTaxes->get("PDtaxCode{$i}"),
								'exempt' => false,
								'section' => 'PD',
							];
						}
					}

					for ($i=0; $i<$maxTaxes; $i++) {
						if (!($xtrTaxes->get("XTtaxAmount{$i}") && $xtrTaxes->get("XTtaxCode{$i}"))) {
							break;
						}

						if ($xtrTaxes->get("XTtaxAmount{$i}")=='EXEMPT') {
							$taxes[]= [
								'amount' => null,
								'code' =>   $xtrTaxes->get("XTtaxCode{$i}"),
								'exempt' => true,
								'section' => 'XT',
							];
						}
						else {
							$taxes[]= [
								'amount' => (float)$xtrTaxes->get("XTtaxAmount{$i}"),
								'code' =>   $xtrTaxes->get("XTtaxCode{$i}"),
								'exempt' => false,
								'section' => 'XT',
							];
						}
					}

					$totalGrouped = [];
					foreach ($taxes as $t) {
						$totalGrouped[$t['section']]+= $t['amount'];
					}

					$ret['slice_with_taxes'] = [
						'hasXT' => 	(bool)$xtrTaxes->hasXT,
						'hasPD' => 	(bool)$xtrTaxes->hasPD,
						'taxes' => 	$taxes,
						'total' => 	array_sum(array_map(function($t){return $t['amount']; }, $taxes)),
						'totalGrouped' => $totalGrouped,
					];
				}
		}

		// TODO: remove this, after Oct 28, 2020, by Lucian Sirbu
		// 		if ($ret['slice_with_taxes']['unknownFormat']) {
		// 			debug($slices);
		// 			throw new \App\Utils\BaseParser\TextParser\Exception\LineParser(sprintf("Unknown format for the IU6FC12 field"));
		// 		}

		/// SLICE 4 - AFTER XF SLICE - save all airport codes/amounts that come after XF tax (US only)

		$ret['slice_after_taxes']['unknownFormat'] = true;	// this gets overritten below
		if ($slices->slice_after_taxes) {

			$maxIterations = 10;
			$regexp = "/^"
				.$extractor->getRegexpForRecursiveMatch("(((?P<unknownAirport{__REGEXP_ITERATION__}>([A-Z]{3}))(?P<unknownAmmount{__REGEXP_ITERATION__}>[0-9\.]+)){__REGEXP_SEED__})?", $maxIterations)
				."$/i";

				$xtr = $extractor->find($regexp, $slices->slice_after_taxes);

				if ($xtr->hasValue()) {
					$rows = [];
					for ($i=0; $i<$maxIterations; $i++) {
						if (!($xtr->get("unknownAirport{$i}") && $xtr->get("unknownAmmount{$i}"))) {
							break;
						}

						$rows[]= [
							'unknownAirport' => 	$xtr->get("unknownAirport{$i}"),
							'unknownAmmount' =>   	(float)$xtr->get("unknownAmmount{$i}"),
							];
					}

					$ret['slice_after_taxes'] = [
						"unknownData"	=>	$rows,
					];
				}

		}

		return $ret;

	}

	/**
	 * Returns itinerary segments
	 * @see https://servicehub.amadeus.com/c/portal/view-solution/774026/en_US/fare-calculation-symbols-and-explanations
	 * 	saved as fare-calculation-symbols-and-explanations.pdf
	 * @param String $sliceStr
	 * @return array
	 */
	private function extractItineraryDataFrom($sliceStr)
	{
		$ret = [];
		$extractor = new \App\Utils\BaseParser\Utils\Extractor\Single();
		$sliceStr = trim($sliceStr);

		// M/IT stands for "Inclusive tour", M/BT stands for "Bulk tour fares". M/ means that no price should be displayed (probably handled as unknown02 in here)
		$overview = [
			'_type' => 'overview',
			'_sliceStr' => 	$sliceStr,
			'containsBT' => $extractor->contains("/M\/(BT|IT)/i", $sliceStr),
		];

		/*
		 * unknown01 seem to only be set for 2'nd segments, that might come as a param to this function
		 * */
		$slice = $extractor->find("/^(?P<stopOver>X\/)?(?P<unknown01>\/-)?(?P<airport>[A-Z]{3})(( )(?P<classCode>[A-Z]{1})( |$))?[\s]*/i", $sliceStr);
		$sliceStr = substr($sliceStr, $slice->getMatchedLength());

		$ret[] = [
			'airport' => $slice->airport,
			'stopOver' => 	(bool)$slice->stopOver,
		];

		$loops = 100;
		while ($sliceStr && $loops--) {
			$extracted = false;

			if (!$extracted) {
				/**
				 * Specific format
				 * @see JIRA:TD-9064, EC/SpeedTravel
				 * Ex:
				 * 	KQ JUB637.50
				 */
				$slice = $extractor->find("/^"
					."(?P<supplier>[A-Z]{2}) "
					."(?P<airport>[A-Z]{3})"
					."(?P<segmentAmount>[0-9\.]+)"
					."/i", $sliceStr);
				if ($slice->hasValue()) {
					$extracted = true;
					$ret[] = [
						'_type' => 		'segment:specific:01',
						'_sliceStr' => 	substr($sliceStr, 0, $slice->getMatchedLength()),
						'airport' => 	$slice->airport,
						'supplier' => 	$slice->supplier,
						'stopOver' => 	false,
						'classCode' => 	$slice->classCode,
						'segmentAmount' => 	$slice->getFloat('segmentAmount'),
					];

					$sliceStr = substr($sliceStr, $slice->getMatchedLength());
				}
			}



			if (!$extracted) {
				$slice = $extractor->find("/^"
					."(?P<supplier>[A-Z]{2})"
					." "
					."(?P<stopOver>X\/)?"
					."(?P<unknown_E>E\/)?"
					."(?P<airport>[A-Z]{3})"
					."((?P<unknown_01>[0-9\.]+))?"
					."( (?P<classCode>[A-Z]{1})(?P<segmentAmount>[0-9\.]+)?)?"
					."( (?P<surcharge>[0-9\.]+))?"
					."(( )?(?P<negotiatedFare>M\/(BT|IT)))?"
					."( |$)"
					."/i", $sliceStr);

				if ($slice->hasValue()) {
					$extracted = true;
					$ret[] = [
						'_type' => 		'default:v01',
						'_sliceStr' => 	substr($sliceStr, 0, $slice->getMatchedLength()),
						'airport' => 	$slice->airport,
						'supplier' => 	$slice->supplier,
						'stopOver' => 	(bool)$slice->stopOver,
						'classCode' => 	$slice->classCode,
						'negotiatedFare' =>	$slice->negotiatedFare,
						'segmentAmount' => 	$slice->getFloat('segmentAmount'),
						'surcharge' => 		$slice->getFloat('surcharge'),
					];

					$sliceStr = substr($sliceStr, $slice->getMatchedLength());
				}
			}


			if (!$extracted) {
				/**
				 * Unknown format
				 * Ex:
				 * 	Q1234.56
				 */
				$slice = $extractor->find("/^"
					."Q"
					."(?P<surcharge>[0-9\.]+)"
					."/i", $sliceStr);
				if ($slice->hasValue()) {
					$extracted = true;
					$ret[] = [
						'_type' => 			'unknown:v02',
						'_sliceStr' => 		substr($sliceStr, 0, $slice->getMatchedLength()),
						'surcharge' => 		$slice->getFloat('surcharge'),
					];

					$sliceStr = substr($sliceStr, $slice->getMatchedLength());
				}
			}

			if (!$extracted) {
				/**
				 * Unknown format
				 * Ex:
				 * 	/-WAS
				 */
				$slice = $extractor->find("/^"
					."\/-"
					."(?P<airport>[A-Z]{3})"
					."( |$)/i", $sliceStr);
				if ($slice->hasValue()) {
					$extracted = true;
					$ret[] = [
						'_type' => 		'unknown:v01',
						'_sliceStr' => 	substr($sliceStr, 0, $slice->getMatchedLength()),
						'airport' => 	$slice->airport,
					];

					$sliceStr = substr($sliceStr, $slice->getMatchedLength());
				}
			}



			if (!$extracted) {
				/**
				 * Ex:
				 * 	SAR461.00
				 */
				$slice = $extractor->find("/^(?P<currency>[A-Z]{3})(?P<totalAmount>[0-9\.]+)$/i", $sliceStr);
				if ($slice->hasValue()) {
					$extracted = true;
					$ret[] = [
						'_type' => 			'total',
						'_sliceStr' => 		substr($sliceStr, 0, $slice->getMatchedLength()),
						'totalAmount' => 	$slice->getFloat('totalAmount'),
						'currency' => 		$slice->currency,
					];

					$sliceStr = substr($sliceStr, $slice->getMatchedLength());
				}
			}

			if (!$extracted) {
				break;
			}
		}

		if ($sliceStr) {
			$ret[] = [
				'_type' => 			'unknown:v99',
				'_sliceStr' => 		$sliceStr,
			];
			# debug_mark(); var_dump($sliceStr); debug_unmark();
			# throw new \App\Utils\BaseParser\TextParser\Exception\LineParser(sprintf("Unknown format for the IU6FC12 field, cannot interpret the itinerary. Remaining text: %s", $sliceStr));
		}

		$ret[]= $overview;

		return $ret;
	}

}
