<?php
namespace ModulesAbstract\PluginReports\Report\Model\Gatherer;

/**
 * Return results by looking in rows from the BSP files
 * Accepted filters:
 * 	office:id
 * 	office:iataCode
 * 	ticketDate:start - mandatory
 * 	ticketDate:end - mandatory
 * @author Lucian Sirbu
 *
 */
class BspPeriodRows extends GathererAbstract
{
	protected $bspPeriodIds = null;
	protected $bspPeriodAttributes = [];
	protected $currentTraverser = null;

	protected $currentGroupIndex = null;
	protected $periodsGroupedByFiles = null;

	public function getAcceptedFilters()
	{
		return [
			'office:id',
			'office:iataCode',
			'ticketDate:start',
			'ticketDate:end',
			'period:id',
		];
	}

	public function getRequiredFilters()
	{
		return [];
	}

	/**
	 * Get all periods according to the current filters
	 * @return Array
	 */
	public function getBspPeriodIds()
	{
		if ($this->bspPeriodIds===null) {
			$listObj = new \App\EntityList\BspPeriods();
			if ($this->filters['office:id']) {
				$listObj->setFilter('idOffice', '=', $this->filters['office:id']);
			}

			if ($this->filters['office:iataCode']) {
				$listObj->setFilter('iataCode', '=', $this->filters['office:iataCode']);
			}

			if ($this->filters['ticketDate:start']) {
				$listObj->setFilter('periodEnd', '>=', $this->filters['ticketDate:start']);
			}
			if ($this->filters['ticketDate:end']) {
				$listObj->setFilter('periodStart', '<=', $this->filters['ticketDate:end']);
			}

			if ($this->filters['period:id']) {
				$listObj->setFilter('period:id', 'IN', $this->filters['period:id']);
			}

			$this->bspPeriodIds = $listObj->exportRecordsByColumn('id');
		}

		return $this->bspPeriodIds;
	}

	/**
	 * Set the period ids externally
	 * @return \ModulesAbstract\PluginReports\Report\Model\Gatherer\BspPeriodRows
	 */
	public function setBspPeriodIds($bspPeriodIds)
	{
		$this->bspPeriodIds = $bspPeriodIds;
		$this->currentGroupIndex= -1;
		return $this;
	}

	/**
	 * Group input period ids by filename
	 * @param array $bspPeriodIds
	 * @return array
	 */
	protected function groupPeriodsIntoFiles(array $bspPeriodIds)
	{
		$periodsGroupedByFiles = [];

		foreach ($bspPeriodIds as $id) {
			$period = \Entity\BspPeriod::select($id);
			$periodsGroupedByFiles[$period->filename][]= $id;
		}

		foreach ($periodsGroupedByFiles as $k=>$values) {
			sort($values);
			$periodsGroupedByFiles[$k] = $values;
		}

		// sort the list. The oldest file should be first
		usort($periodsGroupedByFiles, function($a, $b) {
			return (int)$a[0] - $b[0];
		});

		return array_values($periodsGroupedByFiles);
	}

	/**
	 * {@inheritDoc}
	 * @see \ModulesAbstract\PluginReports\Report\Model\Gatherer\GathererAbstract::objectInitialize()
	 */
	protected function objectInitialize()
	{
		$this->bspPeriodIds = $this->getBspPeriodIds();
		$this->periodsGroupedByFiles = $this->groupPeriodsIntoFiles($this->bspPeriodIds);

		// get period attribtues
		$listObj = new \App\EntityList\BspPeriods();
		$listObj->setColumn('period.group');
		$listObj->setFilter('period.id', 'IN', $this->bspPeriodIds);
		foreach ($listObj->exportRecords() as $record) {
			$this->bspPeriodAttributes[$record['id']]= $record;
		}

		// loop through each period and try to open it, if it has any data
		$this->currentGroupIndex = -1;
		$this->openNextPeriod();
	}

	/**
	 * Open the next period
	 */
	protected function openNextPeriod()
	{
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallStart($this, __METHOD__, func_get_args());

		do {
			if ($this->periodsGroupedByFiles[$this->currentGroupIndex]) {
				$this->closePeriod($this->periodsGroupedByFiles[$this->currentGroupIndex][0]);
			}

			$this->currentGroupIndex++;

			if ($this->periodsGroupedByFiles[$this->currentGroupIndex]) {
				try {
					$this->currentTraverser = $this->openPeriod($this->periodsGroupedByFiles[$this->currentGroupIndex][0]);
				}
				catch(\App\Utils\BaseParser\GeneralParserException $ex) {
					\Log::getInstance()->error(sprintf("[%s] BSP file parser exception caught. This should not happen. Please investigate", __METHOD__), [
						'exception' => $ex,
					]);
				}
			}
		} while(!$this->currentTraverser && isset($this->periodsGroupedByFiles[$this->currentGroupIndex]));

		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();
	}

	/**
	 * Open a certain period
	 * @return boolean|\App\Bsp\Parser\BspTraverserAbstract
	 */
	protected function openPeriod($bspPeriodId)
	{
		global $CFG;
		$bspFactory = \App\Bsp\Parser\BspTraverserFactory::getInstance();
		$traverser = $bspFactory->getParser($CFG['paths']['bsp'].\Entity\BspPeriod::select($bspPeriodId)->filename, \Entity\BspPeriod::select($bspPeriodId)->fileType);
		if (!$traverser) {
			return false;	// parser not found
		}

		if ($this->options['onInvalidBspRecords']) {
			$traverser->setOption('allowInvalidHeader', true);
		}

		$traverser->setOption('taxManipulationsMatrix', unserialize(@gzdecode(base64_decode(\App\Bsp\Config::getInstance()->get("taxManipulationsMatrix")))));

		if (!$traverser->hasContent()) {
			return false;	// empty file, try to open the next one
		}

		$traverser->setLogger($this->logger);

		return $traverser;
	}

	/**
	 * Close a period
	 */
	protected function closePeriod($period)
	{
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallStart($this, __METHOD__, func_get_args());
		$this->currentTraverser = null;
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();
	}

	/**
	 * (non-PHPdoc)
	 * @see \ModulesAbstract\PluginReports\Report\Model\Gatherer\GathererAbstract::objectDestroy()
	 */
	protected function objectDestroy()
	{
		if ($this->periodsGroupedByFiles[$this->currentGroupIndex]) {
			$this->closePeriod($this->periodsGroupedByFiles[$this->currentGroupIndex]);
		}
	}

	/**
	 * (non-PHPdoc)
	 * @see \ModulesAbstract\PluginReports\Report\Model\Gatherer\GathererAbstract::objectNext()
	 */
	protected function objectNext()
	{
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallStart($this, __METHOD__, func_get_args());

		// map group to periodId, and check if we are support to report this periodId
		$bspPeriodGroupsInCurrentFile = [];
		foreach ($this->bspPeriodAttributes as $attr) {
			if (in_array($attr['id'], ($this->periodsGroupedByFiles[$this->currentGroupIndex]??[]))) {
				$bspPeriodGroupsInCurrentFile[(int)$attr['group']]= $attr['id'];
			}
		}

		$bspRecord = null;
		if ($this->currentTraverser) {
			if ($this->options['onInvalidBspRecords']=='skipRow') {
				try {
					$bspRecord = $this->currentTraverser->next();
				}
				catch(\App\Bsp\BspRowException $ex) {
					\Log::getInstance()->error(sprintf("[%s] BSP file parser exception caught. This should not happen. Please investigate", __METHOD__), [
						'exception' => $ex,
					]);

					return 'intern:skip';
				}
			}
			if ($this->options['onInvalidBspRecords']=='skipPeriod') {
				try {
					$bspRecord = $this->currentTraverser->next();
				}
				catch(\App\Bsp\BspRowException $ex) {
					\Log::getInstance()->error(sprintf("[%s] BSP file parser exception caught for bspPeriod %s. This should not happen. Please investigate", __METHOD__, serialize($this->bspPeriodIds)), [
						'exception' => $ex,
					]);

					$bspRecord = null;
				}
			}
			else {
				$bspRecord = $this->currentTraverser->next();
			}
		}

		if (!$bspRecord || !($bspRecord->ticketNumber && $bspRecord->supplierCode3 && $bspRecord->ticketDate)) {
			if ($bspRecord && ($bspRecord->ticketNumber || $bspRecord->supplierCode3 || $bspRecord->ticketDate)) {
				throw new \App\Bsp\BspRowException(sprintf("Incomplete BSP record: ticketNumber=%s, supplierCode3=%s, ticketDate=%s. The parser did not handle the row correctly. Please investigate.",
					($bspRecord->ticketNumber ?? 'NULL'),
					($bspRecord->supplierCode3 ?? 'NULL'),
					($bspRecord->ticketDate ?? 'NULL')
				));
			}

			$this->closePeriod($this->periodsGroupedByFiles[$this->currentGroupIndex]);
			$this->openNextPeriod();
			if (!$this->currentTraverser) {
				\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logOutputWithNull([
					'$ret' => null,
				]);
				\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();
				return null;	// no more rows, stop the gatherer
			}
		}

		// custom filtering routines
		if ($bspRecord) {
			if (!\Engine\DateInterval::inInterval($bspRecord->ticketDate, $this->filters['ticketDate:start'], $this->filters['ticketDate:end'])) {
				\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logOutputWithNull([
					'$ret' => 'intern:skip',
					'__LINE__' => __LINE__,
				]);
				\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();
				return 'intern:skip';
			}

			if ($this->filters['ticketNumber'] && $bspRecord->ticketNumber!=$this->filters['ticketNumber']) {
				\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logOutputWithNull([
					'$ret' => 'intern:skip',
					'__LINE__' => __LINE__,
				]);
				\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();
				return 'intern:skip';
			}

			if (!isset($bspPeriodGroupsInCurrentFile[$bspRecord->group])) {
				\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logOutputWithNull([
					'$ret' => 'intern:skip',
					'__LINE__' => __LINE__,
				]);

				\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();
				return 'intern:skip';
			}
		}


		$ret = null;
		if ($bspRecord) {
			$bspPeriodId = $bspPeriodGroupsInCurrentFile[$bspRecord->group];
			$ret = [
				'bspRecord' => 		$bspRecord,
				'bspPeriodId' => 	$bspPeriodId,
				'bspPeriodGroup' => $this->bspPeriodAttributes[$bspPeriodId]['group'],
			];

			\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logOutputWithNull([
				'$ret' => $ret,
			]);

			\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();
			return $ret;
		}

		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logOutputWithNull([
			'$ret' => 'intern:skip',
			'__LINE__' => __LINE__,
		]);
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();
		return 'intern:skip';
	}
}
