<?php

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

use s9e\TextFormatter\Parser;

class FilterProcessing
{
	/**
	* Execute all the attribute preprocessors of given tag
	*
	* @private
	*
	* @param  Tag   $tag       Source tag
	* @param  array $tagConfig Tag's config
	* @return void
	*/
	public static function executeAttributePreprocessors(Tag $tag, array $tagConfig)
	{
		if (empty($tagConfig['attributePreprocessors']))
		{
			return;
		}

		foreach ($tagConfig['attributePreprocessors'] as list($attrName, $regexp, $map))
		{
			if ($tag->hasAttribute($attrName))
			{
				self::executeAttributePreprocessor($tag, $attrName, $regexp, $map);
			}
		}
	}

	/**
	* Filter the attributes of given tag
	*
	* @private
	*
	* @param  Tag    $tag            Tag being checked
	* @param  array  $tagConfig      Tag's config
	* @param  array  $registeredVars Array of registered vars for use in attribute filters
	* @param  Logger $logger         This parser's Logger instance
	* @return void
	*/
	public static function filterAttributes(Tag $tag, array $tagConfig, array $registeredVars, Logger $logger)
	{
		$attributes = [];
		foreach ($tagConfig['attributes'] as $attrName => $attrConfig)
		{
			$attrValue = false;
			if ($tag->hasAttribute($attrName))
			{
				$vars = [
					'attrName'       => $attrName,
					'attrValue'      => $tag->getAttribute($attrName),
					'logger'         => $logger,
					'registeredVars' => $registeredVars
				];
				$attrValue = self::executeAttributeFilterChain($attrConfig['filterChain'], $vars);
			}

			if ($attrValue !== false)
			{
				$attributes[$attrName] = $attrValue;
			}
			elseif (isset($attrConfig['defaultValue']))
			{
				$attributes[$attrName] = $attrConfig['defaultValue'];
			}
			elseif (!empty($attrConfig['required']))
			{
				$tag->invalidate();
			}
		}
		$tag->setAttributes($attributes);
	}

	/**
	* Execute a tag's filterChain
	*
	* @private
	*
	* @param  Tag    $tag        Tag to filter
	* @param  Parser $parser     Parser
	* @param  array  $tagsConfig Tags' config
	* @param  Tag[]  $openTags   List of open tags
	* @return void
	*/
	public static function filterTag(Tag $tag, Parser $parser, array $tagsConfig, array $openTags)
	{
		$tagName   = $tag->getName();
		$tagConfig = $tagsConfig[$tagName];

		// Record the tag being processed into the logger it can be added to the context of
		// messages logged during the execution
		$logger = $parser->getLogger();
		$logger->setTag($tag);

		// Prepare the variables that are accessible to filters
		$text = $parser->getText();
		$vars = [
			'innerText'      => '',
			'logger'         => $logger,
			'openTags'       => $openTags,
			'outerText'      => substr($text, $tag->getPos(), $tag->getLen()),
			'parser'         => $parser,
			'registeredVars' => $parser->registeredVars,
			'tag'            => $tag,
			'tagConfig'      => $tagConfig,
			'tagText'        => substr($text, $tag->getPos(), $tag->getLen()),
			'text'           => $text
		];
		$endTag = $tag->getEndTag();
		if ($endTag)
		{
			$vars['innerText'] = substr($text, $tag->getPos() + $tag->getLen(), $endTag->getPos() - $tag->getPos() - $tag->getLen());
			$vars['outerText'] = substr($text, $tag->getPos(), $endTag->getPos() + $endTag->getLen() - $tag->getPos());
		}
		foreach ($tagConfig['filterChain'] as $filter)
		{
			if ($tag->isInvalid())
			{
				break;
			}
			self::executeFilter($filter, $vars);
		}

		// Remove the tag from the logger
		$logger->unsetTag();
	}

	/**
	* Execute an attribute's filterChain
	*
	* @param  array $filterChain Attribute's filterChain
	* @param  array $vars        Callback vars
	* @return mixed              Filtered value
	*/
	protected static function executeAttributeFilterChain(array $filterChain, array $vars)
	{
		$vars['logger']->setAttribute($vars['attrName']);
		foreach ($filterChain as $filter)
		{
			$vars['attrValue'] = self::executeFilter($filter, $vars);
			if ($vars['attrValue'] === false)
			{
				break;
			}
		}
		$vars['logger']->unsetAttribute();

		return $vars['attrValue'];
	}

	/**
	* Execute an attribute preprocessor
	*
	* @param  Tag      $tag
	* @param  string   $attrName
	* @param  string   $regexp
	* @param  string[] $map
	* @return void
	*/
	protected static function executeAttributePreprocessor(Tag $tag, $attrName, $regexp, $map)
	{
		$attrValue = $tag->getAttribute($attrName);
		$captures  = self::getNamedCaptures($attrValue, $regexp, $map);
		foreach ($captures as $k => $v)
		{
			// Attribute preprocessors cannot overwrite other attributes but they can
			// overwrite themselves
			if ($k === $attrName || !$tag->hasAttribute($k))
			{
				$tag->setAttribute($k, $v);
			}
		}
	}

	/**
	* Execute a filter
	*
	* @see s9e\TextFormatter\Configurator\Items\ProgrammableCallback
	*
	* @param  array $filter Programmed callback
	* @param  array $vars   Variables to be used when executing the callback
	* @return mixed         Whatever the callback returns
	*/
	protected static function executeFilter(array $filter, array $vars)
	{
		// Add vars from the registeredVars array to the list of vars
		$vars += ['registeredVars' => []];
		$vars += $vars['registeredVars'];

		// Prepare the list of arguments
		$args = [];
		if (isset($filter['params']))
		{
			foreach ($filter['params'] as $k => $v)
			{
				$args[] = $vars[$k] ?? $v;
			}
		}

		return call_user_func_array($filter['callback'], $args);
	}

	/**
	* Execute a regexp and return the values of the mapped captures
	*
	* @param  string   $str
	* @param  string   $regexp
	* @param  string[] $map
	* @return array
	*/
	protected static function getNamedCaptures($str, $regexp, $map)
	{
		if (!preg_match($regexp, $str, $m))
		{
			return [];
		}

		$values = [];
		foreach ($map as $i => $k)
		{
			if (isset($m[$i]) && $m[$i] !== '')
			{
				$values[$k] = $m[$i];
			}
		}

		return $values;
	}
}