<?php
namespace App\Galileo\CompatibilityWithImportedOrders;

class Processor extends \App\Utils\BaseParser\Structure\CompatibilityWithImportedOrders\ProcessorAbstract
{
	/**
	 * Internal list of Galileo services
	 * @var array
	 */
	protected $galileoServices = [];

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

	/**
	 * Initialize the convertor
	 * @param Array:\Entity\GalileoService $services
	 */
	public function __construct($services)
	{
		foreach ($services as $service) {
			/* @var $service \Entity\GalileoService */
			$this->galileoServices[] = $service;
		}
	}

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

	/**
	 * Get the finalized converted order
	 * @throws \Exception
	 * @return \App\ImportedOrder\Order
	 */
	public function getConvertedOrder()
	{
		if (!$this->galileoServices) {
			throw new \Exception("You didn't specify any service for conversion");
		}

		// $firstService is the first service in the whole PNR
		$firstService = $this->galileoServices[0]->PNRRootService;

		$ord = new \App\ImportedOrder\Order();
		$ord->date = 				($firstService->transactionData->documents->docDate?:$firstService->transactionData->documents->issueDate);
		$ord->externalSystemCode = 	"galileo";

		// this is a combination of the first seen RECLOC for this PNR and the docDate, so it could handle SPLITS gracefully (where docDate & RECLOC can change)
		$ord->idSystem = 			$firstService->transactionData->documents->docDate ."-". $firstService->transactionData->documents->RECLOC;

		// add the list of services and their associated offers
		foreach ($this->galileoServices as $key=>$ent) {
			if (!$ent->recordExists()) {
				throw new \Exception("An invalid entity was passed to ".__CLASS__.". Entity #{$key} doesn't exist in the DB");
			}

			$ignoreThisService = false;

			// ignore service surface
			if (in_array($ent->transactionData->getType(), ['surface',]) || in_array($ent->transactionData->documents->docType, ['surface',]) ) {
				$ignoreThisService = true;
			}

			if (in_array('isConjunction', $ent->attributes)) {
				// ignore conjunctions
				$ignoreThisService = true;
			}

			if (in_array($ent->transactionData->getType(), ['car', 'hotel', ]) && $this->isServiceAlreadyImportedInImportedServices($ent)) {
				// ignore already imported car/hotel services
				$ignoreThisService = true;
			}

			if (!$ignoreThisService) {
				$ord->addService($this->createTINAService($ent, $ord));
			}
		}

		// pick the first passenger as the order owner
		$passengers = $ord->passengers;
		if ($passengers) {
			$ord->ownerPassenger = reset($passengers);
		}

		$corporates = $ord->corporates;
		if ($corporates) {
			$ord->ownerCorporate = reset($corporates);
		}

		return $ord;
	}

	/**
	 * Generate the TINA service id
	 * @param \App\Entity\GDSServiceAbstract $galileoService
	 * @return string
	 */
	protected function generateTINAServiceIdSystem(\App\Entity\GDSServiceAbstract $galileoService)
	{
		$ret = null;
		if (!$ret && $galileoService->transactionData->documents->docNumber && in_array($galileoService->serviceCode, ['ticketing', 'NDCTicketing', 'LCCTicketing', 'EMD', 'NDCEMD', 'MCO', 'car', ])) {
			$ret = $galileoService->transactionData->documents->RECLOC . "-" . $galileoService->transactionData->documents->docNumber;
		}

		if (!$ret) {
			$ret = $galileoService->transactionData->documents->transactionCode;
		}

		return $ret;
	}

	/**
	 * Internal, create a user
	 * @param string $idSystem
	 * @return \App\ImportedOrder\User
	 */
	protected function createUser($idSystem)
	{
		$user = new \App\ImportedOrder\User();
		$user->idSystem = $idSystem;
		return $user;
	}

	/**
	 * Internal, create an office
	 * @param string $idSystem
	 * @return \App\ImportedOrder\Office
	 */
	protected function createOffice($idSystem, $iataCode)
	{
		$office = new \App\ImportedOrder\Office();
		$office->idSystem = $idSystem;
		$office->iataCode = $iataCode;
		return $office;
	}

	/**
	 * Internal, generate a passenger type
	 * @param string $codeSystem
	 * @return \App\ImportedOrder\Passenger_Type
	 */
	protected function generatePassengerType($codeSystem)
	{
		$paxType = new \App\ImportedOrder\Passenger_Type();
		$paxType->codeSystem = $codeSystem;
		return $paxType;
	}

	/**
	 * Internal, generate a passenger title
	 * @param string $codeSystem
	 * @return \App\ImportedOrder\Passenger_Title
	 */
	protected function generatePassengerTitle($codeSystem)
	{
		$paxTitle = new \App\ImportedOrder\Passenger_Title();
		$paxTitle->codeSystem = $codeSystem;
		return $paxTitle;
	}

	/**
	 * Internal, generate a station
	 * @return \App\ImportedOrder\Station
	 */
	protected function createStation($code3, $names)
	{
		$station = 	new \App\ImportedOrder\Station();
		if ($code3) {
			$station->code3 = $code3;
		}

		if (!$code3 && $names) {
			$station->name = implode('/', $names);
		}

		return $station;
	}

	/**
	 * Internal, generate a city
	 * @return \App\ImportedOrder\City
	 */
	protected function createCity($idTina, $name, $code3=null)
	{
		$city = new \App\ImportedOrder\City();

		if ($idTina) {
			$city->idTina = $idTina;
		}

		if ($code3) {
			$city->code3 = 	$code3;
		}

		if ($name) {
			$city->name = 	$name;
		}
		return $city;
	}

	/**
	 * Internal, generate a country
	 * @return \App\ImportedOrder\Country
	 */
	protected function createCountry($code2, $name)
	{
		$country = 	new \App\ImportedOrder\Country();
		$country->code2 = 	$code2;
		$country->name = 	$name;
		return $country;
	}

	/**
	 * Internal, generate a serviceType
	 * @return \App\ImportedOrder\ServiceType
	 */
	protected function generateTINAServiceType($codeSystem)
	{
		$st = new \App\ImportedOrder\ServiceType();
		$st->codeSystem = $codeSystem;
		$st->name = $codeSystem;
		return $st;
	}

	/**
	 * Internal, cached, generate a supplier
	 * @param string $name
	 * @param array $codes
	 * @return \App\ImportedOrder\Supplier
	 */
	protected function createSupplier($idSystem, ?string $name=null, ?array $codes=null)
	{
		$keys = [
			($idSystem?sprintf("%s:%s/idSystem", __METHOD__, $idSystem):null),
			($name?sprintf("%s:%s/name", __METHOD__, $name):null),
		];
		foreach ($codes as $k=>$v) {
			if ($v) {
				$keys[]= sprintf("%s:%s:%s/codes", __METHOD__, $k, $v);
			}
		}
		$keys = array_filter($keys);

		$supplier = $this->getCacheSimilarKeys($keys);

		if ($idSystem || $name || ($codes && array_filter($codes))) {
			if (!$supplier) {
				$supplier = new \App\ImportedOrder\Supplier();
			}

			if ($idSystem) {
				$supplier->idSystem = $idSystem;
			}
			$supplier->name = (strlen((string)$supplier->name)>strlen((string)$name)?$supplier->name:$name);

			if ($codes) {
				foreach(array_filter($codes) as $code=>$value) {
					$supplier->addCode($code, $value);
				}
			}

			$this->setCacheSimilarKeys($keys, $supplier);
		}
		else {
			$this->setCacheSimilarKeys($keys, null);
		}

		return $supplier;
	}


	/**
	 * Internal, cached, generate a supplier product
	 * @return \App\ImportedOrder\SupplierProduct
	 */
	protected function createSupplierProduct($idSystem, $name, $chainCode, $segment)
	{
		$key = __METHOD__.$idSystem;

		if (array_key_exists($key, $this->localObjectCache)) {
			return $this->localObjectCache[$key];
		}

		if ($idSystem || $name || $chainCode) {
			$sp = new \App\ImportedOrder\SupplierProduct();
			$sp->idSystem = $idSystem;
			$sp->name = $name;
			$sp->groupName = $chainCode;

			if ($segment) {
				$place = 	new \App\ImportedOrder\Place();
				$country = 	$this->createCountry(	$segment->countryCode, $segment->suggestions->country??null);
				$city = 	$this->createCity(		$segment->suggestions->city??null, $segment->cityId);
				$station = 	$this->createStation(	$segment->airportCode, $segment->suggestions->airport??null);

				if ($segment->suggestions->trainStation??null) {
					$station->name = 		implode("/", (array)($segment->suggestions->trainStation??null));
					$station->idSystem = 	$station->name;
				}

				$city->country = $country;
				$station->city = $city;
				$place->station = $station;
				$sp->place = $place;
			}

			$this->localObjectCache[$key] = $sp;
		}
		else {
			$this->localObjectCache[$key] = null;
		}

		return $this->localObjectCache[$key];
	}

	/**
	 * Internal, cached, generate a passenger
	 * @param \App\Galileo\StructureParser\Types\Passenger $galileoPassenger
	 * @param \App\ImportedOrder\Order $ord
	 * @return \App\ImportedOrder\Passenger
	 */
	protected function createPassenger(\App\Galileo\StructureParser\Types\Passenger $galileoPassenger, \App\ImportedOrder\Order $ord)
	{
		$firstName = ($galileoPassenger->firstName?:$galileoPassenger->lastName);
		$lastName = ($galileoPassenger->lastName?:$galileoPassenger->firstName);
		$key = __METHOD__.implode(",", [$galileoPassenger->title, $galileoPassenger->type, $lastName, $firstName, $galileoPassenger->dateOfBirth]);

		if (array_key_exists($key, $this->localObjectCache)) {
			return $this->localObjectCache[$key];
		}

		$pax = new \App\ImportedOrder\Passenger();
		$pax->type = $this->generatePassengerType($galileoPassenger->type);
		$pax->title = $this->generatePassengerTitle($galileoPassenger->title);
		$pax->firstNameStandard = 	$firstName;
		$pax->lastNameStandard = 	$lastName;
		$pax->birthday = 			$galileoPassenger->dateOfBirth;

		if ($galileoPassenger->emails) {
			foreach ($galileoPassenger->emails as $em) {
				if ($em->isDefault) {
					$pax->email = 			$em->email;
					break;
				}
			}
		}

		if ($galileoPassenger->telephoneNumbers) {
		    foreach ($galileoPassenger->telephoneNumbers as $tel) {
		        if ($tel->isDefault) {
		            $pax->telephone = 		$tel->number;
		            break;
		        }
		    }
		}

		if ($galileoPassenger->companyId || $galileoPassenger->company) {
			$corp = $this->createCorporate($galileoPassenger->companyId, $galileoPassenger->company);

			if ($corp) {
				$pax->corporate = $corp;

				if (!$ord->corporateExists($corp)) {
					$ord->addCorporate($corp);
				}
			}
		}

		$pax->idSystem = sprintf("c:%s %s/%s/%s/%s", $firstName, $lastName, $galileoPassenger->type, $galileoPassenger->title, $galileoPassenger->dateOfBirth);

		$this->localObjectCache[$key] = $pax;

		return $this->localObjectCache[$key];
	}

/**
	 * Internal, generate a corporate
	 * @return \App\ImportedOrder\Corporate|NULL
	 */
	protected function createCorporate($idSystem, $name)
	{
		$corporate = null;

		if ($idSystem || $name) {
			$key1 = sprintf("%s:%s/idSystem", __METHOD__, $idSystem);
			if (!$corporate && array_key_exists($key1, $this->localObjectCache)) {
				$corporate = $this->localObjectCache[$key1];
			}

			if (!$corporate) {
				$corporate = new \App\ImportedOrder\Corporate();
			}

			if ($idSystem) {
				$corporate->idSystem = $idSystem;
			}

			$this->localObjectCache[$key1] = $corporate;

			return $corporate;
		}

		return null;
	}

	/**
	 * Create the TINA server
	 * @param \App\Entity\GDSServiceAbstract $ent
	 * @param \App\ImportedOrder\Order $ord
	 * @return \App\ImportedOrder\Service
	 */
	protected function createTINAService(\App\Entity\GDSServiceAbstract $ent, \App\ImportedOrder\Order $ord)
	{
		$serv = new \App\ImportedOrder\Service();

		if (!in_array($ent->transactionStatus, ['refund', 'void'])) {
			$serv->auxReference = 	$ent->getId();
		}
		$serv->idSystem = 		$this->generateTINAServiceIdSystem($ent);

		$serv->systemStatus = 	$ent->transactionStatus;

		$serv->bookUser = $this->createUser($ent->idGalileoBookUser);
		$serv->issueUser = $this->createUser($ent->idGalileoIssueUser);

		$serv->bookOffice = $this->createOffice($ent->idGalileoBookOffice, $ent->transactionData->agent->bookingAgent->officeIATACode);
		$serv->issueOffice = $this->createOffice($ent->idGalileoIssueOffice, $ent->transactionData->agent->issuingAgent->officeIATACode);

		if (in_array($ent->serviceCode, ['ticketing', 'EMD',]) && $ent->docNumber) {
			$serv->respUser = $this->createUser($ent->idGalileoIssueUser);
			$serv->respOffice = $this->createOffice($ent->idGalileoIssueOffice, $ent->transactionData->agent->issuingAgent->officeIATACode);
		}
		else {
			$serv->respUser = $this->createUser($ent->idGalileoBookUser);
			$serv->respOffice = $this->createOffice($ent->idGalileoBookOffice, $ent->transactionData->agent->bookingAgent->officeIATACode);
		}


		// passengers
		if (!in_array($ent->transactionStatus, ['refund', 'void'])) {
			foreach ($ent->transactionData->passengers->passengers as $galileoPassenger) {
				// i'm not sending "owner" passenger here in any way. TINA doesn't support owner for a service, but only on the whole order
				$pax = $this->createPassenger($galileoPassenger, $ord);
				$serv->addPassenger($pax);

				if (!$ord->passengerExists($pax)) {
					$ord->addPassenger($pax);
				}
			}
		}

		$serv->serviceType = $this->generateTINAServiceType($ent->serviceCode);

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////
		// departure
		if ($ent->transactionData && $ent->transactionData->itinerary && $ent->transactionData->itinerary->getDeparture() && $ent->transactionData->itinerary->getDeparture()->hasLocation()) {
			$place = 	new \App\ImportedOrder\Place();
			$country = 	$this->createCountry( $ent->transactionData->itinerary->getDeparture()->countryCode, null );
			$city = 	$this->createCity( $ent->transactionData->itinerary->getDeparture()->cityId, null, $ent->transactionData->itinerary->getDeparture()->suggestions->cityCode[0]??null );
			$station = 	$this->createStation( $ent->transactionData->itinerary->getDeparture()->airportCode, $ent->transactionData->itinerary->getDeparture()->suggestions->airportName??null );

			$city->country = $country;
			$station->city = $city;
			$place->station = $station;
			$serv->departurePlace = $place;
		}

		// departure datetime
		if ($ent->transactionData && $ent->transactionData->itinerary && $ent->transactionData->itinerary->getDeparture()) {
			$dt = new \App\ImportedOrder\DateTime();
			$dt->dateStart = 	$ent->transactionData->itinerary->getDeparture()->date;
			$dt->dateEnd = 		$ent->transactionData->itinerary->getDeparture()->date;
			if ($ent->transactionData->itinerary->getDeparture()->time) {
				$dt->timeStart = 	$ent->transactionData->itinerary->getDeparture()->time;
				$dt->timeEnd = 		$ent->transactionData->itinerary->getDeparture()->time;
			}
			$serv->departureDateTime = $dt;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////
		// destination
		if ($ent->transactionData && $ent->transactionData->itinerary && $ent->transactionData->itinerary->getDestination() && $ent->transactionData->itinerary->getDestination()->hasLocation()) {
			$place = 	new \App\ImportedOrder\Place();
			$country = 	$this->createCountry(	$ent->transactionData->itinerary->getDestination()->countryCode, 	null);
			$city = 	$this->createCity(		$ent->transactionData->itinerary->getDestination()->cityId, 		null);
			$station = 	$this->createStation(	$ent->transactionData->itinerary->getDestination()->airportCode, 	$ent->transactionData->itinerary->getDestination()->suggestions->airportName??null);

			$city->country = $country;
			$station->city = $city;
			$place->station = $station;
			$serv->destinationPlace = $place;
		}

		// destination datetime
		if ($ent->transactionData && $ent->transactionData->itinerary && $ent->transactionData->itinerary->getDestination()) {
			$dt = new \App\ImportedOrder\DateTime();
			$dt->dateStart = 	$ent->transactionData->itinerary->getDestination()->date;
			$dt->dateEnd = 		$ent->transactionData->itinerary->getDestination()->date;

			if($ent->transactionData->itinerary->getDestination()->time) {
				$dt->timeStart = 	$ent->transactionData->itinerary->getDestination()->time;
				$dt->timeEnd = 		$ent->transactionData->itinerary->getDestination()->time;
			}
			$serv->destinationDateTime = $dt;
		}

		// mark reissues
		if ($ent->transactionStatus=='reissue') {
			$serv->idReissuedService = $this->generateTINAServiceIdSystem($ent->reissuedService);
		}

		$offer = null;
		if ($ent->transactionStatus == 'refund') {
			$serv->refundDetails = $this->generateTINAServiceRefundDetails($ent, $serv);
		}
		elseif ($ent->transactionStatus=='void') {
			$serv->cancelDetails = $this->generateTINAServiceCancelDetails($ent, $serv);
		}
		else {
			$offer = $this->generateTINAOffer($ent, $ord, $serv);
		}

		if ($offer) {
			$serv->chosenOffer = $offer;
			$serv->chosenOfferConfirmed = true;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////
		// setup remarks
		if ($serv->remarks===null) {
			$serv->remarks = '';
		}

		if ($ent->transactionData->remarks) {
			$serv->remarks.= 	implode("\n", $ent->transactionData->remarks->getList(['public', 'associated', 'custom', ]));
		}

		// if EMD - inject synthetic EMD remarks
		if (in_array($ent->serviceCode, ['EMD', 'NDCEMD', ])) {
			$emdRemarks = [
				"EMD REASON CODE:".$ent->transactionData->extraData->reasonCode,
				"EMD REASON NAME:".$ent->transactionData->extraData->reasonCode_text,
				"EMD SUB REASON CODE:".$ent->transactionData->extraData->reasonSubCode,
				"EMD SUB REASON NAME:".$ent->transactionData->extraData->reasonSubCode_text,
			];
			$emdRemarks = implode("\n", $emdRemarks);
			$serv->remarks = ($serv->remarks ? $serv->remarks."\n".$emdRemarks : $emdRemarks);
		}

		if (empty($serv->remarks)) {
			$serv->remarks = null;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////
		// determine the operation type
		if ($ent->transactionStatus=='void') {
			$operation = 'cancel';
		}
		elseif ($ent->transactionStatus=='refund') {
			$operation = 'refund';
		}
		else {
			$operation = 'insert';
			if ($ent->transactionReferences) {
				foreach ($ent->transactionReferences as $ref) {
					if ($ref['relationType'] == 'reissue' && \Entity\GalileoService::select($ref['id'])->docNumber) {
						$operation = 'update';
						break;
					}
				}
			}
		}
		$serv->operation = $operation;

		if ($ent->transactionData->remarks) {
			$processedRemarks = $ent->transactionData->remarks->getProcessedRemarks();
			if ($processedRemarks['INITIAL_SERVICE_ID']) {
				$serv->idServiceTinaInitial = $processedRemarks['INITIAL_SERVICE_ID'];
			}
		}

		return $serv;
	}

	/**
	 * Internal, generate the offer for a service
	 * @param \App\Entity\GDSServiceAbstract $ent
	 * @param \App\ImportedOrder\Order $ord
	 * @param \App\ImportedOrder\Service $serv
	 * @return \App\ImportedOrder\Offer
	 */
	protected function generateTINAOffer(\App\Entity\GDSServiceAbstract $ent, \App\ImportedOrder\Order $ord, \App\ImportedOrder\Service $serv)
	{
		// if we dont have any prices set, skip the offer add completly
		$priceData = $ent->transactionData->price;
		/* @var $priceData \App\Galileo\StructureParser\Types\Price */

		if (!$priceData || !$priceData->getDefinedComponentsList()) {
			return null;
		}

		$offer = new \App\ImportedOrder\Offer();

		$offer->idSystem = 	$this->generateTINAServiceIdSystem($ent);
		$offer->date = 		$ent->transactionData->documents->issueDate;

		$offer->externalSystemReservationId =	$ent->transactionData->documents->RECLOC;

		$firstService = $this->galileoServices[0]->PNRRootService;
		$offer->externalSystemReservationDate =	($firstService->transactionData->documents->docDate?:$firstService->transactionData->documents->issueDate);

		$offer->fareCalculationMode = 	$priceData->fareCalculationModeIndicator;
		$offer->fareCalculation = 		$priceData->fareCalculation;
		$offer->pricingEntry = 			$priceData->fareCalculationEntry;
		$offer->endorsements = 			($priceData->endorsements?implode("\n", array_filter($priceData->endorsements)):null);
		$offer->isNDC = 				in_array($ent->transactionData->getType(), ['NDCTicketing', ]);

		$offer->serviceType = $this->generateTINAServiceType($ent->serviceCode);

		if ($ent->transactionData->supplier->code3==='000' && $ent->transactionData->supplier->name==='') {
			$itinerary = $ent->transactionData->itinerary;
			/* @var $itinerary \App\Galileo\StructureParser\Types\Itinerary */
			$supplier = $this->createSupplier($itinerary->getSegment(0)->supplier->code3, $itinerary->getSegment(0)->supplier->name, [
				'iata' => 		$itinerary->getSegment(0)->supplier->codeIata,
				'tktCode' => 	$itinerary->getSegment(0)->supplier->code3,
			]);
		}
		else {
			$supplier = $this->createSupplier($ent->transactionData->supplier->code3, $ent->transactionData->supplier->name, [
				'iata' => 		$ent->transactionData->supplier->codeAirline,
				'tktCode' => 	$ent->transactionData->supplier->code3,
			]);
		}
		if ($supplier) {
			$offer->supplier = $supplier;
			if (!$ord->supplierExists($supplier) && !$ord->getSupplierByIdSystem($supplier->idSystem)) {
				$ord->addSupplier($supplier);
			}
		}


		$itinerary = $ent->transactionData->itinerary;
		/* @var $itinerary \App\Galileo\StructureParser\Types\Itinerary */

		// fill in product type & category & multipliers
		switch ($ent->serviceCode) {
			case 'ticketing':
				if ($itinerary) {
					$offer->setProductType($itinerary->getTripType(), $itinerary->getTripType());
					$serv->addProductType ($itinerary->getTripType(), $itinerary->getTripType());

					// fill in offer & service category
					$firstSegment = $itinerary->getSegment(0);
					if ($firstSegment && $firstSegment->serviceClass) {
						$offer->setCategory($firstSegment->serviceClass, $firstSegment->serviceClass);
						$serv->addCategory ($firstSegment->serviceClass, $firstSegment->serviceClass);
					}
				}
			break;

			case 'EMD':
			case 'NDCEMD':
				$offer->reasonCode = $ent->transactionData->extraData->reasonCode;
				$offer->reasonName = $ent->transactionData->extraData->reasonCode_text;

				$offer->reasonSubCode = $ent->transactionData->extraData->reasonSubCode;
				$offer->reasonSubName = $ent->transactionData->extraData->reasonSubCode_text;

				if ($itinerary) {
					$offer->setProductType($itinerary->getTripType(), $itinerary->getTripType());
					$serv->addProductType ($itinerary->getTripType(), $itinerary->getTripType());
				}
			break;

			case 'hotel':
				$supplierProduct = $this->createSupplierProduct(null, $ent->transactionData->supplier->name, $ent->transactionData->supplier->accomodation_chainCode, $ent->transactionData->itinerary->getDestination());
				if ($supplierProduct) {
					$offer->supplierProduct = $supplierProduct;
					if (!$ord->supplierProductExists($supplierProduct) && !$ord->getSupplierProductByIdSystem($supplierProduct->idSystem)) {
						$ord->addSupplierProduct($supplierProduct);
					}
				}

				if ($ent->transactionData->extraData->multiplier) {
					$offer->addMultiplierItem($ent->transactionData->extraData->multiplier, $ent->transactionData->extraData->multiplier, $ent->transactionData->extraData->multiplier);
					$serv->addMultiplierItem ($ent->transactionData->extraData->multiplier, $ent->transactionData->extraData->multiplier, $ent->transactionData->extraData->multiplier);
				}
			break;

			case 'car':
				$supplierProduct = $this->createSupplierProduct(null, null, $ent->transactionData->supplier->codeAirline, $ent->transactionData->itinerary->getDestination());
				if ($supplierProduct) {
					$offer->supplierProduct = $supplierProduct;
					if (!$ord->supplierProductExists($supplierProduct) && !$ord->getSupplierProductByIdSystem($supplierProduct->idSystem)) {
						$ord->addSupplierProduct($supplierProduct);
					}
				}

				if ($ent->transactionData->extraData->multiplier) {
					$offer->addMultiplierItem($ent->transactionData->extraData->multiplier, $ent->transactionData->extraData->multiplier, $ent->transactionData->extraData->multiplier);
					$serv->addMultiplierItem ($ent->transactionData->extraData->multiplier, $ent->transactionData->extraData->multiplier, $ent->transactionData->extraData->multiplier);
				}
			break;
		}

		// $offer->ignoreSupplierOnDocuments = ($ent->transactionData->price && $ent->transactionData->price->formOfPayment && $ent->transactionData->price->formOfPayment->isPaidWith('CC')?true:false);

		$offer->ignoreSupplierOnDocuments = $this->getIgnoreSupplierOnDocumentsValue($ent, $priceData);

		if ($ent->transactionData->price && $ent->transactionData->price->formOfPayment && $ent->transactionData->price->formOfPayment->isPaidWith('CC')) {
			$creditCard = new \App\ImportedOrder\CreditCard();
			$CC = $ent->transactionData->price->formOfPayment->getPaymentByType('CC', 0);
			/* @var $CC \App\Galileo\StructureParser\Types\PriceFormOfPaymentComponent */
			$creditCard->company = 		$CC->ccCompanyIndicator;
			$creditCard->expireDate= 	$CC->ccExpiryDate;
			$creditCard->number = 		$CC->ccNumber;
			$creditCard->approvalCode =	$CC->ccApprovalCode;
			$offer->creditCard = $creditCard;

			// TODO read from $CC instead
			//if ($CC->amount && $CC->currency) {
			if ($priceData->total && $priceData->totalCurrency) {
				$offer->creditCardPaymentValue 		= $priceData->total;
				$offer->creditCardPaymentCurrency 	= $priceData->totalCurrency;
			}
		}

		$extraInfo = (array)$offer->extraInfo;
		$extraInfo['originalPriceArr'] = $ent->transactionData->price->originalPriceArr;
		$offer->extraInfo = $extraInfo;

		$offer->totalCurrency = 		$ent->transactionData->price->pickMostProbableMainCurrency();

		// control is the transaction is commissionable or not. The GDS cores are ALWAYS non-commissionable (supplier & commission are 2 separate components of the price)
		if ($ent->transactionData->nonStructuredDataIsSet('commissionIncludedInSupplierPrice') && $ent->transactionData->nonStructuredDataGet('commissionIncludedInSupplierPrice')===false) {
			// this nsd is set only by plugin Plugins\SaSabreExtend
			$offer->commissionable = 		false;

			// servicePriceValue contains the VAT
			$offer->servicePriceTotal =		$ent->transactionData->price->supplier;
		}
		else {
			$offer->commissionable = 		true;
			// servicePriceValue contains the VAT
			$offer->servicePriceTotal = 	$ent->transactionData->price->supplier + $ent->transactionData->price->commission + $ent->transactionData->price->commissionVAT;
		}

		$offer->servicePriceVat = 		$ent->transactionData->price->supplierVAT;
		$offer->servicePriceCurrency = 	$ent->transactionData->price->supplierCurrency;

		if ($ent->transactionData->price->commission!==null || $ent->transactionData->price->commissionVAT!==null) {
			$offer->commissionTotal = 		$ent->transactionData->price->commission + $ent->transactionData->price->commissionVAT;
			$offer->commissionCurrency = 	$ent->transactionData->price->commissionCurrency;
		}

		if ($ent->transactionData->price->discount!==null || $ent->transactionData->price->discountVAT!==null) {
			$offer->discountTotal = 		-1*($ent->transactionData->price->discount + $ent->transactionData->price->discountVAT);
			$offer->discountCurrency = 		$ent->transactionData->price->discountCurrency;
		}

		if ($ent->transactionData->price->serviceFee!==null || $ent->transactionData->price->serviceFeeVAT!==null) {
			$offer->serviceFeeTotal = 		$ent->transactionData->price->serviceFee + $ent->transactionData->price->serviceFeeVAT;
			$offer->serviceFeeCurrency = 	$ent->transactionData->price->serviceFeeCurrency;
		}

		if ($ent->transactionData->price->tax && ($priceData->tax->getAmount() || $priceData->tax->getVAT())) {
			$offer->taxTotal = 				$priceData->tax->getAmount() + $priceData->tax->getVAT();
			$offer->taxVat = 				$ent->transactionData->price->tax->getVAT();
			$offer->taxCurrency = 			$ent->transactionData->price->tax->getCurrency();

			foreach ($ent->transactionData->price->tax->components as $tax) {
				$offer->addTaxCode($tax->code, $tax->amount + $tax->VAT, $tax->currency);
			}
		}

		$offer->confirmationNumber = 	$ent->transactionData->documents->docNumber;

		if (in_array($ent->serviceCode, ['ticketing', 'EMD', 'MCO', 'LCCTicketing',]) && $ent->transactionData->documents->docNumber) {
			$offer->ticketDate = 			$ent->transactionData->documents->issueDate;
			$offer = $this->loadTicketNumbersDefault($ent, $serv, $offer);
		}

		$offer = $this->specialHandlingForFormOfPaymentForReissues($ent, $offer);

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		// departure
		if ($itinerary && $itinerary->getDeparture() && $itinerary->getDeparture()->hasLocation()) {
			$place = 	new \App\ImportedOrder\Place();
			$country = 	$this->createCountry(	$itinerary->getDeparture()->countryCode, 	null);
			$city = 	$this->createCity(		$itinerary->getDeparture()->cityId, 		null);
			$station = 	$this->createStation(	$itinerary->getDeparture()->airportCode, 	$itinerary->getDeparture()->suggestions->airportName??null);

			$city->country = $country;
			$station->city = $city;
			$place->station = $station;
			$offer->departurePlace = $place;
		}

		// destination
		if ($itinerary && $itinerary && $itinerary->getDestination() && $itinerary->getDestination()->hasLocation()) {
			$place = 	new \App\ImportedOrder\Place();
			$country = 	$this->createCountry(	$itinerary->getDestination()->countryCode, null);
			$city = 	$this->createCity(		$itinerary->getDestination()->cityId, 	 null);
			$station = 	$this->createStation(	$itinerary->getDestination()->airportCode, $itinerary->getDeparture()->suggestions->airportName??null);

			$city->country = $country;
			$station->city = $city;
			$place->station = $station;
			$offer->destinationPlace = $place;
		}

		// set dates
		if ($itinerary && $itinerary->getDeparture()) {
			$offer->departureDateTime = \Engine\DateTime::createFromIsoDatetime($itinerary->getDeparture()->getDatetime());
		}

		if ($itinerary && $itinerary->getDestination()) {
			$offer->destinationDateTime = \Engine\DateTime::createFromIsoDatetime($itinerary->getDestination()->getDatetime());
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		if ($itinerary && in_array($ent->serviceCode, ['ticketing', 'NDCTicketing', 'LCCTicketing', 'EMD', 'NDCEMD', 'MCO', ])) {
			$offerItinerary = new \App\ImportedOrder\Itinerary();
			foreach((array)$itinerary->segments as $seg) {
				/* @var $seg \App\Galileo\StructureParser\Types\ItinerarySegment */

				$segment = new \App\ImportedOrder\Itinerary_Segment();

				// departure
				if ($seg->departure->hasLocation()) {
					$place = 	new \App\ImportedOrder\Place();
					$country = 	$this->createCountry(	$seg->departure->countryCode, 	null);
					$city = 	$this->createCity(		$seg->departure->cityId, 		null);
					$station = 	$this->createStation(	$seg->departure->airportCode, 	$seg->departure->suggestions->airportName??null);

					$city->country = $country;
					$station->city = $city;
					$place->station = $station;
					$segment->departurePlace = $place;
				}

				$segment->departureDateTime = \Engine\DateTime::createFromIsoDatetime($seg->departure->getDatetime());

				// destination
				if ($seg->destination->hasLocation()) {
					$place = 	new \App\ImportedOrder\Place();
					$country = 	$this->createCountry(	$seg->destination->countryCode, null);
					$city = 	$this->createCity(		$seg->destination->cityId, 		null);
					$station = 	$this->createStation(	$seg->destination->airportCode, $seg->destination->suggestions->airportName??null);

					$city->country = $country;
					$station->city = $city;
					$place->station = $station;
					$segment->arrivalPlace = $place;
				}

				$segment->arrivalDateTime = \Engine\DateTime::createFromIsoDatetime($seg->destination->getDatetime());

				// misc data
				if ($seg->duration) {
					$segment->duration = $seg->duration;
				}

				if ($seg->distance) {
					$segment->distance = $seg->distance;
					$segment->distanceUnit = 'miles';
				}

				if ($seg->transportNumber) {
					$segment->transportNumber = $seg->transportNumber;
				}
				if ($seg->transportModel) {
					$segment->transportUnit = $seg->transportModel;
				}
				if ($seg->serviceClassCode) {
					$segment->classCode = $seg->serviceClassCode;
					$segment->className = $seg->serviceClass;
				}

				if ($seg->ticketDesignator) {
					$segment->ticketDesignator = $seg->ticketDesignator;
				}

				$supplier = $this->createSupplier($seg->supplier->code3, $seg->supplier->name, [
					'iata' => 		$seg->supplier->codeAirline,
					'tktCode' => 	$seg->supplier->code3,
				]);
				if ($supplier) {
					$segment->supplier = $supplier;
					if (!$ord->supplierExists($supplier) && !$ord->getSupplierByIdSystem($supplier->idSystem)) {
						$ord->addSupplier($supplier);
					}
				}

				$segment->fareBasis = sprintf("%s", $seg->fareBasis);

				$offerItinerary->addSegment($segment);
			}
			$offer->itinerary = $offerItinerary;
		}

		// load the form of payment
		if ($ent->transactionData->price && $ent->transactionData->price->formOfPayment) {
			$offer->formOfPayment =  strtoupper($ent->transactionData->price->formOfPayment->getShortNotation());
		}

		if ($ent->transactionData->price->tourCode) {
			$offer->tourCode =  	 $ent->transactionData->price->tourCode;
		}

		return $offer;
	}

	/**
	 * Internal, generate a refund operations
	 * @return \App\ImportedOrder\RefundDetails
	 */
	protected function generateTINAServiceRefundDetails(\Entity\GalileoService $ent, \App\ImportedOrder\Service $serv)
	{
		$messages = [];

		$refundDetails = new \App\ImportedOrder\RefundDetails();

		$entRefunded = 				$ent->refundedService;
		$refundDetails->currency = 	$ent->transactionData->price->supplierCurrency;
		$refundDetails->date = 		($ent->transactionData->documents->issueDate?:$ent->transactionData->documents->docDate);

		$extraInfo = (array)$refundDetails->extraInfo;
		$extraInfo['originalPriceArr'] = $ent->transactionData->price->originalPriceArr;
		$extraInfo['originalPriceArr']['fareUsedCommissionValue'] = 0.0;

		/**
		 * compute the fare used field
		 * TINA has 2 issues with refunds:
		 * 	- the supplierValue contains the commission by default, and if we get a TICK with supp: 99, and commission: 1, then the supplierValue sent to TINA is 100
		 * 		when we get the refund, if the supplierValue is 100 and the commission is 0, then we would get a difference of 1 for the commission component,
		 * 		so we need to handle the case where fareUsedSupplierValue==0 (and also take into account float number differences)
		 *
		 *  - when we have a ticket with 100EUR, and we get a refund of 101 EUR, TINA cannot handle the 101 EUR return, it must be sent the original 100EUR at maximum.
		 *  	for this reason, we check if fareUsedSupplierValue<0, and force it to 0 if its negative. These cases can happen because of exchange rates differences
		 */
		$refundDetails->fareUsedSupplierValue = $entRefunded->transactionData->price->supplier + $ent->transactionData->price->supplier
												+ $entRefunded->transactionData->price->supplierVAT + $ent->transactionData->price->supplierVAT;

		/**
		 * @2020-05-xx Seems like we should never set the VAT on the supplier and commission fields, as they're never used, and never will be used.
		 * The current changes will only calculate the tax VAT because the 4 existing plugins only mangle the tax components and lets TINA calculate as much of the VAT as possible
		 * Also, setting up the VAT would imply that TINA has configured the offer component with -1, and that a very rare and undesired case
		 *
		 * The correct code would set the supplier VAT with something like this:
		 *
		 * 	if ($entRefundedPriceData->supplierVAT!==null || $priceData->supplierVAT!==null) {
		 * 		$refundDetails->fareUsedSupplierVat = 	($entRefundedPriceData->supplierVAT + $priceData->supplierVAT?:null);
		 * 	}
		 * 	- it handles the case when the VAT is null, and leave it as-is, because a value of 0.00 means that TINA should force the VAT to 0, and will not ignore it
		 * 	- it calculates the VAT as a difference, to take into account the significane of "fareUsed*" fields, instead of the amount paid back, as the GDS reports it
		 *
		 * $refundDetails->fareUsedCommissionValue = $entRefundedPriceData->commission + $priceData->commission + $entRefundedPriceData->commissionVAT + $priceData->commissionVAT;
		 * 	- the commission total should include the VAT components
		 */
		if ($refundDetails->fareUsedSupplierValue<=0.0001) {
			$refundDetails->fareUsedSupplierValue = 0.0;
			$refundDetails->fareUsedCommissionValue = 0.0;

			$messages[] = "The refund supplier value was greater than the service supplier value";
		}
		else {
			$refundDetails->fareUsedCommissionValue = $entRefunded->transactionData->price->commission + $ent->transactionData->price->commission;
			$extraInfo['originalPriceArr']['fareUsedCommissionValue'] = ($entRefunded->transactionData->price->originalPriceArr['commission']??$entRefunded->transactionData->price->commission) + ($ent->transactionData->price->originalPriceArr['commission']??$ent->transactionData->price->commission);
		}

		$refundDetails->extraInfo = $extraInfo;

		// compute the tax fare used field
		$refundDetails->fareUsedTaxValue = ($entRefunded->transactionData->price->tax?($entRefunded->transactionData->price->tax->getAmount() + $entRefunded->transactionData->price->tax->getVAT()):0)
			+ ($ent->transactionData->price->tax?($ent->transactionData->price->tax->getAmount() + $ent->transactionData->price->tax->getVAT()):0);

		$refundDetails->fareUsedTaxVat = ($entRefunded->transactionData->price->tax?$entRefunded->transactionData->price->tax->getVAT():0)
			+ ($ent->transactionData->price->tax?$ent->transactionData->price->tax->getVAT():0);

		if ($refundDetails->fareUsedTaxValue<0) {
			$refundDetails->fareUsedTaxValue = 0;
			$refundDetails->fareUsedTaxVat = ($refundDetails->fareUsedTaxVat?0:null);

			$messages[] = "The refund taxes were greater than the service taxes";
		}

		// compute the penalty field
		$refundDetails->penaltyValue = $ent->transactionData->price->penalty;
		if ($refundDetails->penaltyValue<0) {
			$refundDetails->penaltyValue = 0;

			$messages[] = "The refund penalty was negative";
		}

		// compute the serviceFee
		$refundDetails->serviceFeeValue = $ent->transactionData->price->serviceFee;
		if ($refundDetails->serviceFeeValue<0) {
			$refundDetails->serviceFeeValue = 0;

			$messages[] = "The refund serviceFee was negative";
		}

		// compute the transaction price data
		if ( $ent->transactionData->price->serviceFee && ($entRefunded->transactionData->price->serviceFee + $ent->transactionData->price->serviceFee==0)) {
			$refundDetails->returnOriginalServiceFee = true;
		}
		else {
			$refundDetails->returnOriginalServiceFee = false;
		}

		// populate issue user
		$refundDetails->issueUser = $this->createUser($ent->idGalileoIssueUser);

		// populate office user
		$refundDetails->issueOffice = $this->createOffice($ent->idGalileoIssueOffice, $ent->transactionData->agent->issuingAgent->officeIATACode);

		return $refundDetails;
	}

	/**
	 * Internal, generate a void operation
	 * @return \App\ImportedOrder\CancelDetails
	 */
	protected function generateTINAServiceCancelDetails(\Entity\GalileoService $ent, \App\ImportedOrder\Service $serv)
	{
		$cancelDetails = new \App\ImportedOrder\CancelDetails();
		$cancelDetails->date = ($ent->transactionData->documents->issueDate?:$ent->transactionData->documents->docDate);

		return $cancelDetails;
	}


	/**
	 * Get the state(true/false) for agencyCommission invoice based on Amadeus interface configs
	 * @param \Entity\GalileoService $ent
	 * @param \App\Galileo\StructureParser\Types\Price $priceData
	 * @return boolean
	 */
	protected function getIgnoreSupplierOnDocumentsValue(\Entity\GalileoService $ent, \App\Galileo\StructureParser\Types\Price $priceData)
	{
		$shouldIgnoreSupplierOnDocuments = false;

		$ignoreSupplierOnDocumentsAssociations = unserialize((string)\App\Galileo\Config::getInstance()->get("ignoreSupplierOnDocuments"));

		// set the agencyCommission invoice depending on the formOfPayment/service set in config
		if ($ignoreSupplierOnDocumentsAssociations) {
			if ($ignoreSupplierOnDocumentsAssociations[$ent->serviceCode]) {
				foreach ($ignoreSupplierOnDocumentsAssociations[$ent->serviceCode] as $paymentValue) {
					if (!$shouldIgnoreSupplierOnDocuments) {
						if ($ent->transactionData->price->formOfPayment->isPaidWith($paymentValue)) {
							$shouldIgnoreSupplierOnDocuments = true;
						}
					}
				}
			}
		}

		// set the agencyCommission invoice off when matching by credit card config from interface
		if ($shouldIgnoreSupplierOnDocuments) {
			if ($priceData and $priceData->formOfPayment and $priceData->formOfPayment->isPaidWith('CC')) {
				$shouldIgnoreSupplierOnDocuments = $this->determineIgnoreSupplierOnDocumentsValueForCC($priceData->formOfPayment->getPaymentByType('CC'));
			}
		}

		return $shouldIgnoreSupplierOnDocuments;

	}

}
