<?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\Configurator\Items;

use InvalidArgumentException;
use s9e\TextFormatter\Configurator\Collections\AttributeCollection;
use s9e\TextFormatter\Configurator\Collections\AttributePreprocessorCollection;
use s9e\TextFormatter\Configurator\Collections\Ruleset;
use s9e\TextFormatter\Configurator\Collections\TagFilterChain;
use s9e\TextFormatter\Configurator\ConfigProvider;
use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
use s9e\TextFormatter\Configurator\Items\Template;
use s9e\TextFormatter\Configurator\Traits\Configurable;

/**
* @property AttributeCollection $attributes This tag's attributes
* @property AttributePreprocessorCollection $attributePreprocessors This tag's attribute parsers
* @property TagFilterChain $filterChain This tag's filter chain
* @property integer $nestingLimit Maximum nesting level for this tag
* @property Ruleset $rules Rules associated with this tag
* @property integer $tagLimit Maximum number of this tag per message
* @property-read Template $template Template associated with this tag
* @property-write string|Template $template Template associated with this tag
*/
class Tag implements ConfigProvider
{
	use Configurable;

	/**
	* @var AttributeCollection This tag's attributes
	*/
	protected $attributes;

	/**
	* @var AttributePreprocessorCollection This tag's attribute parsers
	*/
	protected $attributePreprocessors;

	/**
	* @var TagFilterChain This tag's filter chain
	*/
	protected $filterChain;

	/**
	* @var integer Maximum nesting level for this tag
	*/
	protected $nestingLimit = 10;

	/**
	* @var Ruleset Rules associated with this tag
	*/
	protected $rules;

	/**
	* @var integer Maximum number of this tag per message
	*/
	protected $tagLimit = 5000;

	/**
	* @var Template Template associated with this tag
	*/
	protected $template;

	/**
	* Constructor
	*
	* @param  array $options This tag's options
	*/
	public function __construct(?array $options = null)
	{
		$this->attributes             = new AttributeCollection;
		$this->attributePreprocessors = new AttributePreprocessorCollection;
		$this->filterChain            = new TagFilterChain;
		$this->rules                  = new Ruleset;

		// Start the filterChain with the default processing
		$this->filterChain->append('s9e\\TextFormatter\\Parser\\FilterProcessing::executeAttributePreprocessors')
		                  ->addParameterByName('tagConfig')
		                  ->setJS('executeAttributePreprocessors');

		$this->filterChain->append('s9e\\TextFormatter\\Parser\\FilterProcessing::filterAttributes')
		                  ->addParameterByName('tagConfig')
		                  ->addParameterByName('registeredVars')
		                  ->addParameterByName('logger')
		                  ->setJS('filterAttributes');

		if (isset($options))
		{
			// Sort the options by name so that attributes are set before the template, which is
			// necessary to evaluate whether the template is safe
			ksort($options);

			foreach ($options as $optionName => $optionValue)
			{
				$this->__set($optionName, $optionValue);
			}
		}
	}

	/**
	* {@inheritdoc}
	*/
	public function asConfig()
	{
		$vars = get_object_vars($this);

		// Remove properties that are not needed during parsing
		unset($vars['template']);

		// If there are no attribute preprocessors defined, we can remove the step from this tag's
		// filterChain
		if (!count($this->attributePreprocessors))
		{
			$callback = 's9e\\TextFormatter\\Parser\\FilterProcessing::executeAttributePreprocessors';

			// We operate on a copy of the filterChain, without modifying the original
			$filterChain = clone $vars['filterChain'];

			// Process the chain in reverse order so that we don't skip indices
			$i = count($filterChain);
			while (--$i >= 0)
			{
				if ($filterChain[$i]->getCallback() === $callback)
				{
					unset($filterChain[$i]);
				}
			}

			$vars['filterChain'] = $filterChain;
		}

		return ConfigHelper::toArray($vars) + ['attributes' => [], 'filterChain' => []];
	}

	/**
	* Return this tag's template
	*
	* @return Template
	*/
	public function getTemplate()
	{
		return $this->template;
	}

	/**
	* Test whether this tag has a template
	*
	* @return bool
	*/
	public function issetTemplate()
	{
		return isset($this->template);
	}

	/**
	* Set this tag's attribute preprocessors
	*
	* @param  array|AttributePreprocessorCollection $attributePreprocessors 2D array of [attrName=>[regexp]], or an instance of AttributePreprocessorCollection
	* @return void
	*/
	public function setAttributePreprocessors($attributePreprocessors)
	{
		$this->attributePreprocessors->clear();
		$this->attributePreprocessors->merge($attributePreprocessors);
	}

	/**
	* Set this tag's nestingLimit
	*
	* @param  integer $limit
	* @return void
	*/
	public function setNestingLimit($limit)
	{
		$limit = (int) $limit;

		if ($limit < 1)
		{
			throw new InvalidArgumentException('nestingLimit must be a number greater than 0');
		}

		$this->nestingLimit = $limit;
	}

	/**
	* Set this tag's rules
	*
	* @param  array|Ruleset $rules 2D array of rule definitions, or instance of Ruleset
	* @return void
	*/
	public function setRules($rules)
	{
		$this->rules->clear();
		$this->rules->merge($rules);
	}

	/**
	* Set this tag's tagLimit
	*
	* @param  integer $limit
	* @return void
	*/
	public function setTagLimit($limit)
	{
		$limit = (int) $limit;

		if ($limit < 1)
		{
			throw new InvalidArgumentException('tagLimit must be a number greater than 0');
		}

		$this->tagLimit = $limit;
	}

	/**
	* Set the template associated with this tag
	*
	* @param  string|Template $template
	* @return void
	*/
	public function setTemplate($template)
	{
		if (!($template instanceof Template))
		{
			$template = new Template($template);
		}

		$this->template = $template;
	}

	/**
	* Unset this tag's template
	*
	* @return void
	*/
	public function unsetTemplate()
	{
		unset($this->template);
	}
}