<?php
/**
 * Please see weepie-framework.php for more details.
 */

namespace WpieFw\Templates;

use WpieFw\Exceptions\WpieRuntimeException;
use WpieFw\Templates\Files\WpieTemplatesFileFinder;

if( ! defined( 'ABSPATH' ) ) exit;

/**
 * Final Class WpieTemplate
 *
 * Class for handling template related action. Templates can have a parent -> child relation.
 * Child templates inherit parent vars
 *
 * Main functionality is:
 * - Adding tags to the template like {tag}.
 * - Adding vars to the template
 * - Rendering the template file
 *
 * @author $Author: Vincent Weber <weepie-plugins@outlook.com> $
 *
 * @since 0.1
 */
final class WpieTemplate implements WpieTemplateInterface
{
	/**
	 * The name of the template
	 *
	 * @since 0.1
	 *
	 * @var string
	 */
	private $name;

	/**
	 * The base file path
	 *
	 * @since 0.1
	 *
	 * @var string
	 */
	private $basePath;

	/**
	 * The filename
	 *
	 * @since 0.1
	 *
	 * @var string
	 */
	private $fileName;

	/**
	 * The full path to the file
	 *
	 * @since 0.1
	 *
	 * @var string
	 */
	private $filePath = '';

	/**
	 * Holder for the template vars
	 *
	 * @since 0.1
	 *
	 * @var array
	 */
	private $vars = [];

	/**
	 * Holder for the template tags
	 *
	 * @since 0.1
	 *
	 * @var array
	 */
	private $tags = [];

	/**
	 * Attached parent WpieTemplate instance
	 *
	 * @since 0.1
	 *
	 * @var bool
	 */
	private $parent = false;

	/**
	 * Flag if template has a parent
	 *
	 * @since 0.1
	 *
	 * @var bool
	 */
	private $hasParent = false;

	/**
	 * Flag if template is a child
	 *
	 * @since 0.1
	 *
	 * @var bool
	 */
	private $isChild = false;

	/**
	 * Attached child WpieTemplate instances
	 *
	 * @since 0.1
	 *
	 * @var array
	 */
	private $childs = [];

	/**
	 * Flag if template has child(s)
	 *
	 * @since 0.1
	 *
	 * @var bool
	 */
	private $hasChilds = false;

	/**
	 * Content of the template
	 *
	 * This will hold the template content if rendering is set to save
	 *
	 * @since 0.1
	 *
	 * @var string
	 */
	private $content;

	/**
	 * WpieTemplatesFileFinder instance
	 *
	 * @var WpieTemplatesFileFinder
	 */
	private $finder;

	/**
	 * Constructor
	 *
	 * @access public
	 *
	 * @param WpieTemplatesFileFinder $finder
	 * @param string $name
	 * @param bool $createFileIfNotExist
	 *
	 * @throws WpieRuntimeException
	 *
	 * @since 0.1
	 */
	public function __construct( WpieTemplatesFileFinder $finder, $name = '', $createFileIfNotExist = false )
	{
		$this->finder = $finder;
		$this->name = $name;

		// find file(s)
		$this->finder->findFiles( null, $createFileIfNotExist );

		if( !$this->finder->filesFound ) {
			throw new WpieRuntimeException( sprintf( 'Template file does not exist on path "%s".' , $this->finder->getSearchPath() ) );
		}
		// because only one template file is needed, finding multiple files is not possible
		if( 1 < $this->finder->filesCount ) {
			throw new WpieRuntimeException( sprintf( 'Multiple template files for "%s" found while searching for 1 file.' , $name ) );
		}

		// get a WpieFile instance based on the founded template file
		$wpieFile = $this->finder->getFile();

		// if having a file, set some class properties
		if( $wpieFile ) {
			$this->basePath = rtrim( $this->finder->getPath(), DIRECTORY_SEPARATOR );
			$this->fileName = $wpieFile->getFilename();
			$this->filePath = $wpieFile->getPathname();
		}
	}

	/**
	 * Add a single var to the vars holder
	 *
	 * @access public
	 *
	 * @param string $key
	 * @param mixed $value
	 *
	 * @since 0.1
	 *
	 * @return \WpieFw\Templates\WpieTemplate
	 */
	public function setVar( string $key, $value )
	{
		$this->vars[$key] = $value;

		return $this;
	}

	/**
	 * Add an array to the vars holder
	 *
	 * @access public
	 *
	 * @param array $vars
	 *
	 * @since 0.1
	 *
	 * @return \WpieFw\Templates\WpieTemplate
	 */
	public function setVars( array $vars )
	{
		if( is_array( $vars ) ) {
			$this->vars = array_merge( $this->vars, $vars );
		}

		return $this;
	}

	/**
	 * Set the parent WpieTemplate instance
	 *
	 * Some flags are also set
	 *
	 * @access public
	 *
	 * @param WpieTemplate $parent
	 *
	 * @since 0.1
	 *
	 * @return \WpieFw\Templates\WpieTemplate
	 */
	public function setParent( WpieTemplate $parent )
	{
		$this->hasParent = true;
		$this->parent = $parent;

		return $this;
	}

	/**
	 * Add a WpieTemplate instance to the childs holder
	 *
	 * Some flags are also set
	 *
	 * @access public
	 *
	 * @param WpieTemplate $child
	 *
	 * @since 0.1
	 *
	 * @return \WpieFw\Templates\WpieTemplate
	 */
	public function setChild( WpieTemplate $child )
	{
		$child->isChild = true;
		$child->setParent( $this );
		$this->hasChilds = true;
		$this->childs[$child->getName()] = $child;

		return $this;
	}

	/**
	 * Add a template tag to the tags holder
	 *
	 * Tags are placeholders for substituting data (variables) in the template
	 * The tag keys have the format: {tag}
	 *
	 * @access public
	 *
	 * @param string $key
	 * @param string $value
	 *
	 * @since 0.1
	 *
	 * @return \WpieFw\Templates\WpieTemplate
	 */
	public function setTag( $key = '', $value = '' )
	{
		$this->tags[$key] = $value;

		return $this;
	}

	/**
	 * Get the template name
	 *
	 * @access public
	 *
	 * @since 0.1
	 *
	 * @return string the template name
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * Get all template vars
	 *
	 * @access public
	 *
	 * @since 0.1
	 *
	 * @return array with vars
	 */
	public function getVars()
	{
		return $this->vars;
	}

	/**
	 * Get a single var from the vars holder
	 *
	 * @access public
	 *
	 * @param string $key
	 *
	 * @since 0.1
	 *
	 * @return mixed
	 */
	public function getVar( $key = '' )
	{
		return $this->vars[$key];
	}

	/**
	 * Get all template tags
	 *
	 * @access public
	 *
	 * @since 0.1
	 *
	 * @return array with tags
	 */
	public function getTags()
	{
		return $this->tags;
	}

	/**
	 * Get a single tag from the tags holder
	 *
	 * @access public
	 *
	 * @param string $key
	 *
	 * @since 0.1
	 *
	 * @return string the tag value
	 */
	public function getTag( $key = '' )
	{
		return $this->tags[$key];
	}

	/**
	 * Get attached child templates
	 *
	 * @access public
	 *
	 * @since 0.1
	 *
	 * @return array with child templates
	 */
	public function getChilds()
	{
		return $this->childs;
	}

	/**
	 * Get a single attached child template
	 *
	 * @access public
	 *
	 * @param string $key
	 *
	 * @since 0.1
	 *
	 * @return WpieTemplate instance
	 */
	public function getChild( $key = '' )
	{
		return $this->childs[$key];
	}

	/**
	 * Get template content
	 *
	 * @access public
	 *
	 * @since 0.1
	 *
	 * @return string
	 */
	public function getContent()
	{
		return $this->content;
	}

	/**
	 * Render the template content
	 *
	 * @access public
	 *
	 * @param bool $exit
	 * @param bool $return
	 * @param bool $save
	 * @param bool $removeNewLines
	 *
	 * @return string when $return is set to true
	 *
	 * @since 0.1
	 */
	public function render( $exit = false, $return = false, $save = false, $removeNewLines = false )
	{
		$path = $this->getFilePath();
		$vars = $this->getVars();

		if( true === $this->isChild ) {
			$parentVars = $this->parent->getVars();
			$vars = array_merge( $vars, $parentVars );
		}
		if( is_array( $vars ) ) {
			extract( $vars );
		}
		if( true === $this->hasChilds ) {
			$childTemplates = $this->getChilds();
		}

		ob_start();
		include $path;
		$template = ob_get_contents();
		// @since 1.2 use ob_get_clean()
		// instead of ob_end_clean()
		ob_get_clean();

		$this->substituteTags( $template );
		$this->escape( $template );

		if( $removeNewLines ) {
			$this->removeNewLines( $template );
		}
		if( true === $save ) {
			$this->content = $template;
		}

		unset( $vars );

		if( false === $return ) {
			echo $template;
			if( true === $exit ) {
				exit();
			}
		} else {
			return $template;
		}
	}

	/**
	 * Get the template path
	 *
	 * @access private
	 *
	 * @since 0.1
	 *
	 * @return string the base path
	 */
	private function getBasePath()
	{
		return $this->basePath;
	}

	/**
	 * Get the complete path to the template file
	 *
	 * @access private
	 *
	 * @since 0.1
	 *
	 * @return string the file path
	 */
	private function getFilePath()
	{
		if( '' === $this->filePath ) {
			return $this->finder->getPath() . DIRECTORY_SEPARATOR . $this->finder->getFileName();
		} else {
			return $this->filePath;
		}
	}

	/**
	 * Get the template file name
	 *
	 * @access private
	 *
	 * @since 0.1
	 *
	 * @return string the file name
	 */
	private function getFileName()
	{
		return $this->fileName;
	}

	/**
	 * Remove new line characters
	 *
	 * @access private
	 *
	 * @param string $template
	 *
	 * @since
	 */
	private function removeNewLines( &$template )
	{
		$template = trim( $template );
		$template = trim( preg_replace( '/\s\s+/', ' ', $template ) );
	}

	/**
	 * Apply some cleaning up to the template content (passed by reference)
	 *
	 * @param string $template the Template passed by refernce
	 *
	 * @uses trim()
	 * @uses preg_replace()
	 *
	 * @since 0.1
	 */
	private function escape( &$template )
	{
		$template = trim( $template );
		$template = preg_replace( '/>\s+</', '><', $template );
	}

	/**
	 * Substitute the template tags in the template (passed by reference)
	 *
	 * @access private
	 *
	 * @param string $template the Template passed by refernce
	 *
	 * @uses str_replace()
	 *
	 * @since 0.1
	 */
	private function substituteTags( &$template )
	{
		$tags = $this->getTags();

		if( !empty( $tags ) ) {
			$tagNames = $tagValues = [];

			foreach( $tags as $name => $value ) {
				$tagNames[] = '{'.$name.'}';
				$tagNames[] = '%'.$name.'%';
				$tagValues[] = $value;
				$tagValues[] = $value;
			}

			$template = str_replace( $tagNames, $tagValues, $template );
		}
	}
}