<?php
namespace App\Bsp\Parser;

abstract class BspTraverserAbstract
{
	/**
	 * The path to the file
	 * @var string
	 */
	protected $path = null;

	/**
	 * The original filename
	 * @var string
	 */
	protected $originalFilename = null;

	/**
	 * The internal parser initialized flag
	 * @var boolean
	 * @see \App\Bsp\Parser\BspTraverserAbstract::initialize()
	 */
	protected $initialized = false;

	/**
	 * File details, as extracted during parse
	 * @var array:\App\Bsp\Parser\BspTraverserFileDetails
	 */
	protected $filesDetails = [];

	/**
	 * Internal statistics
	 * @var array
	 * @see \App\Bsp\Parser\BspTraverserAbstract::getDebugStats()
	 */
	protected $stats = [];

	/**
	 * The file contents
	 * @var mixed
	 * @internal
	 * @see \App\Bsp\Parser\BspTraverserAbstract::loadFile()
	 * @see \App\Bsp\Parser\BspTraverserAbstract::hasContent()
	 */
	protected $contents = null;

	/**
	 * The internal regexp extractor
	 * @var \App\Utils\BaseParser\Utils\Extractor\SingleOptimized
	 */
	protected $extractor = null;

	/**
	 * Internal parser options
	 * @var array
	 */
	protected $options = [];

	/**
	 * The internal logger
	 * @var \Engine\Logger\GeneralAbstract
	 */
	protected $logger = null;

	/**
	 * Internal state variables
	 * @var array
	 */
	protected $state = [];

	public function __construct()
	{
		$this->logger = new \Engine\Logger\Dummy();	// could also be new \Engine\Debug\Dline();
		$this->filesDetails[] = new BspTraverserFileDetails();
	}

	/**
	 * Get the used logger
	 * @return \Engine\Logger\GeneralAbstract
	 */
	public function getLogger()
	{
		return $this->logger;
	}

	/**
	 * Set the internal logger
	 *
	 * @param \Engine\Logger\GeneralAbstract|\Engine\Logger\MultipleLogger $logger
	 * @return $this
	 */
	public function setLogger($logger)
	{
		$this->logger = $logger;

		return $this;
	}

	/**
	 * Load and/or set the path to the file to be parsed
	 * @param string $path
	 * @return \App\Bsp\Parser\BspTraverserAbstract
	 */
	public function loadFile($path)
	{
		$this->path = $path;
		return $this;
	}

	/**
	 * Set the original filename. It cand have some predefined formats, useful for the file parsers
	 * @param string $originalFilename
	 * @return \App\Bsp\Parser\BspTraverserAbstract
	 */
	public function setOriginalFilename($originalFilename)
	{
		$this->originalFilename = $originalFilename;
		return $this;
	}

	/**
	 * Get file details
	 * @return \App\Bsp\Parser\BspTraverserFileDetails
	 */
	public function getDetails()
	{
		return $this->filesDetails;
	}

	/**
	 * Get debugging information
	 * @return multitype:
	 */
	public function getDebugStats()
	{
		return $this->stats;
	}

	/**
	 * Check if file has real content
	 * @return bool
	 */
	abstract public function hasContent();

	/**
	 * Initialize the parser
	 */
	abstract public function initialize();

	/**
	 * Destroy the parser
	 */
	abstract public function destroy();

	/**
	 * Get the next valid ticketing row
	 */
	abstract protected function _next();

	/**
	 * Get to the next line in the file
	 * @return \App\Bsp\BspRow
	 */
	public function next()
	{
		if (!$this->initialized) {
			$this->initialize();
			$this->initialized = true;
		}

		$ret = null;
		do {
			$ret = $this->_next();
		} while ($ret==='intern:skip');

		if ($ret === null) {
			$this->destroy();
		}

		// post-process
		$taxManipulationsMatrix = $this->getOption('taxManipulationsMatrix');
		if ($ret && $taxManipulationsMatrix) {
			$affectedTaxes = [];
			$action = null;
			foreach ($taxManipulationsMatrix as $tm) {
				if ($tm['service']=='issue' && $ret->extraData->isIssue) {
					$affectedTaxes[]= $tm['taxCode'];
					$action = $tm['action'];
				}
				elseif ($tm['service']=='reissue' && $ret->extraData->isReissue) {
					$affectedTaxes[]= $tm['taxCode'];
					$action = $tm['action'];
				}
				elseif ($tm['service']=='ADM' && $ret->extraData->isAdm) {
					$affectedTaxes[]= $tm['taxCode'];
					$action = $tm['action'];
				}
			}

			/* @var $ret \App\Bsp\BspRow */
			if ($action=='moveTaxToSupplier') {
				foreach ($ret->priceTaxCash->getByCodes($affectedTaxes) as $tax) {
					if ($tax['currency'] != $ret->priceSupplierCash->currency) {
						throw new \RuntimeException(sprintf("[%s] Cannot move tax '%s' (%.2f %s) to the supplier price (%.2fs %s) because the currencies are not indentical", __METHOD__,  $tax['code'], $tax['amount'], $tax['currency'], $ret->priceSupplierCash->amount, $ret->priceSupplierCash->currency));
					}
					$ret->priceSupplierCash->amount+= 	$tax['amount'];
					$ret->priceTaxCash->removeTax($tax);
				}

				foreach ($ret->priceTaxCc->getByCodes($affectedTaxes) as $tax) {
					if ($tax['currency'] != $ret->priceSupplierCc->currency) {
						throw new \RuntimeException(sprintf("[%s] Cannot move tax '%s' (%.2f %s) to the supplier price (%.2fs %s) because the currencies are not indentical", __METHOD__,  $tax['code'], $tax['amount'], $tax['currency'], $ret->priceSupplierCc->amount, $ret->priceSupplierCc->currency));
					}
					$ret->priceSupplierCc->amount+= 	$tax['amount'];
					$ret->priceTaxCc->removeTax($tax);
				}
			}
			elseif ($action) {
				throw new \Exception(sprintf("[%s] Don't know how to handle action '%s' ", __METHOD__, $action));
			}
		}

		return $ret;
	}

	/**
	 * The internal regexp extractor
	 * @return \App\Utils\BaseParser\Utils\Extractor\Single
	 */
	protected function getExtractor()
	{
		if ($this->extractor) {
			return $this->extractor;
		}

		$this->extractor = new \App\Utils\BaseParser\Utils\Extractor\SingleOptimized();
		return $this->extractor;
	}

	/**
	 * Set option for parser
	 * @param string $key
	 * @param string $value
	 * @return \App\Bsp\Parser\BspTraverserAbstract
	 */
	public function setOption($key, $value)
	{
		$this->options[$key] = $value;
		return $this;
	}

	/**
	 * Get option for a spefic key
	 * @param String $key
	 * @return multitype:string boolean
	 */
	public function getOption($key)
	{
		return $this->options[$key];
	}


	/**
	 * Compare float numbers, return true if equal
	 * @param float $price1
	 * @param float $price2
	 * @return boolean
	 * @see \App\Utils\BaseParser\Structure\StructureParser\Structures\Base\Price::floatsAreEqual()
	 */
	protected function floatsAreEqual($price1, $price2)
	{
		return (sprintf("%.2f", $price1)===sprintf("%.2f", $price2));
	}



	/**
	 * Convert the results list to an iterator
	 * @see \ModulesAbstract\PluginReports\Report\Model\Gatherer\GathererAbstract::asIterator()
	 */
	public function asIterator()
	{
		while ($row = $this->next()) {
			yield $row;
		}
	}

	/**
	 * Convert the results list to an array. Be careful about the used memory!!!
	 * @see \ModulesAbstract\PluginReports\Report\Model\Gatherer\GathererAbstract::asArray()
	 * @return Array[]
	 */
	public function asArray()
	{
		$ret = [];
		while ($row = $this->next()) {
			$ret[]= $row;
		}

		return $ret;
	}

}
