<?php

namespace App\Reports\Sources;

/**
 * The class manages a custom column formula (of any type) and returns the result from that formula.
 * @author GabrielGrosu
 */
class CustomColumnFormulaResult
{

	/**
	 * Formula details based on internal reports format (associative arraty of keys: type, calculation, entity, id, attribute, source, key).
	 * @var array
	 */
	private array $formula;

	/**
	 * Associative array of report row: field=>value
	 * @var array
	 */
	private array $reportRow;

	/**
	 * Loads the formula details and the report row (as assoc array)
	 * @param array $formula
	 * @param array $reportRow
	 */
	public function __construct (array $formula, array $reportRow)
	{
		$this->formula = $formula;
		$this->reportRow = $reportRow;
	}

	/**
	 * Return the formula result.
	 * @return string|NULL
	 */
	public function getResult() :?string
	{

		switch ($this->formula['type']) {


			// MATH FORMULA ///////////////////////////////////////////////////////////////////////
			case 'math': {

				// get the current formula
				$calculation = $this->formula['calculation'];

				foreach($this->reportRow as $col=>$val) {
					$calculation = str_replace('%'.$col.'%', '('.(Float)$val.')', $calculation);
				}

				// there should not be any "%" symbols
				if (strpos($calculation, '%')!==false) {
					throw new \App\Reports\Sources\CustomColumnFormulaException('Invalid math formula - there should be no `%` char in math calculation after replacing values. Formula: '.$this->formula['calculation']);
				}

				$mathResult = null;
				try {
					if (eval("\$mathResult = $calculation;")!==null) {
						throw new \App\Reports\Sources\CustomColumnFormulaException('Invalid math formula during eval(). Formula: '.$this->formula['calculation']);
					}
				}
				catch(\DivisionByZeroError $e) {
					// forces the returned value to zero, also do not stop report execution
					return null;
				}
				catch (\ParseError $e) {
					throw new \App\Reports\Sources\CustomColumnFormulaException('Invalid math formula during eval() - parse error: '.$e->getMessage().'. Formula: '.$this->formula['calculation']);
				}

				return $mathResult;
			}


			// RD ENTITY FORMULA //////////////////////////////////////////////////////////////////
			case 'entity': {

				$class = "\\App\\ReportsData\\EntityFields\\".$this->formula['entity'];
				$idEntity = $this->reportRow[$this->formula['id']];

				// if no id for this formula - simply skip everything
				if (!$idEntity) {
					return null;
				}

				$entObj = new $class($idEntity);

				// get the attributes
				$attributes = explode('.', $this->formula['attribute']);

				// execute the attribute one by one
				$result = $entObj;
				foreach($attributes as $attr) {

					// check if the current object is still an Entity extension
					if ( !($result instanceof \App\ReportsData\EntityFields\GeneralAbstract) ) {
						throw new \App\Reports\Sources\CustomColumnFormulaException( sprintf('Invalid formula for entity custom column on class type; entity=`%s`, id=`%s`, attr=`%s`.', $this->formula['entity'], $this->formula['id'], $this->formula['attribute']) );
					}
					try {
						$result = $result->getValue($attr);
					} catch (\Exception $e) {
						throw new \App\Reports\Sources\CustomColumnFormulaException( sprintf('Invalid formula for entity custom column on attribute access; entity=`%s`, id=`%s`, attr=`%s`. Error: %s', $this->formula['entity'], $this->formula['id'], $this->formula['attribute'], $e->getMessage()) );
					}
				}
				// if, in the end, we still have an object, kill the report
				if (is_object($result)) {
					throw new \App\Reports\Sources\CustomColumnFormulaException( sprintf('Invalid formula for entity custom column on last attribute (must be scalar value); entity=`%s`, id=`%s`, attr=`%s`.', $this->formula['entity'], $this->formula['id'], $this->formula['attribute']) );
				}

				return $result;

			}

			// ARRAY VALUE FORMULA ////////////////////////////////////////////////////////////////
			case 'arrayValue': {

				// gets the serialized array (but allows blank string and stops)
				$sourceColumnCode = $this->formula['source'];
				$sourceColumnValueSer = $this->reportRow[$sourceColumnCode];
				if (!$sourceColumnValueSer) {
					return null;
				}

				// some string found - we json decode it and assume it is a correct
				$sourceColumnValue = json_decode($sourceColumnValueSer, true);
				if (!is_array($sourceColumnValue)) {
					throw new \App\Reports\Sources\CustomColumnFormulaException( sprintf('Invalid formula for arrayValue custom column on source field; source=`%s`,  key=`%s`.', $this->formula['source'], $this->formula['key']) );
				}

				// gets the list of traversed keys (may be more than one)
				$keys = explode('.', $this->formula['key']);

				// traverses the array and loads the last key from the sequence
				$ok = true; // indicates that the sequence of keys exists correctly
				$traversedArray = $sourceColumnValue;
				foreach ($keys as $key) {
					if (!is_array($traversedArray) or !key_exists($key, $traversedArray)) {
						$ok = false;
						break;
					}
					$traversedArray = $traversedArray[$key];
				}

				// if issued found along the way - just return null
				if (!$ok) {
					return null;
				}

				// we may still have an array - return null
				if (!is_scalar($traversedArray)) {
					return null;
				}

				// traversing went ok, and we have a scalar value - return it
				return $traversedArray;

			}

			default: {
				throw new \App\Reports\Sources\CustomColumnFormulaException("Invalid formula type `{$this->formula['type']}`. Formula details: " . var_export($this->formula, true));
			}
		}

	}

}