<?php
namespace App\Utils\BaseParser\TextCleaner;

class TextCleaner
{
	/**
	 * The internal list of rules
	 *
	 * @var array
	 */
	protected $rules = [];

	/**
	 * Append a rule
	 *
	 * @param Rule $rule
	 * @return \App\Utils\BaseParser\TextCleaner\TextCleaner
	 */
	public function appendRule(Rule $rule)
	{
		$this->rules[]= [
			'class' => 	get_class($rule),
			'obj' => 	$rule,
			'hits' => 	0,
		];

		return $this;
	}

	/**
	 * Simplify the internal list of rules
	 * Will remove duplicate rules and sort them
	 *
	 * @return \App\Utils\BaseParser\TextCleaner\TextCleaner
	 */
	public function simplifyRules()
	{
		$this->rules = $this->internal_simplifyRules($this->getRules());

		return $this;
	}

	/**
	 * Get the internal list of rules
	 *
	 * @return array
	 */
	public function getRules()
	{
		return $this->rules;
	}

	/**
	 * Simplify a general list of rules
	 *
	 * @param array $rules
	 * @return array
	 */
	protected function internal_simplifyRules(array $rules)
	{
		// remove duplicate rules. They pretty common, because the same CC number is used to pay multiple services for example
		$rules = array_uunique($rules, function (array $a, array $b) {
			$ret = 0;
			if (!$ret) {
				$ret = strcmp(get_class($a['obj']), get_class($b['obj']));
			}

			if (!$ret) {
				$ret = strcmp($a['obj']->lineMatch, $b['obj']->lineMatch);
			}

			if (!$ret) {
				$ret = strcmp($a['obj']->search, $b['obj']->search);
			}

			return $ret;
		});

		// reverse sort by length, prioritise longer strings first, in order to avoid doing the small replacements first, that could affect the larger ones
		usort($rules, function (array $a, array $b) {
			$ret = 0;

			if (!$ret) {
				$ret = strcmp(get_class($b['obj']), get_class($a['obj']));
			}

			if (!$ret) {
				$ret = strlen($b['obj']->lineMatch) - strlen($a['obj']->lineMatch);
			}

			if (!$ret) {
				$ret = strcmp($b['obj']->lineMatch, $a['obj']->lineMatch);
			}

			return $ret;
		});

		return $rules;
	}

	/**
	 * Check if any rules are defined
	 *
	 * @return boolean
	 */
	public function hasRules()
	{
		return (bool)$this->rules;
	}

	/**
	 * Do the actual cleanup of a list of lines
	 * Will return the modified $contentArr back, and also some stats that might be needed when doing highlights or validations
	 *
	 * @param array $contentArr
	 * @return array
	 */
	public function cleanupArrRaw(array $contentArr)
	{
		$hitRules = [];
		$hitLines = [];

		foreach ($contentArr as $lineIdx=>$line) {
			foreach ($this->rules as $ruleIndex=>$rule) {
				if ($rule['obj']->isMatch($line)) {
					$nline = $rule['obj']->replace($line);

					$this->rules[$ruleIndex]['hits']++;
					$hitRules[$ruleIndex]++;
					$hitLines[$lineIdx][] = $ruleIndex;

					$contentArr[$lineIdx] = $nline;
					$line = $nline;
				}
			}
		}

		return [$contentArr, $hitRules, $hitLines];
	}

	/**
	 * Convenience call for cleanupArrRaw()
	 * Ignores returned stats
	 *
	 * @param array $contentArr
	 * @return array
	 */
	public function cleanupArr(array $contentArr)
	{
		list($contentArr, , ) = $this->cleanupArrRaw($contentArr);

		return $contentArr;
	}
}
