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

abstract class ExporterTreeJsonCustomFormatter extends ExporterTreeAbstract
{

	protected function node_setAttribute($node, $key, $value) {}

	protected function node_setValue($node, $key, $value) {}

	protected function node_appendValue($node, $key, $newValue) {}

	protected function node_setNewNode($node, $value) {}



	protected function formatTreeValue($reportName, $column, $rawRow, $cfg, $parents, $options)
	{
		// if no type was specified, assume 'S'
		if (!$cfg['type']) {
			$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->addTreeFieldValidatorExceptionData($ex->getMessagesCollector(), $newVal, $reportName, $column, $cfg, $rawRow, $oval, $parents, $options);
		}

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

		$ret = [$newVal, $showValue];
		return $ret;
	}

		/**
	 * Log export messages
	 * @param \App\MessagesCollector $msgs
	 */
	public function addTreeFieldValidatorExceptionData(\App\MessagesCollector $msgs, $newValue, $reportName, $field, $fieldCfg, $rawRow, $validatedRow, $parents, $options)
	{
		foreach ($msgs->getMessages() as $msgs) {
			$context = [
				'validatedRow' => $validatedRow,
			];
			$this->errorReport->logRaw(
				'error', $this->composeErrorMessage('error', $reportName, $field, $msgs->getText(), $newValue, (array)$options['_identifiers'], $context, $parents, $options)
			);
		}
	}

	protected function composeErrorMessage($level, $reportName, $field, $message, $value, array $identifiers, array $context, array $parents, array $options)
	{
		$ret= [];

		$ret['report'] =		$reportName;
		$ret['field'] =			implode("/", $parents) . "/{$field}";
		$ret['message'] = 		$message;
		$ret['value'] =			$value;

		return $ret;
	}


	protected function jsonFormat($value, array $cfg, array $rawRow=[])
	{
		switch ($cfg['type']) {
			case 'B':
				$value = ($value ? 'true' : 'false');
			break;
			case 'NI':
			case 'NF':
				// do not escape int's
			break;

			default:
				$value = sprintf("\"%s\"", addcslashes($value, '"'));
		}

		return $value;
	}


	protected function fillTreeStructure($reportName, $content, Array $cfg, $ret, Array $parents = [],$options = [])
	{
		// 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 ($ret && !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"));
		}

		// do not stop with an error when now rows are to be exported
		if (!$ret) {
			$ret = [];
		}

		/**
		 * Propagate the local _identifiers key as an option
		 * It's not "standard" to inject this as an option, but modifying each $row and taking care of array-rows or value-rows or single values would be pretty convoluted
		 * Overweite _identifiers keys from local data, and propagate to lower child levels
		 * */
		if ($ret['_identifiers']) {
			foreach ($ret['_identifiers'] as $k=>$v) {
				$options['_identifiers'][$k] = $v;
			}
		}
		$depth = $options['depth'];

		// loop through non-repeatable children
		$cfgCount = count($cfg);
		$cfgIndex = -1;
		$contentArr = [];
		foreach ($cfg as $chldKey => $chldCfg) {
			$p = $parents;
			$p[] = $chldKey;
			$cfgIndex++;

			$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
				// check if value actually exists in $cfg
				if (array_key_exists($chldKey, $ret)) {
					$o = $options;
					list($contentStr, $handled) = $this->generateFieldData($alias, $ret[$chldKey], $chldKey, $chldCfg, $cfgIndex, $cfgCount, $depth, $reportName, $cfg, $ret, $p, $o);
					if ($handled) {
						$contentArr[]= $contentStr;
					}
				}
				else {
					// vaue is not configured in $cfg, we should probably ignore it
					$handled = true;
					$value = null;	// report non-existing values, let the exporter decide if and how to handle them

					if ($chldCfg['event:onEmpty']) {
						$o = $options;
						$o['depth']++;
						$contentStr = call_user_func_array([$this, $chldCfg['event:onEmpty']], ['render:element:empty', $contentArr, $alias, $value, $chldKey, $chldCfg, $cfgIndex, $cfgCount, $depth, $reportName, $cfg, $ret, $p, $o]);
						$contentArr[]= $contentStr;
					}
					elseif ($chldCfg['event:render']) {
						$o = $options;
						$o['depth']++;
						$contentStr = call_user_func_array([$this, $chldCfg['event:render']], ['render:element:empty', $contentArr, $alias, $value, $chldKey, $chldCfg, $cfgIndex, $cfgCount, $depth, $reportName, $cfg, $ret, $p, $o]);
						$contentArr[]= $contentStr;
					}
					else {
						if ($this->getCfg('nonExistingValues:show') && $this->getCfg('emptyValues:show') && $this->getCfg('emptyLists:show')) {
							$contentStr = sprintf("%s\"%s\": []",
								$this->generateIndent($depth),
								$alias
							);
							$contentArr[]= $contentStr;
						}
					}
				}
			}

			if (!$handled) {	// normal child, one value in it
				if (array_key_exists($chldKey, $ret)) {
					$o = $options;
					list($contentStr, $handled) = $this->generateFieldData($alias, $ret[$chldKey], $chldKey, $chldCfg, $cfgIndex, $cfgCount, $depth, $reportName, $cfg, $ret, $parents, $o);
					if ($handled) {
						$contentArr[]= $contentStr;
					}
				}
				else {	// child not in the ret data
					$handled = true;
					if ($this->getCfg('nonExistingValues:show') && $this->getCfg('emptyValues:show')) {
						$value = null;	// report non-existing values, let the exporter decide if and how to handle them
						list ($outputValue, $showValue) = $this->formatTreeValue($reportName, $chldKey, [ $chldKey=>$value, ], $chldCfg, $parents, $options);
						if ($showValue) {
							$contentStr = sprintf("%s\"%s\": %s",
								$this->generateIndent($depth),
								$alias,
								$outputValue
							);
							$contentArr[]= $contentStr;
						}
					}
					// 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));
			}
		}

		$content.= sprintf("%s{\n",
			$this->generateIndent($depth, true)
		);

		// first, filter the list of values to render, in order to put the delimiters as needed
		$contentArr = array_filter($contentArr);
		$contentArrCount = count($contentArr);
		foreach ($contentArr as $contentArrIdx=>$contentStr) {
			$content.= sprintf("%s%s\n", $contentStr, $this->generateFieldDelimiter($contentArrIdx, $contentArrCount));
		}
		$content.= sprintf("%s}%s\n",
			$this->generateIndent($depth, true),
			$this->generateFieldDelimiter(0, 1)
		);

		return $content;
	}

	protected function generateIndent($depth, $halfIndent=false)
	{
		if ($halfIndent) {
			if ($depth) {
				return str_repeat(" ", (int)(($depth + 1)*4 - 2));
			}
			return "";
		}

		return str_repeat(" ", (int)($depth + 1)*4);
	}

	protected function generateFieldDelimiter($fieldIndex, $fieldCount)
	{
		if ($fieldIndex == $fieldCount - 1) {
			return "";
		}
		return ",";
	}


	protected function generateFieldData(string $alias, $value, string $chldKey, array $chldCfg, int $cfgIndex, int $cfgCount, int $depth, string $reportName, array $cfg, array $ret, array $parents, array $options)
	{
		$contentStr = null;
		$handled = false;
		if (!$handled && $chldCfg['children'] && $chldCfg['repeatable']) {
			if ($ret && !is_array($value)) {
				throw new \Exception(sprintf("The child '%s' is not a list, and the exporter expects a numeric list.", $chldKey));
			}

			$handled = true;
			if ($value) {
				if ($chldCfg['event:render']) {
					$o = $options;
					$o['depth']++;
					$contentStr = call_user_func_array([$this, $chldCfg['event:render']], ['render:list:element', [], $alias, $value, $chldKey, $chldCfg, $cfgIndex, $cfgCount, $depth, $reportName, $cfg, $ret, $parents, $o]);

				}
				else {
					$contentStr = sprintf("%s\"%s\": [\n",
						$this->generateIndent($depth),
						$alias
					);
					$valuesIndex = -1;
					$valuesCount = count($value);
					foreach ($value as $v) {
						$valuesIndex++;
						$o = $options;
						$o['depth']++;
						$contentStr.= sprintf("%s%s\n",
							rtrim($this->fillTreeStructure($reportName, "", $chldCfg['children'], $v, $parents, $o), "\n"),
							$this->generateFieldDelimiter($valuesIndex, $valuesCount)
						);
					}
					$contentStr.= sprintf("%s]", $this->generateIndent($depth));
				}
			}
			else {
				// we are reporting an empty list
				$value = [];
				if ($this->getCfg('emptyLists:show')) {
					if ($chldCfg['event:render']) {
						$o = $options;
						$o['depth']++;
						$contentStr = call_user_func_array([$this, $chldCfg['event:render']], ['render:list:empty', [], $alias, $value, $chldKey, $chldCfg, $cfgIndex, $cfgCount, $depth, $reportName, $cfg, $ret, $parents, $o]);
					}
					else {
						$contentStr = sprintf("%s\"%s\": []",
							$this->generateIndent($depth),
							$alias
						);
					}
				}
			}
		}


		if (!$handled) {
			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->formatTreeValue($reportName, $chldKey, [ $chldKey=>$value, ], $chldCfg, $parents, $options);
			$handled = true;
			if ($showValue) {
				$contentStr = sprintf("%s\"%s\": %s",
					$this->generateIndent($depth),
					$alias,
					$outputValue
				);
			}
		}

		return [$contentStr, $handled];
	}
}
