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

abstract class ProcessorAbstract
{
	/**
	 * Local object cache, used to avoid duplicating various entities
	 * @var array
	 */
	protected $localObjectCache = [];

	/**
	 * Get the applyMode that should be used when inserting the entity corresponding to this order
	 * @return string: merge or overwrite
	 */
	abstract public function getConvertedApplyMode();

	/**
	 * Get the finalized converted order
	 * @throws \Exception
	 * @return \App\ImportedOrder\Order
	 */
	abstract public function getConvertedOrder();


	/**
	 * Generate the TINA service id
	 * @param \App\Entity\GDSServiceAbstract $service
	 * @return string
	 */
	abstract protected function generateTINAServiceIdSystem(\App\Entity\GDSServiceAbstract $service);


	/**
	 * Create unique cache key
	 * @param String $key
	 * @param array $references
	 * @return String
	 */
	protected function createCacheKey($key, Array $references)
	{
		return sprintf("%s-%s", $key, implode("-", $references));
	}

	protected function getCacheSimilarKeys($keys)
	{
		$object = null;
		foreach ($keys as $k) {
			if (array_key_exists($k, $this->localObjectCache)) {
				$object = $this->localObjectCache[$k];
				break;
			}
		}

		return $object;
	}

	protected function setCacheSimilarKeys($keys, $object)
	{
		foreach ($keys as $k) {
			$this->localObjectCache[$k] = $object;
		}
	}

	protected function cloneServiceOnOrder(\App\ImportedOrder\Service $sourceService, \App\ImportedOrder\Order $order)
	{
		$destinationService = clone $sourceService;

/*		$destinationService->operation = 		$sourceService->operation;
		$destinationService->idSystem = 		$sourceService->idSystem;
		$destinationService->systemStatus = 	$sourceService->systemStatus;

		// add passenger to service and order
		$passengers = (array)$sourceService->passengers;

		foreach ($passengers as $passenger) {
			// add passenger to service
			if (!$destinationService->passengerExists($passenger)) {
				$destinationService->addPassenger($passenger);
			}
			// add passenger to order
			if (!$order->passengerExists($passenger)) {
				$order->addPassenger($passenger);
			}
		}

		// set chosen offer
		$chosenOffer = $sourceService->chosenOffer;
		if ($chosenOffer) {
			$destinationService->chosenOffer = $chosenOffer;
			$destinationService->chosenOfferConfirmed = true;
		}

		// get product type
		$sourceProductTypes = (array)$sourceService->productTypes;
		foreach ($sourceProductTypes as $sourceProductType) {
			$destinationService->addProductType($sourceProductType->code, $sourceProductType->name);
		}

		// set departure and destination object
		$sourceDeparturePlace = $sourceService->departurePlace;
		$destinationService->departurePlace = $sourceDeparturePlace;

		$sourceDestinationPlace = $sourceService->destinationPlace;
		$destinationService->destinationPlace = $sourceDestinationPlace;

		$sourceDepartureTime = $sourceService->departureDateTime;
		$destinationService->departureDateTime = $sourceDepartureTime;

		$sourceDestinationTime = $sourceService->destinationDateTime;
		$destinationService->destinationDateTime = $sourceDestinationTime;

		// set service type
		$sourceServiceType = $sourceService->serviceType;
		$destinationService->serviceType = $sourceServiceType;
*/
#		debug($sourceService);

#		debug($destinationService);
#		printf("%s: %s", __FILE__, __LINE__); die();

		$order->addService($destinationService);

		return $destinationService;

	}


	/**
	 * Determine if agencyCommission is true/false depending if the credit card enlisted in the gds is set in the AgencyCreditCards entity
	 * @param array $creditCardList
	 * @return boolean
	 */
	protected function determineIgnoreSupplierOnDocumentsValueForCC(Array $creditCardList)
	{
		foreach ($creditCardList as $CCRowDetails) {
			if ($CCRowDetails->ccNumber) {
				$matcher = new \App\Utils\CreditCard\MatchCardFromGDS($CCRowDetails->ccNumber, ($CCRowDetails->ccExpiryDate?$CCRowDetails->ccExpiryDate:null)); // the parser wrongly assumes the expiry date on 01 of the month, in reality it's the last day of the month
				if ($matcher->matchByAgencyCreditCards()) {
					return false;
				}
			}
		}

		return true;
	}


	/**
	 * Check if a service already exists in importedOrders
	 */
	protected function isServiceAlreadyImportedInImportedServices(\App\Entity\GDSServiceAbstract $entity)
	{
		$IOService = \Entity\ImportedService::getDetailsByServiceExtSys(
			$this->generateTINAServiceIdSystem($entity),
			\Entity\ExtResSystem::getSystemIdByCode($entity::systemCode),
			false,
			'-2 years'
		);
		if ($IOService && $IOService['systemStatus']!='deleted') {
			return true;
		}
		return false;
	}

	/**
	 * Generate TINA fareUsed equavalent for fareUsedTaxCodes
	 *
	 * @param array $refundTaxComponents
	 * @param array $issuetaxComponents
	 * @return array
	 */
	protected function generateTinaSericeRefundDetailsFareUsedTaxes(array $refundTaxComponents, array $issuetaxComponents)
	{
		// Step 1: In order to fill in fareUsedTaxCode, we need to first group taxes by code, both in the issue and in the refund service

		// Step 1.1: grouped taxes in the initial issue transaction
		$groupedTaxesInEntRefunded = [];
		foreach ($issuetaxComponents as $comp) {
			if (!$groupedTaxesInEntRefunded[$comp->code]) {
				$groupedTaxesInEntRefunded[$comp->code] = [
					'amount' => 	$comp->amount,
					'VAT' => 		$comp->VAT,
					'currency' => 	$comp->currency,
					'code' => 		$comp->code,
				];
			}
			else {
				if ($comp->currency != $groupedTaxesInEntRefunded[$comp->code]['currency']) {
					throw new \App\Utils\BaseParser\Structure\CompatibilityWithImportedOrders\InvalidStructure(sprintf("Trying to group tax code %s but it has a different currencies (%s vs %s)", $comp->code, $comp->currency, $groupedTaxesInEntRefunded[$comp->code]['currency']));
				}

				$groupedTaxesInEntRefunded[$comp->code]['amount']+= $comp->amount;
				$groupedTaxesInEntRefunded[$comp->code]['VAT']+= 	$comp->VAT;
			}
		}

		// Step 1.2: grouped taxes in the initial issue transaction
		$groupedTaxesInEnt = [];
		foreach ($refundTaxComponents as $comp) {
			if (!$groupedTaxesInEnt[$comp->code]) {
				$groupedTaxesInEnt[$comp->code] = [
					'amount' => 	$comp->amount,
					'VAT' => 		$comp->VAT,
					'currency' => 	$comp->currency,
					'code' => 		$comp->code,
				];
			}
			else {
				if ($comp->currency != $groupedTaxesInEnt[$comp->code]['currency']) {
					throw new \App\Utils\BaseParser\Structure\CompatibilityWithImportedOrders\InvalidStructure(sprintf("Trying to group tax code %s but it has a different currencies (%s vs %s)", $comp->code, $comp->currency, $groupedTaxesInEnt[$comp->code]['currency']));
				}

				$groupedTaxesInEnt[$comp->code]['amount']+= $comp->amount;
				$groupedTaxesInEnt[$comp->code]['VAT']+= 	$comp->VAT;
			}
		}


		// Step 2: now calculate the difference between both arrays
		$fareUsedGroupedTaxes = [];
		foreach ($groupedTaxesInEntRefunded as $compArr) {
			$fareUsedGroupedTaxes[$compArr['code']] = [];
			$fareUsedGroupedTaxes[$compArr['code']]['currency'] = 	$compArr['currency'];
			$fareUsedGroupedTaxes[$compArr['code']]['code'] = 		$compArr['code'];
			$fareUsedGroupedTaxes[$compArr['code']]['amount'] = 	(
				$compArr['amount']!==null || $groupedTaxesInEnt[$compArr['code']]['amount']!==null
				?($compArr['amount'] + $groupedTaxesInEnt[$compArr['code']]['amount'])
				:null
			);
			$fareUsedGroupedTaxes[$compArr['code']]['VAT'] = 		(
				 ($compArr['VAT']!==null || $groupedTaxesInEnt[$compArr['code']]['VAT']!==null)
				?($compArr['VAT'] + $groupedTaxesInEnt[$compArr['code']]['VAT'])
				:null
			);
		}

		// Step 2.1: add back remaining taxes in $ent
		foreach ($groupedTaxesInEnt as $compArr) {
			if (!$fareUsedGroupedTaxes[$compArr['code']]) {
				$fareUsedGroupedTaxes[$compArr['code']] = [];
				$fareUsedGroupedTaxes[$compArr['code']]['currency'] = 	$compArr['currency'];
				$fareUsedGroupedTaxes[$compArr['code']]['code'] = 		$compArr['code'];
				$fareUsedGroupedTaxes[$compArr['code']]['amount'] = 	$compArr['amount'];
				$fareUsedGroupedTaxes[$compArr['code']]['VAT'] = 		$compArr['VAT'];
			}
		}

		return $fareUsedGroupedTaxes;
	}

	/**
	 * Fill in the credit card details
	 *
	 * @param \App\ImportedOrder\Offer $offer
	 * @param \App\Utils\BaseParser\Structure\StructureParser\Types\Price $priceData
	 * @param array $options ['onEmptyCCAmounts' => 'ignore/loadTotal']
	 * @return array
	 */
	protected function generateOfferCreditCardDetails(\App\ImportedOrder\Offer $offer, \App\Utils\BaseParser\Structure\StructureParser\Types\Price $priceData, array $options = ['onEmptyCCAmounts'=>'ignore'])
	{
		$extraRemarks = [];
		$creditCard = new \App\ImportedOrder\CreditCard();
		$fopCCs = $priceData->formOfPayment->getPaymentByType('CC');
		$clientCCs = [];
		$agencyCCs = [];
		// first, separate agency & client CC's
		foreach ($fopCCs as $CC) {
			/* @var $CC \App\Utils\BaseParser\Structure\StructureParser\Types\PriceFormOfPaymentComponent */
			if ($CC->ccNumber) {
				$matcher = new \App\Utils\CreditCard\MatchCardFromGDS($CC->ccNumber, ($CC->ccExpiryDate?$CC->ccExpiryDate:null));
				if ($matcher->matchByAgencyCreditCards()) {
					$agencyCCs[]= $CC;
				}
				else {
					$clientCCs[]= $CC;
				}
			}
			else {
				$clientCCs[]= $CC;
			}
		}

		/**
		 * CC picking algorithm (A = agency, C = client):
		 * 	if A  use first CC, amount = first(CC)
		 * 	if C  use first CC, amount = first(CC)
		 * 	if AC use client CC, amount = max(client CC)
		 * 	if AA use max(agency CC), amount = max(agency CC)
		 * 	if CC use max(client CC), amount = sum(CC)
		 */

		if ($clientCCs) {
			$pickedCCs = $clientCCs;
		}
		else {	// if no client CCs, take the max agency CC
			$CCAmounts = [];
			$CCCurrencies = [];
			$CCLargestPay = null;
			/* @var $CCLargestPay \App\Utils\BaseParser\Structure\StructureParser\Types\PriceFormOfPaymentComponent */
			foreach ($agencyCCs as $CC) {
				/* @var $CC \App\Utils\BaseParser\Structure\StructureParser\Types\PriceFormOfPaymentComponent */
				if ($CC->amount > ($CCAmounts?max($CCAmounts):0)) {
					$CCLargestPay = $CC;
				}

				$CCAmounts[]= $CC->amount;
				$CCCurrencies[]= $CC->currency;
			}

			$pickedCCs = [$CCLargestPay, ];	// use only the largest CC, ignore the others, because the algorithm for AA is different
		}

		// filter & sum as needed according to the above algorithm
		$CCAmounts = [];
		$CCCurrencies = [];
		$CCLargestPay = null;
		foreach ($pickedCCs as $CC) {
			/* @var $CC \App\Utils\BaseParser\Structure\StructureParser\Types\PriceFormOfPaymentComponent */
			if (!$CCAmounts || ($CC->amount > max($CCAmounts))) {
				$CCLargestPay = $CC;
			}

			$CCAmounts[]= $CC->amount;
			$CCCurrencies[]= $CC->currency;
		}

		if ($CCAmounts && count(array_unique($CCCurrencies))==1) {
			$offer->creditCardPaymentValue = 	array_sum($CCAmounts);
			$offer->creditCardPaymentCurrency =	$CCCurrencies[0];
		}
		if ($options['onEmptyCCAmounts']=='loadTotal' && !$offer->creditCardPaymentValue && !$offer->creditCardPaymentCurrency) {
			if ($priceData->total && $priceData->totalCurrency) {
				$offer->creditCardPaymentValue 		= $priceData->total;
				$offer->creditCardPaymentCurrency 	= $priceData->totalCurrency;
			}
		}

		// use $extraRemarks to store remarks that should get appended once the service starts being populated. At this moment it's mostly empty
		if (count($pickedCCs)>1) {
			$extraRemarks[]= sprintf("MultipleCards:%s", implode("/", array_map(function(\App\Sabre\StructureParser\Types\PriceFormOfPaymentComponent $CC) {
				return sprintf("%s%s", $CC->amount, $CC->currency);
			}, $pickedCCs)));
		}

		$CC = $CCLargestPay;
		/* @var $CC \App\Utils\BaseParser\Structure\StructureParser\Types\PriceFormOfPaymentComponent */
		$creditCard->company = 		$CC->ccCompanyIndicator;
		$creditCard->expireDate= 	$CC->ccExpiryDate;
		$creditCard->number = 		$CC->ccNumber;
		$creditCard->approvalCode=  $CC->ccApprovalCode;
		$offer->creditCard = $creditCard;

		return [$offer, $extraRemarks];
	}

	protected function getIOServiceEntity(string $IOServiceId, \App\Entity\GDSServiceAbstract $entity)
	{
		$IOService = \Entity\ImportedService::getDetailsByServiceExtSys(
			$IOServiceId,
			\Entity\ExtResSystem::getSystemIdByCode($entity::systemCode),
			false,
			'-2 years'
		);
		if ($IOService && $IOService['systemStatus']!='deleted') {
			return \Entity\ImportedService::select($IOService['id']);
		}
		return null;
	}

	/**
	 * For reissued tickets, without any price exchange, copy the form of payment from the original ticket to ensure consistent payment information in IO
	 * @param \App\Entity\GDSServiceAbstract $ent
	 * @param \App\ImportedOrder\Offer $offer
	 * @param null|\Entity\ImportedService $reissuedIOService
	 * @return \App\ImportedOrder\Offer
	 */
	protected function specialHandlingForFormOfPaymentForReissues(\App\Entity\GDSServiceAbstract $ent, \App\ImportedOrder\Offer $offer, ?\Entity\ImportedService $reissuedIOService=null)
	{
		if ($ent->transactionStatus=='reissue' && $offer->servicePriceTotal==0 && $offer->taxTotal==0 && !$offer->formOfPayment) {
			// Get the original service entity
			$reissuedIOServiceUsed = ($reissuedIOService?:$this->getIOServiceEntity($this->generateTINAServiceIdSystem($ent->reissuedService->nonConjunction), $ent->reissuedService->nonConjunction));
			if ($reissuedIOServiceUsed->object && $reissuedIOServiceUsed->object->chosenOffer && $reissuedIOServiceUsed->object->chosenOffer->formOfPayment) {
				$reissuedOffer = $reissuedIOServiceUsed->object->chosenOffer;
				// Copy the form of payment from the original ticket
				$offer->formOfPayment = $reissuedOffer->formOfPayment;

				// If the original had a credit card, copy that too
				if ($reissuedOffer->creditCard) {
					$offer->creditCard = 				clone $reissuedOffer->creditCard;
				}
			}
		}

		return $offer;
	}

	/**
	 * Load ticket numbers from documents->docNumber & documents->conjunctionDocNumbers
	 * @param \App\Entity\GDSServiceAbstract $ent
	 * @param \App\ImportedOrder\Service $serv
	 * @param \App\ImportedOrder\Offer $offer
	 * @return \App\ImportedOrder\Offer
	 */
	protected function loadTicketNumbersDefault(\App\Entity\GDSServiceAbstract $ent, \App\ImportedOrder\Service $serv, \App\ImportedOrder\Offer $offer)
	{
		$paxs = (array)$serv->passengers;

		if ($paxs) {
			$conjunctions = [];
			if ($ent->transactionData->documents->conjunctionDocNumbers) {
				foreach ($ent->transactionData->documents->conjunctionDocNumbers as $doc) {
					if ($doc!=$ent->docNumber) {
						$conjunctions[] = $doc;
					}
				}
			}

			$offer->addTicket($paxs[0], $ent->transactionData->documents->docNumber, $conjunctions);
		}

		return $offer;
	}

	/**
	 * Load ticket numbers from remarks
	 * @param \App\Entity\GDSServiceAbstract $ent
	 * @param \App\ImportedOrder\Service $serv
	 * @param \App\ImportedOrder\Offer $offer
	 * @return \App\ImportedOrder\Offer
	 */
	protected function loadTicketNumbersFromRemarks(\App\Entity\GDSServiceAbstract $ent, \App\ImportedOrder\Service $serv, \App\ImportedOrder\Offer $offer)
	{
		$processedRemarks = $ent->transactionData->remarks->getProcessedRemarks();

		if ($processedRemarks['TICKET_NUMBER']) {
			$offer->confirmationNumber = $processedRemarks['TICKET_NUMBER'];
			$offer->ticketDate = $ent->transactionData->documents->issueDate;

			$paxs = $serv->passengers;
			if ($paxs) {
				$offer->addTicket($paxs[0], $processedRemarks['TICKET_NUMBER'], []);
			}
		}

		return $offer;
	}

}
