<?php
namespace ModulesAbstract\PluginReports\Report\Model\Exporter;

abstract class ExporterTreeJson extends ExporterTreeAbstract
{

	protected function formatValue($reportName, $column, $rawRow, $cfg)
	{
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallStart($this, __METHOD__, []);
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logInput([
			'reportName' => $reportName,
			'column' => $column,
			'rawRow' => $rawRow,
			#'cfg' => $cfg,
		]);

		if (!$cfg['type']) {
			\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->dlog("The column {%0} doesn't have a type. Assuming 'S'", [$column]);
			$cfg['type'] = 'S';
		}

		$oval = null;
		if ($rawRow && array_key_exists($column, $rawRow)) {
			$oval = $rawRow[$column];
		}

		$showValue = true; // default value


		$newVal = $this->preFormat($oval, ($cfg?: []));

		try {
			// display always values for boolean type
			if ($cfg['type'] == 'B') {
				$showValue = true;
			}
			elseif ($newVal) {
				$showValue = true;
			}
			elseif ($cfg['forceVisible']) {
				$showValue = true;
			}

			$this->validate($newVal, $column, ($cfg?: []));
		}
		catch ( \App\MessagesException $ex ) {
			$this->addFieldValidatorExceptionData ( $ex->getMessagesCollector (), $newVal, $reportName, $column, $cfg, $rawRow, $oval );
		}

		$newVal = $this->postFormat($newVal, ($cfg?: []));


		$ret = [$newVal, $showValue];
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();
		return $ret;
	}


	protected function &fillTreeStructure($reportName, $rootNode, Array $cfg, $ret, Array $parents = [], Array $options = [])
	{
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallStart($this, __METHOD__, []);
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logInput([
			'reportName' => $reportName,
			'rootNode' => $rootNode,
			#'cfg' => $cfg,
			'ret' => $ret,
			'parents' => $parents,
		]);

		// this exporter doesn't suport value-nodes on the root node
		if (!$parents) {
			$valueNodes = $this->getCfgValues($cfg);
			if ($valueNodes) {
				throw new \Exception(sprintf("You have value nodes defined in the root tag. The current exporter doesn't support value-nodes in the root element. Please pre-insert them from the local exporter class. Value-nodes: '%s'", implode("', '", array_keys($valueNodes))));
			}
		}

		if (!is_array($ret)) {
			throw new \Exception(sprintf("The \$ret variable is not an array. It should be an array and the keys for childs should be scalar values, or arrays"));
		}

		// loop through non-repeatable children
		foreach ($cfg as $chldKey => $chldCfg) {
			$p = $parents;
			$p[] = $chldKey;

			$alias = ($chldCfg['alias']?:$chldKey);	// alias is an optional field, exported as tag name

			$handled = false;
			if (!$handled && $chldCfg['children'] && $chldCfg['repeatable']) {	// container with repeatable children
				if (array_key_exists($chldKey, $ret)) {
					$values = $ret[$chldKey];
					if ($ret && !is_array($values)) {
						throw new \Exception(sprintf("The child '%s' is not a list, and the exporter expects a numeric list.", $chldKey));
					}

					$handled = true;
					if ($values) {
						$this->node_setValue($rootNode, $alias, []);
						foreach ($values as $value) {
							$this->node_appendValue($rootNode, $alias,
								$this->fillTreeStructure($reportName, $this->node_createBlank($rootNode), $chldCfg['children'], $value, $p)
							);
						}
					}
					else {
						// we are reporting an empty list
						if ($this->getCfg('emptyLists:show')) {
							$this->node_setValue($rootNode, $alias, []);
						}
					}
				}
				else {
					$handled = true;
					if ($this->getCfg('nonExistingValues:show')) {
						$value = null;	// report non-existing values, let the exporter decinde if and how to handle them
						$this->node_setValue($rootNode, $alias, []);
					}
				}
			}

			if (!$handled) {	// normal child, one value in it
				if (array_key_exists($chldKey, $ret)) {
					$value = $ret[$chldKey];
					if ($ret && is_array($value)) {
						throw new \Exception(sprintf("The child '%s' is a list, and the exporter expects a value.", $chldKey));
					}

					list ($outputValue, $showValue) = $this->formatValue($reportName, $chldKey, [ $chldKey=>$value, ], $chldCfg);
					$handled = true;
					if ($showValue) {
						\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logOutput(['addChild' => [$alias, $outputValue]]);

						$this->node_setValue($rootNode, $alias, $outputValue);
					}
				}
				else {
					$handled = true;
					if ($this->getCfg('emptyValues:show')) {
						$value = null;	// report non-existing values, let the exporter decinde if and how to handle them
						list ($outputValue, $showValue) = $this->formatValue($reportName, $chldKey, [ $chldKey=>$value, ], $chldCfg);
						if ($showValue) {
							\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logOutput(['addChild' => [$alias, $outputValue]]);

							$this->node_setValue($rootNode, $alias, $outputValue);
						}
					}
					// else hide empty values, do not report them at all
				}
			}

			if (!$handled) {
				throw new \Exception(sprintf("The child '%s' is not supported by the exporter. Value was not handled", $chldKey));
			}
		}

		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();

		return $rootNode;
	}

	protected function node_createBlank($rootNode) {
		return new \stdClass();
	}


	protected function node_setAttribute($node, $key, $value)
	{
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallStart($this, __METHOD__, []);
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logInput([
			'node' => $node,
			'key' => $key,
			'value' => $value,
		]);

		$node->{$key} = $value;

		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();

		return $node->{$key};
	}


	protected function &node_setValue($node, $key, $value)
	{
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallStart($this, __METHOD__, []);
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logInput([
			'node' => $node,
			'key' => $key,
			'value' => $value,
		]);

		if (!is_object($node) && !is_array($node)) {
			throw new \Exception('node_setValue: node is not an object or array');
		}

		$node->{$key} = $value;

		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();

		return $node->{$key};
	}


	protected function &node_appendValue($node, $key, $newValue)
	{
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallStart($this, __METHOD__, []);
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logInput([
			'node' => $node,
			'newValue' => $newValue,
		]);

		$node->{$key}[] = $newValue;

		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();

		return $node->{$key};
	}


	protected function &node_setNewNode($node, $key)
	{
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallStart($this, __METHOD__, []);
		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logInput([
			'node' => $node,
			'key' => $key,
		]);

		$node->{$key} = new \stdClass();

		\Engine\Debug\ParserCallTrace\ParserCallTrace::getInstance()->logCallEnd();

		return $node->{$key};
	}

}
