<?php

/**
* @package   s9e\TextFormatter
* @copyright Copyright (c) 2010-2020 The s9e authors
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Helpers;

use RuntimeException;
use s9e\TextFormatter\Configurator\ConfigProvider;
use s9e\TextFormatter\Configurator\FilterableConfigValue;
use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
use Traversable;

abstract class ConfigHelper
{
	/**
	* Recursively filter a config array to replace variants with the desired value
	*
	* @param  array  $config Config array
	* @param  string $target Target parser
	* @return array          Filtered config
	*/
	public static function filterConfig(array $config, $target = 'PHP')
	{
		$filteredConfig = [];
		foreach ($config as $name => $value)
		{
			if ($value instanceof FilterableConfigValue)
			{
				$value = $value->filterConfig($target);
				if (!isset($value))
				{
					continue;
				}
			}
			if (is_array($value))
			{
				$value = self::filterConfig($value, $target);
			}
			$filteredConfig[$name] = $value;
		}

		return $filteredConfig;
	}

	/**
	* Generate a quickMatch string from a list of strings
	*
	* This is basically a LCS implementation, tuned for small strings and fast failure
	*
	* @param  array $strings Array of strings
	* @return mixed          quickMatch string, or FALSE if none could be generated
	*/
	public static function generateQuickMatchFromList(array $strings)
	{
		foreach ($strings as $string)
		{
			$stringLen  = strlen($string);
			$substrings = [];

			for ($len = $stringLen; $len; --$len)
			{
				$pos = $stringLen - $len;

				do
				{
					$substrings[substr($string, $pos, $len)] = 1;
				}
				while (--$pos >= 0);
			}

			if (isset($goodStrings))
			{
				$goodStrings = array_intersect_key($goodStrings, $substrings);

				if (empty($goodStrings))
				{
					break;
				}
			}
			else
			{
				$goodStrings = $substrings;
			}
		}

		if (empty($goodStrings))
		{
			return false;
		}

		// The strings are stored by length descending, so we return the first in the list
		return strval(key($goodStrings));
	}

	/**
	* Optimize the size of a deep array by deduplicating identical structures
	*
	* This method is meant to be used on a config array which is only read and never modified
	*
	* @param  array &$config
	* @param  array &$cache
	* @return array
	*/
	public static function optimizeArray(array &$config, array &$cache = [])
	{
		foreach ($config as $k => &$v)
		{
			if (!is_array($v))
			{
				continue;
			}

			// Dig deeper into this array
			self::optimizeArray($v, $cache);

			// Look for a matching structure
			$cacheKey = serialize($v);
			if (!isset($cache[$cacheKey]))
			{
				// Record this value in the cache
				$cache[$cacheKey] = $v;
			}

			// Replace the entry in $config with a reference to the cached value
			$config[$k] =& $cache[$cacheKey];
		}
		unset($v);
	}

	/**
	* Convert a structure to a (possibly multidimensional) array
	*
	* @param  mixed $value
	* @param  bool  $keepEmpty Whether to keep empty arrays instead of removing them
	* @param  bool  $keepNull  Whether to keep NULL values instead of removing them
	* @return array
	*/
	public static function toArray($value, $keepEmpty = false, $keepNull = false)
	{
		$array = [];

		foreach ($value as $k => $v)
		{
			$isDictionary = $v instanceof Dictionary;
			if ($v instanceof ConfigProvider)
			{
				$v = $v->asConfig();
			}
			elseif ($v instanceof Traversable || is_array($v))
			{
				$v = self::toArray($v, $keepEmpty, $keepNull);
			}
			elseif (is_scalar($v) || is_null($v))
			{
				// Do nothing
			}
			else
			{
				$type = (is_object($v))
				      ? 'an instance of ' . get_class($v)
				      : 'a ' . gettype($v);

				throw new RuntimeException('Cannot convert ' . $type . ' to array');
			}

			if (!isset($v) && !$keepNull)
			{
				// We don't record NULL values
				continue;
			}

			if (!$keepEmpty && $v === [])
			{
				// We don't record empty structures
				continue;
			}

			$array[$k] = ($isDictionary) ? new Dictionary($v) : $v;
		}

		return $array;
	}
}