<?php

namespace App\Spotlight\Source;

class MathOperations extends \App\Spotlight\SourceAbstract
{
	protected $cacheKey = null;

	public function __construct()
	{
		parent::__construct();

		$this->cacheKey = sprintf("%s:%s", __CLASS__, CURRENT_ID_USER);
	}


	/**
	 * {@inheritDoc}
	 * @see \App\Spotlight\SourceAbstract::determineRecords()
	 */
	protected function determineRecords($offset, $resultsPerPage):array
	{
		$return = [];

		// remove leftover operations, in order to acces inputs like '1+1+', and covnert them to '1+1'
		$this->searchString = rtrim($this->searchString, '+-*/');
		if ($this->extractor->contains("/^[0-9\.\s\(]+[\+\-\/\*\(\)]+[0-9\.\+\-\/\*\(\)\s]+$/i", $this->searchString)) {
			// isolate the inpout str in a function, to separate local variables
			if ($this->isValidMathExpression($this->searchString)) {
				$fcnCode = sprintf("\$ret = function() {return %s;}; return \$ret();", $this->searchString);

				$prettyInput = $this->searchString;
				$prettyInput = preg_replace("/([\+\-\*\/]+)/i", ' $1 ', $prettyInput);
				$prettyInput = preg_replace("/[\s]+([\*\/]+)[\s]+/i", '$1', $prettyInput);
				$prettyInput = trim($prettyInput);

				$shouldTruncate = (strlen($prettyInput)>23);

				error_clear_last();
				$evalResult = @eval($fcnCode);
				if (error_get_last()) {
					$result = 0;
					$error = error_get_last();

					$record = new \App\Spotlight\Record(
						'An error occured while evaluating the math expression',
						($shouldTruncate? '...'.substr($prettyInput, -20):$prettyInput),
						sprintf("spotlight_autocomplete.setValue({value:'%s' });", $this->searchString),
						0,
						['color-important', ]
					);
					$record->tooltip = $error['message'];
					$return[]= $record;
				}
				else {
					$result = round($evalResult, 2);

					$record = new \App\Spotlight\Record(
						$result,
						($shouldTruncate? '...'.substr($prettyInput, -20):$prettyInput),
						sprintf("if(null===window.prompt('%s =', '%s')){ spotlight_autocomplete.setValue({value:'%s' }); }", $prettyInput, $result, $this->searchString),
						0,
						['color-important', ]
					);

					if ($shouldTruncate) {
						$record->tooltip = $prettyInput;
					}
					$return[]= $record;

					// initialize the history
					$history = \Engine\Cache\General::getInstance('disk')->get($this->cacheKey);
					if (!$history) {
						$history = [];
					}

					// append matching items from the history, most-recent first
					foreach (array_reverse($history) as $hist) {
						$shouldTruncate = (strlen($hist['input'])>23);
						if ($hist['input']!=$prettyInput && $this->matchesSearchString($hist['input'])) {
							$record = new \App\Spotlight\Record(
								$hist['result'],
								($shouldTruncate? '...'.substr($hist['input'], -20):$hist['input']),
								sprintf("if(null===window.prompt('%s =', '%s')){ spotlight_autocomplete.setValue({value:'%s' }); }", $hist['input'], $hist['result'], $hist['input']),
								10,
								['text-italic', ]
							);
							if ($shouldTruncate) {
								$record->tooltip = $hist['input'];
							}
							$return[]= $record;
						}
					}

					// append and deduplicate and cleanup the history
					$history[]= [
						'input' => 	$prettyInput,
						'result' => $result,
					];
					// deduplicate items
					$keys = [];
					foreach ($history as $idx=>$hist) {
						$k = $hist['input'];
						if (in_array($k, $keys)) {
							unset($history[$idx]);
						}
						$keys[]= $k;
					}
					$history = array_values(array_slice($history, -50));		// limit history size
					\Engine\Cache\General::getInstance('disk')->set($this->cacheKey, $history, 60*60*24*7);	// save it to disk
				}
			}
		}

		return $return;
	}

	/**
	 * {@inheritDoc}
	 * @see \App\Spotlight\SourceAbstract::matchesSearchString()
	 */
	protected function matchesSearchString($label)
	{
		$searchStr = 	preg_replace("/[^0-9]+/i", ' ', $this->searchString);
		$labelStr = 	preg_replace("/[^0-9]+/i", ' ', $label);

		return $this->extractor->startsWith($searchStr, $labelStr) || $this->extractor->startsWith($labelStr, $searchStr);
	}


	/**
	 * Check if the input string is a valid math expression
	 * @see https://stackoverflow.com/a/2596378/11301
	 * */
	protected function isValidMathExpression($str)
	{
		// also @see https://www.php.net/manual/en/function.eval.php#107377

	    // define the grammar
	    $number = "\d+(\.\d+)?";
	    $ident  = "[a-z]\w*";
	    $atom   = "[+-]?($number|$ident)";
	    $op     = "[+*/-]";

	    if (!$this->extractor->contains("~{$op}{2,}~i", $str)) {
		    $sexpr  = "$atom($op$atom)*"; // simple expression

		    // step1. remove whitespace
		    $str = preg_replace('~\s+~', '', $str);

		    // step2. repeatedly replace parenthetic expressions with 'x'
		    $par = "~\($sexpr\)~";
		    while (preg_match($par, $str)) {
		        $str = preg_replace($par, 'x', $str);
		    }

		    // step3. no more parens, the string must be simple expression
		    return preg_match("~^$sexpr$~", $str);
	    }

	    return false;
	}
}
