<?php

abstract class Waindigo_Listener_Template
{
	protected static $_copyrightNotice = false;
	
	protected static $copyrightNotice = '<div id="waindigoCopyrightNotice" class="footerLegal" style="clear:both"><div class="pageContent"><span class="muted">Certain <a href="http://www.waindigo.com/" class="concealed">XenForo add-ons by Waindigo</a>&trade; &copy;2011-2012 <a href="http://www.waindigo.com" class="concealed">Waindigo Limited</a>.</span></div></div>';
	
	public static $contentsReplaceArray = array();
	
	/**
	 * Standard approach to caching other model objects for the lifetime of the model.
	 *
	 * @var array
	 */
	protected $_modelCache = array();
	
	protected $_contents = null;
	/**
	* @var XenForo_Template_Abstract
	*/
	protected $_template = null;
	
	/**
	 * @param string $contents
	 * @param XenForo_Template_Abstract $template
	 */
	public function __construct(&$contents, XenForo_Template_Abstract $template = null)
	{
		$this->_contents = $contents;
		if ($template) $this->_template = $template;
	}

	/**
	 * @return string
	 */
	public function run()
	{
		return $this->_contents;
	}
	
	/**
	 * @return string
	 */
	protected function _run()
	{
		try {
			return $this->run();
		} catch (Exception $e) {
			return $this->_contents;
		}
	}
	
	/**
	 * Gets the specified model object from the cache. If it does not exist,
	 * it will be instantiated.
	 *
	 * @param string $class Name of the class to load
	 *
	 * @return XenForo_Model
	 */
	public function getModelFromCache($class)
	{
		if (!isset($this->_modelCache[$class]))
		{
			$this->_modelCache[$class] = XenForo_Model::create($class);
		}
	
		return $this->_modelCache[$class];
	}	

	/**
	 * @param string $rendered
	 * @param string|null $contents
	 * @param boolean $after
	 */
	protected function _append($rendered, &$contents = null, $after = true)
	{
		$this->_appendAtCodeSnippet(null, $rendered, $contents, $after);
	}
	
	/**
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 * @param boolean $after
	 */
	protected function _appendTemplate($templateName, $viewParams = null, &$contents = null, $after = true)
	{
		$rendered = $this->_render($templateName, $viewParams);
		$this->_append($rendered, $contents, $after);
	}

	/**
	 * @param string $slot
	 * @param string $rendered
	 * @param string|null $contents
	 * @param boolean $after
	 */
	protected function _appendAtSlot($slot, $rendered, &$contents = null, $after = true)
	{
		$codeSnippet = "<!-- slot: $slot -->";
		$this->_appendAtCodeSnippet($codeSnippet, $rendered, $contents, $after);
	}

	/**
	 * @param string $contents
	 * @return string contents
	 */
	protected function _utf8Decode($contents = null)
	{
		if (!$contents) $contents = $this->_contents;
		$contents = utf8_encode($contents);
		$contents = preg_replace_callback("/[^\x01-\x7F]+/", create_function('$matches', '
			$key = array_search($matches[0], Waindigo_Listener_Template::$contentsReplaceArray);
			if ($key === false)
			{
				Waindigo_Listener_Template::$contentsReplaceArray[] = $matches[0];
				$key = count(Waindigo_Listener_Template::$contentsReplaceArray) - 1;
			}
			
			return "WAINDIGO_".$key."_REPLACE";'), $contents);
		return $contents;
	}
	
	/**
	 * @param string $contents
	 * @return string contents
	 */
	protected function _utf8Encode($contents = null)
	{
		if (!$contents) $contents = $this->_contents;
		$contents = preg_replace_callback("/WAINDIGO_([0-9]+)_REPLACE/", create_function('$matches', '
			if (isset(Waindigo_Listener_Template::$contentsReplaceArray[$matches[1]]))
			{
				return Waindigo_Listener_Template::$contentsReplaceArray[$matches[1]];
			}
			return "";'), $contents);
		
		$contents = preg_replace('#<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">\s*<html>\s*<body>(.*)</body>\s*</html>#s',
				'$1', $contents);
		$contents = $this->_indentContent(utf8_decode($contents));
		return $contents;
	}
	
	protected function _indentContent($content, $tab="\t")
	{
		// add marker linefeeds to aid the pretty-tokeniser (adds a linefeed between all tag-end boundaries)
		$content = preg_replace('/(>)(<)(\/*)/', "$1\n$2$3", $content);
	
		// now indent the tags
		$token = strtok($content, "\n");
		$result = ''; // holds formatted version as it is built
		$pad = 0; // initial indent
		$matches = array(); // returns from preg_matches()
		$textarea = 0; // no indenting inside textarea
		
		// scan each line and adjust indent based on opening/closing tags
		while ($token !== false)
		{
			$token = trim($token);
			// test for the various tag states
	
			// 1. open and closing tags on same line - no change
			if (preg_match('/.+<\/\w[^>]*>.*$/', $token, $matches)) $indent=0;
			// 2. closing tag - outdent now
			elseif (preg_match('/^<\/(\w+)(.*)>/', $token, $matches))
			{
				if ($matches[1] == "textarea" || $matches[1] == "pre")
				{
					$textarea = -1;
				}
				$pad--;
				if($indent>0) $indent=0;
			}
			// 3. opening tag - don't pad this one, only subsequent tags
			elseif (!$textarea && preg_match('/^<(\w+)(.*)>.*$/', $token, $matches))
			{
				if ($matches[1] == "textarea" || $matches[1] == "pre")
				{
					$textarea = 1;
				}
				if ($matches[1] == "link" || $matches[1] == "meta" || $matches[1] == "img")
				{
					$indent = 0;
				}
				else
				{
					$indent=1;
				}
			}
			// 4. no indentation needed
			else $indent = 0;
	
			if (!$textarea)
			{
				// pad the line with the required number of leading spaces
				$line = str_pad($token, strlen($token)+$pad, $tab, STR_PAD_LEFT);
				$result .= $line."\n"; // add to the cumulative result, with linefeed
			}
			else
			{
				$line = $token;
				$result .= $line."\n";
				$textarea = 0;
			}
			$token = strtok("\n"); // get the next token
			$pad += $indent; // update the pad size for subsequent lines
		}
	
		return $result;
	}
	
	/**
	 * @return boolean true if replaced, false otherwise
	 * @param string $query
	 * @param string $rendered
	 * @param string|null $contents
	 * @param Zend_Dom_Query $dom
	 * @param boolean $after
	 */
	protected function _appendAtDomQuery($query, $rendered, &$contents = null, Zend_Dom_Query &$dom = null, $after = true, $outside = true)
	{
		if (!$contents) $contents =& $this->_contents;
		if (!$contents) return false;
		if ($dom === null) $dom = new Zend_Dom_Query($this->_utf8Decode($contents));
		if (is_array($query))
		{
			$result = $dom->query($query[0]);
		}
		else
		{
			$result = $dom->query($query);
		}
		if ($rendered && $result->count())
		{
			$appendDom = new Zend_Dom_Query($this->_utf8Decode($rendered));
			if (is_array($query))
			{
				$appendQuery = $appendDom->query($query[1]);
			}
			else
			{
				$appendQuery = $appendDom->query($query);
			}
			if ($appendQuery->count())
			{
				$childNodes = $appendQuery->current()->childNodes;
				$firstChild = $result->current()->firstChild;
				foreach ($childNodes as $childNode)
				{
					if ($after)
					{
						$result->current()->appendChild($result->getDocument()->importNode($childNode, true));
					}
					else
					{
						if ($outside)
						{
							$result->current()->parentNode->insertBefore(
								$result->getDocument()->importNode($childNode, true),
								$result->current()
							);
						}
						else
						{
							$result->current()->insertBefore(
									$result->getDocument()->importNode($childNode, true),
									$result->current()->firstChild
							);
						}
					}
				}
				$contents = $this->_utf8Encode($result->getDocument()->saveHTML());
				return true;
			}
		}
		return false;
	}

	/**
	 * @return boolean true if replaced, false otherwise
	 * @param string $query
	 * @param string $rendered
	 * @param string|null $contents
	 * @param Zend_Dom_Query $dom
	 */
	protected function _appendAfterDomQuery($query, $rendered, &$contents = null, Zend_Dom_Query &$dom = null, $outside = true)
	{
		return $this->_appendAtDomQuery($query, $rendered, $contents, $dom, true, true, $outside);
	}
	
	/**
	 * @return boolean true if replaced, false otherwise
	 * @param string $query
	 * @param string $rendered
	 * @param string|null $contents
	 * @param Zend_Dom_Query $dom
	 */
	protected function _appendBeforeDomQuery($query, $rendered, &$contents = null, Zend_Dom_Query &$dom = null, $outside = true)
	{
		return $this->_appendAtDomQuery($query, $rendered, $contents, $dom, false, $outside);
	}
	
	/**
	 * @param string $slot
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 * @param boolean $after
	 */
	protected function _appendTemplateAtSlot($slot, $templateName, $viewParams = null, &$contents = null, $after = true)
	{
		$codeSnippet = "<!-- slot: $slot -->";
		$this->_appendTemplateAtCodeSnippet($codeSnippet, $templateName, $viewParams, $contents, $after);
	}
	
	/**
	 * @param string $slot
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 */
	protected function _appendTemplateBeforeSlot($slot, $templateName, $viewParams = null, &$contents = null)
	{
		$this->_appendTemplateAtSlot($slot, $templateName, $viewParams, $contents, false);
	}

	/**
	 * @param string $slot
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 */
	protected function _appendTemplateAfterSlot($slot, $templateName, $viewParams = null, &$contents = null)
	{
		$this->_appendTemplateAtSlot($slot, $templateName, $viewParams, $contents, true);
	}
	
	/**
	 * @return boolean true if replaced, false otherwise
	 * @param string $query
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 * @param Zend_Dom_Query $dom
	 * @param boolean $after
	 */
	protected function _appendTemplateAtDomQuery($query, $templateName, $viewParams = null, &$contents = null, Zend_Dom_Query &$dom = null, $after = true, $outside = true)
	{
		$rendered = $this->_render($templateName, $viewParams);
		return $this->_appendAtDomQuery($query, $rendered, $contents, $dom, $after, $outside);
	}

	/**
	 * @return boolean true if replaced, false otherwise
	 * @param string $query
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 * @param Zend_Dom_Query $dom
	 */
	protected function _appendTemplateBeforeDomQuery($query, $templateName, $viewParams = null, &$contents = null, Zend_Dom_Query &$dom = null, $outside = true)
	{
		return $this->_appendTemplateAtDomQuery($query, $templateName, $viewParams, $contents, $dom, false, $outside);
	}
	
	/**
	 * @return boolean true if replaced, false otherwise
	 * @param string $query
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 * @param Zend_Dom_Query $dom
	 */
	protected function _appendTemplateAfterDomQuery($query, $templateName, $viewParams = null, &$contents = null, Zend_Dom_Query &$dom = null)
	{
		return $this->_appendTemplateAtDomQuery($query, $templateName, $viewParams, $contents, $dom, true);
	}
	
	/**
	 * @param string $codeSnippet
	 * @param string $rendered
	 * @param string|null $contents
	 * @param boolean $after
	 */
	protected function _appendAtCodeSnippet($codeSnippet, $rendered, &$contents = null, $after = true)
	{
		if (!$contents) $contents =& $this->_contents;
		if ($after)
		{
			if ($codeSnippet)
				$contents = str_replace($codeSnippet, $codeSnippet . "\n" . $rendered, $contents);
			else
			$contents .= "\n" . $rendered;
		}
		else
		{
			if ($codeSnippet)
				$contents = str_replace($codeSnippet, $rendered . "\n" . $codeSnippet, $contents);
			else
				$contents = $rendered . "\n" . $contents;
		}
	}

	/**
	 * @param string $codeSnippet
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 * @param boolean $after
	 */
	protected function _appendTemplateAtCodeSnippet($codeSnippet, $templateName, $viewParams = null, &$contents = null, $after = true)
	{
		$rendered = $this->_render($templateName, $viewParams);
		$this->_appendAtCodeSnippet($codeSnippet, $rendered, $contents, $after);
	}	
	
	/**
	 * @param string $codeSnippet
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 */
	protected function _appendTemplateBeforeCodeSnippet($codeSnippet, $templateName, $viewParams = null, &$contents = null)
	{
		$this->_appendTemplateAtCodeSnippet($codeSnippet, $templateName, $viewParams, $contents, false);
	}

	/**
	 * @param string $codeSnippet
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 */
	protected function _appendTemplateAfterCodeSnippet($codeSnippet, $templateName, $viewParams = null, &$contents = null)
	{
		$this->_appendTemplateAtCodeSnippet($codeSnippet, $templateName, $viewParams, $contents, true);
	}

	/**
	 * @param string $rendered
	 * @param string|null $contents
	 */
	protected function _prepend($rendered, &$contents = null)
	{
		$this->_appendAtCodeSnippet(null, $rendered, $contents, false);
	}
	
	/**
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 */
	protected function _prependTemplate($templateName, $viewParams = null, &$contents = null)
	{
		$this->_appendTemplate($templateName, $viewParams, $contents, false);
	}
	
	/**
	 * @param string $codeSnippet
	 * @param string $rendered
	 * @param string|null $contents
	 */
	protected function _replaceAtCodeSnippet($codeSnippet, $rendered, &$contents = null)
	{
		if (!$contents) $contents =& $this->_contents;
		$contents = str_replace($codeSnippet, $rendered, $contents);
	}
	
	/**
	 * @param Zend_Dom_Query_Result $results
	 * @param Waindigo_Listener_Template $listener
	 * @param array $extraData
	 */
	protected static function _replaceInResults(Zend_Dom_Query_Result $results, Waindigo_Listener_Template $listener, array $extraData)
	{
		$rendered = $extraData['rendered'];
		$query = $extraData['query'];
		$appendDom = new Zend_Dom_Query($rendered);
		if (is_array($query))
		{
			$appendQuery = $appendDom->query($query[1]);
		}
		else
		{
			$appendQuery = $appendDom->query($query);
		}
		if ($appendQuery->count())
		{
			$newnode = $results->getDocument()->importNode($appendQuery->current(), true);
			$results->current()->parentNode->replaceChild($newnode, $results->current());
		}
		return $results;
	}
	
	/**
	 * @return boolean true if replaced, false otherwise
	 * @param string $query
	 * @param string|null $contents
	 * @param Zend_Dom_Query $dom
	 */
	protected function _replaceAtDomQuery($query, $rendered, &$contents = null)
	{
		$this->_replaceCallbackAtDomQuery($query, array($this, "_replaceInResults"), $contents, array('rendered' => $this->_utf8Decode($rendered), 'query' => $query));
	}

	/**
	 * @return boolean true if replaced, false otherwise
	 * @param string $query
	 * @param callback $callback
	 * @param string|null $contents
	 * @param mixed $extraData
	 */
	protected function _replaceCallbackAtDomQuery($query, $callback, &$contents = null, $extraData = null)
	{
		if (!$contents) $contents =& $this->_contents;
		if (!$contents) return false;
		$dom = new Zend_Dom_Query($this->_utf8Decode($contents));
		if (is_array($query))
		{
			$results = $dom->query($query[0]);
		}
		else
		{
			$results = $dom->query($query);
		}
		if ($results->count())
		{
			$results = call_user_func_array($callback, array($results, $this, $extraData));
			$contents = $this->_utf8Encode($results->getDocument()->saveHTML());
		}
		return false;
	}
	
	/**
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 */
	protected function _replaceWithTemplate($templateName, $viewParams = null, &$contents = null)
	{
		if (!$contents) $contents =& $this->_contents;
		$contents = $this->_render($templateName, $viewParams);
	}
	
	/**
	 * @param string $codeSnippet
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 */
	protected function _replaceWithTemplateAtCodeSnippet($codeSnippet, $templateName, $viewParams = null, &$contents = null)
	{
		$rendered = $this->_render($templateName, $viewParams);
		$this->_replaceAtCodeSnippet($codeSnippet, $rendered, $contents);
	}

	/**
	 * @param string $title
	 * @param string|null $contents
	 */
	protected function _replaceTitle($title, &$contents = null)
	{
		if (!$contents) $contents =& $this->_contents;
		$contents = preg_replace("%<xen:title>.*</xen:title>%", "<xen:title>".$title."</xen:title>", $contents);
	}

	/**
	 * @return array
	 */
	protected function _fetchViewParams()
	{
		if (!$this->_template) return array();
		return $this->_template->getParams();
	}
	
	/**
	 * @return string
	 * @param string $templateName
	 * @param array|null $viewParams
	 */
	protected function _render($templateName, $viewParams = null)
	{
		if (!$this->_template) return '';
		if (!$viewParams) $viewParams = $this->_fetchViewParams();
		return $this->_template->create($templateName, $viewParams)->render();
	}
	
	/**
	 * @return boolean true if replaced, false otherwise
	 * @param string $query
	 * @param string|null $contents
	 * @param Zend_Dom_Query $dom
	 */
	protected function _removeAtDomQuery($query, &$contents = null, Zend_Dom_Query &$dom = null)
	{
		if (!$contents) $contents =& $this->_contents;
		if (!$contents) return false;
		if (!$dom) $dom = new Zend_Dom_Query($this->_utf8Decode($contents));
		$results = $dom->query($query);
		if ($results->count())
		{
			$results->current()->parentNode->removeChild($results->current()->firstChild->parentNode);
			$contents = $this->_utf8Encode($results->getDocument()->saveHTML());
			return true;
		}
		return false;
	}
	
	/**
	 * @return true if successful, false otherwise
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 */
	protected function _appendTemplateAfterTopCtrl($templateName, $viewParams = null, &$contents = null)
	{
		if (!$this->_appendTemplateAfterDomQuery('.topCtrl', $templateName, $viewParams, $contents))
		{
			return $this->_appendTemplateBeforeDomQuery('.breadBoxTop nav, .titleBar h1', $templateName, $viewParams, $contents);
		}
		return true;
	}
	
	/**
	 * @return true if successful, false otherwise
	 * @param string $templateName
	 * @param array|null $viewParams
	 * @param string|null $contents
	 */
	protected function _appendTemplateBeforeTopCtrl($templateName, $viewParams = null, &$contents = null)
	{
		if (!$this->_appendTemplateBeforeDomQuery('.topCtrl', $templateName, $viewParams, $contents))
		{
			return $this->_appendTemplateBeforeDomQuery('.breadBoxTop nav, .titleBar h1', $templateName, $viewParams, $contents);
		}
		return true;
	}
}