<?php

/**
 * This file is part of TwigView.
 *
 ** (c) 2014 Cees-Jan Kiewiet
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace WyriHaximus\TwigView\View;

use Cake\Core\Configure;
use Cake\View\Exception\MissingLayoutException;
use Cake\View\Exception\MissingTemplateException;
use Cake\View\View;
use Exception;
use WyriHaximus\TwigView\Event\ConstructEvent;
use WyriHaximus\TwigView\Event\EnvironmentConfigEvent;
use WyriHaximus\TwigView\Event\LoaderEvent;
use WyriHaximus\TwigView\Lib\Twig\Loader;

/**
 * Class TwigView
 * @package WyriHaximus\TwigView\View
 */
// @codingStandardsIgnoreStart
class TwigView extends View
// @codingStandardsIgnoreEnd
{
    const EXT = '.twig';

    const ENV_CONFIG = 'WyriHaximus.TwigView.environment';

    /**
     * Extension to use.
     *
     * @var string
     */
    // @codingStandardsIgnoreStart
    protected $_ext = self::EXT;
    // @codingStandardsIgnoreEnd

    /**
     * @var array
     */
    protected $extensions = [
        self::EXT,
        '.tpl',
        '.ctp',
    ];

    /**
     * Twig instance.
     *
     * @var \Twig_Environment
     */
    protected $twig;

    /**
     * Initialize view.
     *
     * @return void
     */
    public function initialize()
    {
        $this->twig = new \Twig_Environment($this->getLoader(), $this->resolveConfig());

        $this->getEventManager()->dispatch(ConstructEvent::create($this, $this->twig));

        $this->_ext = self::EXT;

        parent::initialize();
    }

    /**
     * @return array
     */
    protected function resolveConfig()
    {
        $config = [
            'cache' => CACHE . 'twigView' . DS,
            'charset' => strtolower(Configure::readOrFail('App.encoding')),
            'auto_reload' => Configure::read('debug'),
            'debug' => Configure::read('debug'),
        ];

        $config = array_replace($config, $this->readConfig());

        $configEvent = EnvironmentConfigEvent::create($config);
        $this->getEventManager()->dispatch($configEvent);
        return $configEvent->getConfig();
    }

    /**
     * @return array
     */
    protected function readConfig()
    {
        if (!Configure::check(static::ENV_CONFIG)) {
            return [];
        }

        $config = Configure::read(static::ENV_CONFIG);
        if (!is_array($config)) {
            return [];
        }

        return $config;
    }

    /**
     * @param string $extension
     */
    public function unshiftExtension($extension)
    {
        array_unshift($this->extensions, $extension);
    }

    /**
     * Create the template loader.
     *
     * @return \Twig_LoaderInterface
     */
    protected function getLoader()
    {
        $event = LoaderEvent::create(new Loader());
        $this->getEventManager()->dispatch($event);
        return $event->getResultLoader();
    }

    /**
     * Get helper list.
     *
     * @return \Cake\View\Helper[]
     */
    protected function generateHelperList()
    {
        $registry = $this->helpers();
        $helperList = [];

        foreach ($registry->loaded() as $alias) {
            $helperList[$alias] = $registry->get($alias);
        }

        return $helperList;
    }
    /**
     * Render the template.
     *
     * @param string $viewFile Template file.
     * @param array  $data     Data that can be used by the template.
     *
     * @throws \Exception
     * @return string
     */
    // @codingStandardsIgnoreStart
    protected function _render($viewFile, $data = array())
    {
        // @codingStandardsIgnoreEnd
        if (empty($data)) {
            $data = $this->viewVars;
        }

        if (substr($viewFile, -3) === 'ctp') {
            $out = parent::_render($viewFile, $data);
            // @codingStandardsIgnoreStart
        } else {
            // @codingStandardsIgnoreEnd
            $data = array_merge(
                $data,
                $this->generateHelperList(),
                [
                    '_view' => $this,
                ]
            );

            // @codingStandardsIgnoreStart
            try {
                $out = $this->getTwig()->loadTemplate($viewFile)->render($data);
            } catch (Exception $e) {
                $previous = $e->getPrevious();

                if ($previous !== null && $previous instanceof Exception) {
                    throw $previous;
                } else {
                    throw $e;
                }
            }
            // @codingStandardsIgnoreEnd
        }

        return $out;
    }

    /**
     * @param string|null $name
     * @return string
     * @throws \Exception
     */
    // @codingStandardsIgnoreStart
    protected function _getViewFileName($name = null)
    {
        // @codingStandardsIgnoreEnd
        $templatePath = $subDir = '';

        if ($this->subDir !== null) {
            $subDir = $this->subDir . DIRECTORY_SEPARATOR;
        }
        if ($this->templatePath) {
            $templatePath = $this->templatePath . DIRECTORY_SEPARATOR;
        }

        if ($name === null) {
            $name = $this->template;
        }

        list($plugin, $name) = $this->pluginSplit($name);
        $name = str_replace('/', DIRECTORY_SEPARATOR, $name);

        if (strpos($name, DIRECTORY_SEPARATOR) === false && $name[0] !== '.') {
            $name = $templatePath . $subDir . $this->_inflectViewFileName($name);
        } elseif (strpos($name, DIRECTORY_SEPARATOR) !== false) {
            if ($name[0] === DIRECTORY_SEPARATOR || $name[1] === ':') {
                $name = trim($name, DIRECTORY_SEPARATOR);
            } elseif (!$plugin || $this->templatePath !== $this->name) {
                $name = $templatePath . $subDir . $name;
            } else {
                $name = DIRECTORY_SEPARATOR . $subDir . $name;
            }
        }

        foreach ($this->_paths($plugin) as $path) {
            foreach ($this->extensions as $extension) {
                if (file_exists($path . $name . $extension)) {
                    return $this->_checkFilePath($path . $name . $extension, $path);
                }
            }
        }

        throw new MissingTemplateException(['file' => $name . $this->_ext]);
    }

    /**
     * @param string|null $name
     * @return string
     * @throws \Exception
     */
    // @codingStandardsIgnoreStart
    protected function _getLayoutFileName($name = null)
    {
        // @codingStandardsIgnoreEnd
        if ($name === null) {
            $name = $this->layout;
        }
        $subDir = null;

        if ($this->layoutPath) {
            $subDir = $this->layoutPath . DIRECTORY_SEPARATOR;
        }
        list($plugin, $name) = $this->pluginSplit($name);

        $layoutPaths = $this->_getSubPaths('Layout' . DIRECTORY_SEPARATOR . $subDir);

        foreach ($this->_paths($plugin) as $path) {
            foreach ($layoutPaths as $layoutPath) {
                $currentPath = $path . $layoutPath;
                foreach ($this->extensions as $extension) {
                    if (file_exists($currentPath . $name . $extension)) {
                        return $this->_checkFilePath($currentPath . $name . $extension, $currentPath);
                    }
                }
            }
        }
        throw new MissingLayoutException([
            'file' => $layoutPaths[0] . $name . $this->_ext
        ]);
    }

    /**
     * @param string $name
     * @param bool $pluginCheck
     * @return string
     * @throws \Exception
     */
    // @codingStandardsIgnoreStart
    protected function _getElementFileName($name, $pluginCheck = true)
    {
        // @codingStandardsIgnoreEnd
        list($plugin, $name) = $this->pluginSplit($name, $pluginCheck);

        $paths = $this->_paths($plugin);
        $elementPaths = $this->_getSubPaths('Element');

        foreach ($paths as $path) {
            foreach ($elementPaths as $elementPath) {
                foreach ($this->extensions as $extension) {
                    if (file_exists($path . $elementPath . DIRECTORY_SEPARATOR . $name . $extension)) {
                        return $path . $elementPath . DIRECTORY_SEPARATOR . $name . $extension;
                    }
                }
            }
        }

        return false;
    }

    /**
     * Get twig environment instance.
     *
     * @return \Twig_Environment
     */
    public function getTwig()
    {
        return $this->twig;
    }

    /**
     * Return empty string when View instance is cast to string.
     *
     * @return string
     */
    public function __toString()
    {
        return '';
    }
}
