<?php
namespace Modules\Orders\Sabre\FluX;

class Controller extends \ModulesAbstract\FluxRest\ControllerUploaderAutoSaveFile
{
	/**
	 * (non-PHPdoc)
	 * @see \ModulesAbstract\FluxRest\ControllerAbstract::getBasePath()
	 */
	protected function getBasePath()
	{
		global $CFG;
		return $CFG['paths']['sabre'];
	}

	/**
	 * (non-PHPdoc)
	 * @see \ModulesAbstract\FluxRest\ControllerAbstract::validateFluXSystem()
	 */
	protected function validateFluXSystem(\Entity\FluxInstance $fluxInstance)
	{
		return ($fluxInstance->assignedSystem=='sabre');
	}

	/**
	 * {@inheritDoc}
	 * @see \ModulesAbstract\FluxRest\ControllerAbstract::handleSystemHealth()
	 */
	protected function handleSystemHealth($request, \ModulesAbstract\FluxRest\Response\Main $mainResponse, \Entity\FluxInstance $fluxInstance)
	{
		parent::handleSystemHealth($request, $mainResponse, $fluxInstance);

		if (!$mainResponse->hasOperation('Wait')) {
			// fmt: 'onSystemLoad:wait,onCronjobs:wait,onTablesBusy:wait'/
			$behaviourOnSystemLoad = [];
			foreach (explode(",", $fluxInstance->behaviourOnSystemLoad) as $behaviour) {
				list($k, $v) = explode(":", $behaviour);
				$behaviourOnSystemLoad[$k] = $v;
			}

			// check sensitive cronjobs, wait accordingly
			global $DBTImportedOrders_services, $DBTImportedOrders_tickets, $DBTImportedOrders_orders, $DBTSabreParsedFiles, $DBTSabreServices;

			$openTables = \DB::getOpenTables();
			$busyTables = array_intersect([$DBTImportedOrders_services, $DBTImportedOrders_tickets, $DBTImportedOrders_orders, $DBTSabreParsedFiles, $DBTSabreServices, ], $openTables);
			if ($busyTables) {
				$op = new \ModulesAbstract\FluxRest\Response\Operation\NotifyUserLog();
				$op->text = sprintf('Server busy, some database tables are in use (%s)', implode(", ", $busyTables));
				$mainResponse->appendOperation($op);

				$op = new \ModulesAbstract\FluxRest\Response\Operation\Wait();
				$op->timeout = count($busyTables)*2.5;	// sleep longer the more tables are in use
				$mainResponse->appendOperation($op);

				if ($behaviourOnSystemLoad['onTablesBusy']=='wait') {
	 				// stop here, do not parse the file
	 				throw new \Exception\IO\Base("System busy");	// text message isn't actually displayed anywhere
	 			}
	 			// else $behaviourOnSystemLoad['onTablesBusy']=='slowdown'
			}
		}
	}

	/**
	 * (non-PHPdoc)
	 * @see \ModulesAbstract\FluxRest\ControllerUploaderAutoSaveFile::parseFileOnDisk()
	 */
	protected function parseFileOnDisk($path, $originalFilename, \Entity\FluxInstance $fluxInstance, $rawRequest)
	{
		\DB::startTransaction();
		$transactionTimings = [
			'start' => null,
			'FileParser' => null,
			'Storage' => null,
			'CompatibilityWithImportedOrders' => null,
			'commit' => null,
		];
		try {
			$transactionTimings['start'] = microtime(true);

			// parse the file
			$parser = new \App\Sabre\FileParser\IUR();
			$parser->setRawData(file_get_contents($path));
			$parser->setStoredData('fileDate', min($parser->collectFileDatesFromPath($path)));
			$parser->extract();

			// if we're still running, all went ok
			$parser->getStructure();

			$transactionTimings['FileParser'] = microtime(true);

			// use the processor to process the raw data
			$structureParser = new \App\Sabre\StructureParser\Processor($parser);
			$structureParser->initialize();
			$structureParser->setFile($path);

			// store the parsed data
			$storage = new \App\Sabre\Storage\StorageHandler();
			$storage->setOption('source', 'FluX');
			$storage->setOption('IP', ($_SERVER['REMOTE_ADDR']?:''));
			$storage->setProcessor($structureParser);
			$ret = $storage->execute();

			$transactionTimings['Storage'] = microtime(true);

			// determine what entities were inserted, reselect from DB, and insert them into ImportedOrders
			if ($ret) {
				$entities = [];
				if ($ret['inserted']) {
					foreach($ret['inserted'] as $entityId) {
						$entities[] = \Entity\SabreService::select($entityId);
					}
				}

				// plugin trigger
				\Engine\Plugins\Manager::getInstance()->executeEvent('Sabre-beforeConvertingImportedOrder-inFluX', [
					'this' => 		  	$this,
					'entities' => 		$entities,
					'structureParser'=> $structureParser,
					'storage' => 		$storage,
					'storageOutput' => 	$ret,
				]);

				// filter out documentIssue operations
				$entities = array_filter($entities, function($service) {
					/* @var $service \Entity\SabreService */
					return !(in_array($service->serviceCode, ['documentIssue', ]) && !$service->transactionData->documents->docSubType);
				});

				if ($entities) {
					// do the conversion from Sabre services to ImportedOrder "operation object"
					$convertor = new \App\Sabre\CompatibilityWithImportedOrders\Processor($entities);
					$order = $convertor->getConvertedOrder();
					$mode = $convertor->getConvertedApplyMode();

					// insert the importedOrder
					$orderInsert = \Entity\ImportedOrder::insert();
					$orderInsert->applyMode = $mode;	// overwrite, merge
					$orderInsert->order = $order;
					$orderInsert->originalFilesWithPath = [$this->getFilePathRelativeToFilesRoot($path)];
					$orderInsert->save();

					$insertedOrderId = $orderInsert->getId();

					// collect a list of affected files, and update their `idImportedOrders` field with the current $insertedOrderId
					//  #1: collect affected entities, group them by idFile. I'd expect that only one file gets filled in here, but one never knows
					$affectedFiles = [];
					foreach ($entities as $ent) {
						$affectedFiles[$ent->idFile] = $ent->file;
					}

					//  #2: update their corresponding field
					foreach ($affectedFiles as $file) {
						$fileUpd = \Entity\SabreFile::update($file->getId());
						$idImportedOrders = $file->idImportedOrders;
						$idImportedOrders[] = $insertedOrderId;
						$fileUpd->idImportedOrders = $idImportedOrders;
						$fileUpd->save();
					}
					$transactionTimings['CompatibilityWithImportedOrders'] = microtime(true);
				}
			}

			$parser->destroyCCInformationAndWriteInto($path);

			\DB::commitTransaction();
			$transactionTimings['commit'] = microtime(true);

			$this->queueSessionData[__FUNCTION__] = $transactionTimings;
		}
		catch(\Exception $ex) {
			\DB::rollbackTransaction();
			\App\Logger\FluxInstance::getInstance()->warning(sprintf("[%s] %s: %s", __METHOD__, get_class($ex), $ex->getMessage()), [
				'file' => new \Engine\Logger\Context\File([
					'name'=> $originalFilename,
					'content' => \Engine\File::getContents($path),
				]),
				'fluxInstance' => $fluxInstance,
			]);

			if (is_a($ex, "App\Utils\BaseParser\GeneralParserException")) {
				throw new \ModulesAbstract\FluxRest\ExceptionInvalidFile(sprintf("Invalid file %s. Error: %s", $originalFilename, $ex->getMessage()), null, $ex);
			}
			else {
				throw $ex;	// re-throw
			}
		}
	}
}

