<?php
namespace App\Sabre\FileParser;

class IUR extends \App\Utils\BaseParser\TextParser\FileParser\ParserForBlocks
{
	protected $extractRegexp = "/^(?P<rawData>AA(.+)M0(.+))/";

	/**
	 * Structure used by normal issues
	 */
	protected $children_issue = [
		['key'=>'IOMB',  'type'=>'IOMB', 'req' =>true],
		['key'=>'M0',									'children' => [
			['key'=>'M0',  	'type'=>'M0', 'req' =>true],
			['key'=>'M0Var',  'type'=>'M0Var', 'req' =>true],
		]],

		['key'=>'M1',  	'type'=>'M1', 'req' =>true,    'rep'=> [1, PHP_INT_MAX], 'children' => [
			['key'=>'M1',  	'type'=>'M1', 'req' =>true],
			['key'=>'M1Var',  'type'=>'M1Var', 'req' =>true],
		]],

		['key'=>'M2',  	'type'=>'M2', 'req' =>false, 'rep'=> [1, PHP_INT_MAX],  'children' => [
			['key'=>'M2',  	'type'=>'M2', 'req' =>true],
			['key'=>'M2Var',  'type'=>'M2Var', 'req' =>true],
			// do not remove this or 2016-04-xx - alex/AJRHCW00.PNR will fail
			['key'=>'NL',     'type'=>'NewLine', 'req' => false,],
		]],

		['key'=>'M3', 	              'req' =>false,    'rep'=> [1, 9], 'merge'=>true, 'children' => [
			['key'=>'M3', 	              'req' =>false,    'rep'=> [1, PHP_INT_MAX], 'children' => [
				['key'=>'M3Air', 	'type'=>'M3Air', 'req' =>true],
				['key'=>'M3AirVar','type'=>'M3AirVar', 'req' =>true],
			]],
			['key'=>'M3', 	                 'req' =>false, 'rep'=> [1, PHP_INT_MAX], 'children' => [
				['key'=>'M3Hhl', 	'type'=>'M3Hhl', 'req' =>true],
				['key'=>'M3HhlrVar','type'=>'M3HhlVar', 'req' =>true],
			]],
			['key'=>'M3', 	                 'req' =>false, 'rep'=> [1, PHP_INT_MAX], 'children' => [
				['key'=>'M3NonAir', 	'type'=>'M3NonAir', 'req' =>true],
			]],
			['key'=>'M3', 	                 'req' =>false, 'rep'=> [1, PHP_INT_MAX], 'children' => [
				['key'=>'M3Oths', 	'type'=>'M3Oths', 'req' =>true],
			]],
		]],

		['key'=>'M4',  	'type'=>'M4', 'req' => false,         'rep'=> [1, PHP_INT_MAX],],
		['key'=>'M5',  	'type'=>'M5', 'req' => false,         'rep'=> [1, PHP_INT_MAX],],
		['key'=>'M6',  	'type'=>'M6', 'req' => false,         'rep'=> [1, PHP_INT_MAX],],
		['key'=>'M7', 	                  'req' =>false,      'rep'=> [1, PHP_INT_MAX],  'children' => [
			['key'=>'M7',  	'type'=>'M7',],
			['key'=>'M7ASP',  'type'=>'M7ASP', 'req' =>false,],
		]],

		['key'=>'M8', 	                  'req' =>false,      'rep'=> [1, PHP_INT_MAX],  'children' => [
			['key'=>'M8',  	'type'=>'M8',],
			['key'=>'M8ASP',  'type'=>'M8ASP', 'req' =>false,],
		]],

		['key'=>'M9', 	                  'req' =>false,      'rep'=> [1, PHP_INT_MAX],  'children' => [
			['key'=>'M9',  	'type'=>'M9',],
			['key'=>'M9ASP',  'type'=>'M9ASP', 'req' =>false,],
		]],

		['key'=>'MA',  	'type'=>'MA', 'req' => false,         'rep'=> [1, PHP_INT_MAX],],
		['key'=>'MB',  	'type'=>'MB', 'req' => false,         'rep'=> [1, PHP_INT_MAX],],
		['key'=>'MC',  	'type'=>'MC', 'req' => false,         'rep'=> [1, PHP_INT_MAX],],
		['key'=>'MD',  	'type'=>'MD', 'req' => false,         'rep'=> [1, PHP_INT_MAX],],
		['key'=>'ME', 	                  'req' =>false,                                      'children' => [
			['key'=>'ME',  	'type'=>'ME', 'req' =>false,      'rep'=> [1, PHP_INT_MAX],],
			['key'=>'NL',     'type'=>'NewLine',],
		]],

		['key'=>'MF', 	                  'req' =>false,                                      'children' => [
			['key'=>'MF',  	'type'=>'MF', 'req' =>false,      'rep'=> [1, PHP_INT_MAX],],
			['key'=>'NL',     'type'=>'NewLine',],
		]],

		['key'=>'MG', 	              'req' =>false,          'rep'=> [1, PHP_INT_MAX], 'children' => [
			['key'=>'MG',  	    'type'=>'MG', 'req' => true,],
			['key'=>'IUGFCA',  	'type'=>'StringMultilineFixed',   'req' => false,],
			['key'=>'IUGREA',  	'type'=>'StringMultilineFixed',   'req' => false,],
			['key'=>'IUGCIN',     'req' => true,                         'rep'=> [1, PHP_INT_MAX], 'children' => [
				['key'=>'IUGCIN',  	'type'=>'MGIUGCIN',                'req' => true,],
				['key'=>'IUGTTD',  	'type'=>'StringMultilineFixed',    'req' => false,],
				['key'=>'IUGPTA',  	'type'=>'StringMultilineFixed',    'req' => false,],
				['key'=>'IUGPAA',  	'type'=>'StringMultilineFixed',    'req' => false,],
			]],

			['key'=>'IUGXCH',  	'type'=>'StringMultilineFixed',    'req' => false,  'isValid_regex!' => '/^M(G|X|Y)/',],
			['key'=>'IUGORG',  	'type'=>'StringMultilineFixed',    'req' => false,  'isValid_regex!' => '/^M(G|X|Y)/',],
			['key'=>'IUGIIE',  	'type'=>'StringMultilineFixed',    'req' => false,  'isValid_regex!' => '/^M(G|X|Y)/',],
		]],

		['key'=>'MX', 	              'req' =>false,          'rep'=> [1, PHP_INT_MAX], 'children' => [
			['key'=>'MX',  	    'type'=>'MX', 'req' => true,],
		]],

		['key'=>'MY', 	              'req' =>false,          'rep'=> [1, PHP_INT_MAX], 'children' => [
			['key'=>'MY',  	    'type'=>'MY', 'req' => true,],
		]],

		['key'=>'NL',     'type'=>'NewLine', 'req' => false,],
		['key'=>'NL',     'type'=>'NewLine', 'req' => false,],
		['key'=>'NL',     'type'=>'NewLine', 'req' => false,],
		['key'=>'NL',     'type'=>'NewLine', 'req' => false,],
		['key'=>'EOM',  	'type'=>'Constant', 'req' => true,   'val'=> '***EOM***'],
	];

	/**
	 * Structure used by VOID files
	 */
	protected $children_void = [
		['key'=>'IOMB',  'type'=>'IOMB', 'req' =>true],
		['key'=>'M0',									'children' => [
			['key'=>'M0Void',  	'type'=>'M0Void', 'req' =>	true],
			['key'=>'NL',     'type'=>'NewLine', 'req' => false,],
		]],

		['key'=>'NL',     'type'=>'NewLine', 'req' => false,],
		['key'=>'NL',     'type'=>'NewLine', 'req' => false,],
		['key'=>'EOM',  	'type'=>'Constant', 'req' => false,   'val'=> '***EOM***'],
	];

	/**
	 * The line structure
	 */
	protected $children = [];

	/**
	 * (non-PHPdoc)
	 * @see \App\Utils\BaseParser\TextParser\BlockAbstract::createObject()
	 */
	public function createObject($type, $options, $self)
	{
		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();
		if ($CFG['sabre']['FileParser']['LineParser'][$type]) {
			$type = $CFG['sabre']['FileParser']['LineParser'][$type];
		}

		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallStart($this, __METHOD__, func_get_args());
		$options['req'] = (isset($options['req'])?$options['req']:true);
		$obj = null;

		$class = sprintf("\App\Sabre\LineParser\Types\Line\%s", $type);
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logInput(['class' => $class]);
		if (class_exists($class)) {
			\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logOutput(['found' => true]);
			$obj = new $class($options, $self);
		}

		if (!$obj) {
			\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logOutput(['found' => false]);
			$obj = parent::createObject($type, $options, $self);
		}

		$obj->setHelper($this->lineHelper);
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();
		return $obj;
	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Utils\BaseParser\TextParser\BlockAbstract::preExtract()
	 */
	protected function preExtract(\App\Utils\BaseParser\TextParser\ExtractedBlock $ret)
	{
		if ($this->safeMatch("/^AA(.{9})M0(5|C).+/", $this->rawData)->count) {
			$this->children = $this->children_void;
		}
		else {
			$this->children = $this->children_issue;
		}
		return $ret;
	}


	/**
	 * (non-PHPdoc)
	 * @see \App\Utils\BaseParser\TextParser\BlockAbstract::postExtract()
	 */
	protected function postExtract(\App\Utils\BaseParser\TextParser\ExtractedBlock $ret)
	{
		// perform some post-parsing file validations
		if ($ret->extractedData['M1'] && $ret->extractedData['M0']['M0']['IU0NM1'] && $ret->extractedData['M0']['M0']['IU0NM1'] != count($ret->extractedData['M1'])) {
			throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\General("Invalid file. IU0NM1 doesn't match number of M1 lines");
		}

		if ($ret->extractedData['M2'] && $ret->extractedData['M0']['M0']['IU0NM2'] && $ret->extractedData['M0']['M0']['IU0NM2'] != count($ret->extractedData['M2'])) {
			throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\General("Invalid file. IU0NM2 doesn't match number of M2 lines");
		}

		if ($ret->extractedData['M3'] && $ret->extractedData['M0']['M0']['IU0NM3'] && $ret->extractedData['M0']['M0']['IU0NM3'] != count($ret->extractedData['M3'])) {
			throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\General(sprintf("Invalid file. IU0NM3 doesn't match number of M3 lines (%d vs %d)", $ret->extractedData['M0']['M0']['IU0NM3'], count($ret->extractedData['M3'])));
		}

		if ($ret->extractedData['M4'] && $ret->extractedData['M0']['M0']['IU0NM4'] && $ret->extractedData['M0']['M0']['IU0NM4'] != count($ret->extractedData['M4'])) {
			throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\General("Invalid file. IU0NM4 doesn't match number of M4 lines");
		}

		if ($ret->extractedData['M5'] && $ret->extractedData['M0']['M0']['IU0NM5'] && $ret->extractedData['M0']['M0']['IU0NM5'] != count($ret->extractedData['M5'])) {
			throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\General("Invalid file. IU0NM5 doesn't match number of M5 lines");
		}

		if ($ret->extractedData['M6'] && $ret->extractedData['M0']['M0']['IU0NM6'] && $ret->extractedData['M0']['M0']['IU0NM6'] != count($ret->extractedData['M6'])) {
			throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\General("Invalid file. IU0NM6 doesn't match number of M6 lines");
		}

		if ($ret->extractedData['M7'] && $ret->extractedData['M0']['M0']['IU0NM7'] && $ret->extractedData['M0']['M0']['IU0NM7'] != count($ret->extractedData['M7'])) {
			throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\General("Invalid file. IU0NM7 doesn't match number of M7 lines");
		}

		if ($ret->extractedData['M8'] && $ret->extractedData['M0']['M0']['IU0NM8'] && $ret->extractedData['M0']['M0']['IU0NM8'] != count($ret->extractedData['M8'])) {
			throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\General("Invalid file. IU0NM8 doesn't match number of M8 lines");
		}

		// if ($ret->extractedData['M9'] && $ret->extractedData['M0']['M0']['IU0NM9'] && $ret->extractedData['M0']['M0']['IU0NM9'] != count($ret->extractedData['M9'])) {
		// 	throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\General("Invalid file. IU0NM9 doesn't match number of M9 lines");
		// }

		return parent::postExtract($ret);
	}

	/**
	 * Get the unique file id
	 * @return string
	 */
	public function getFileId()
	{
		if ($this->extractedData['M0']['M0']) {
			$M0 = $this->extractedData['M0']['M0'];

			$ID = sprintf("%08s%06s%d%d%d%d%d%d%d%d%d%d",	// 8+6+10 chars = 24chars
				strtoupper(base_convert(\Engine\DateTime::createFromIsoDateTime($this->extractedData['IOMB']['datetime_'])->format("ymdHis"), 10, 32)),	// write the file issue date as a string
				$M0['IU0PNR'],
				$M0['IU0NM1'], $M0['IU0NM2'], $M0['IU0NM3'], $M0['IU0NM4'], $M0['IU0NM5'], $M0['IU0NM6'],
				$M0['IU0NM7'], $M0['IU0NM8'], $M0['IU0NM9'], $M0['IU0NMA']
			);
		}

		if ($this->extractedData['M0']['M0Void']) {
			$M0 = $this->extractedData['M0']['M0Void'];

			$ID = sprintf("%08s%06s%10s",	// 8+6+10
				strtoupper(base_convert(\Engine\DateTime::createFromIsoDateTime($this->extractedData['IOMB']['datetime_'])->format("ymdHis"), 10, 32)),	// write the file issue date as a string
				$M0['IU0PR1'],
				$M0['IU0TKI_ticket']
			);
		}

		if (!$ID) {
			throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\General("Cannot extract file id");
		}

		return $ID;
	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Utils\BaseParser\TextParser\FileParser\ParserForBlocks::preProcessRawData()
	 */
	protected function preProcessRawData($originalRawData)
	{
		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();

		// remove invalid binary chars
		$originalRawData = str_replace(chr(0x0c), "", $originalRawData);
		$originalRawData = str_replace(["\r\n", "\r", ], "\n", $originalRawData);

		// remove whitespace from blank lines
		if ($CFG['sabre']['FileParser']['IUR']['preProcessRawData']['operations']['trimEmptyLines']) {
			// remove line with only one space in them
			$originalRawData = implode("\n", array_map(function($line) {
				if (trim($line)==="" && strlen($line)<2) {
					return "";
				}
				return $line;
			}, explode("\n", $originalRawData)));
		}

		$originalRawData = rtrim($originalRawData);

		return parent::preProcessRawData($originalRawData);
	}

	/**
	 * Destroy CC information from the current file
	 * Is can remove by doing str_replace over each encountered line
	 *
	 * @param array $regexpReplacements
	 * @return array
	 */
	public function destroyCCInformationAndWriteInto($filePath, ?\App\Utils\BaseParser\TextCleaner\TextCleaner $textCleaner = null)
	{
		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();
		if (!$CFG['pciDss']['compliant']) {
			return [];
		}

		$hitLines = [];

		$contents = $this->rawData;
		$parsedStructure = $this->getExtractedData();

		$textCleaner = ($textCleaner?:new \App\Utils\BaseParser\TextCleaner\TextCleaner());

		if ($parsedStructure['M2']) {
			foreach ($parsedStructure['M2'] as $M2) {
				if ($M2['M2Var']['IU2FOP_']) {
					foreach ($M2['M2Var']['IU2FOP_'] as $IU2FOP_) {
						if ($IU2FOP_['type']=='CC' and $IU2FOP_['number']) {
							$textCleaner->appendRule(new \App\Utils\BaseParser\TextCleaner\RuleLineRegexpAndTextReplacement(
								sprintf("^(?!(M[0-9])).*%s", preg_quote($M2['M2Var']['IU2FOP'], '/')),
								sprintf("%s%s", $IU2FOP_['company'], $IU2FOP_['number']),
								sprintf("%s%s", $IU2FOP_['company'], \App\Utils\CreditCard\NumberDetails::create((string)$IU2FOP_['number'])->getMaskedNumber())
							));
						}
					}
				}
			}
		}

		if ($parsedStructure['M3']) {
			foreach ($parsedStructure['M3'] as $M3) {
				if ($M3['M3Oths']['IU3VR4_']['G']['ccNumber']) {
					$textCleaner->appendRule(new \App\Utils\BaseParser\TextCleaner\RuleLineRegexpAndTextReplacement(
						sprintf("^M3.+G%s%s", preg_quote($M3['M3Oths']['IU3VR4_']['G']['ccCompany'], '/'), preg_quote($M3['M3Oths']['IU3VR4_']['G']['ccNumber'], '/')),
						sprintf("%s%s", $M3['M3Oths']['IU3VR4_']['G']['ccCompany'], $M3['M3Oths']['IU3VR4_']['G']['ccNumber']),
						sprintf("%s%s", $M3['M3Oths']['IU3VR4_']['G']['ccCompany'], \App\Utils\CreditCard\NumberDetails::create((string)$M3['M3Oths']['IU3VR4_']['G']['ccNumber'])->getMaskedNumber())
					));
				}
			}
		}

		// remove from associated I lines
		if ($parsedStructure['M5']) {
			foreach ($parsedStructure['M5'] as $M5) {
				if ($M5['formOfPayment_']['type']=='CC') {
					$textCleaner->appendRule(new \App\Utils\BaseParser\TextCleaner\RuleLineRegexpAndTextReplacement(
						sprintf("^M5.*%s", preg_quote($M5['formOfPayment'], '/')),
						sprintf("%s%s%s", $M5['formOfPayment_']['typeRaw'], $M5['formOfPayment_']['companyIndicator'], $M5['formOfPayment_']['number']),
						sprintf("%s%s%s", $M5['formOfPayment_']['typeRaw'], $M5['formOfPayment_']['companyIndicator'], \App\Utils\CreditCard\NumberDetails::create((string)$M5['formOfPayment_']['number'])->getMaskedNumber())
					));
				}
			}
		}


		if ($parsedStructure['MG']) {
			foreach ($parsedStructure['MG'] as $MG) {
				if ($MG['MG']['IUGCCN'] && $MG['MG']['IUGFOP']=='CC') {
					$textCleaner->appendRule(new \App\Utils\BaseParser\TextCleaner\RuleLineRegexpAndTextReplacement(
						sprintf("^MG.*%s", preg_quote($MG['MG']['IUGPNO'], '/')),
						sprintf("CC%s %s", $MG['MG']['IUGCCC'], $MG['MG']['IUGCCN']),
						sprintf("CC%s %s", $MG['MG']['IUGCCC'], \App\Utils\CreditCard\NumberDetails::create((string)$MG['MG']['IUGCCN'])->getMaskedNumber())
					));
				}
			}
		}

		if ($textCleaner->hasRules()) {
			$contentArr = explode("\n", $contents);
			$originalContentsArr = $contentArr;

			list($contentArr, , $hitLines) = $textCleaner->simplifyRules()->cleanupArrRaw($contentArr);

			// NOTE: we could make sure all regexps applied at least once. If not, then some CC's might have gotten skipped

			// write the file back to disk, if any differences were found. We might have just parsed a file that was already masked, so no real changes were done
			if ($contentArr!=$originalContentsArr) {
				\Engine\File::create($filePath, implode("\n", $contentArr));
			}
		}

		return array_keys($hitLines);
	}
}
