<?php
/**
 * core.framework
 *
 * @category  Core
 * @package   Core_Module
 * @copyright Copyright (c) 2013. Burza d.o.o. (http://web.burza.hr/en/)
 * @license   proprietary
 */

/**
 * @category  Core
 * @package   Core_Module
 * @copyright Copyright (c) 2013. Burza d.o.o. (http://web.burza.hr/en/)
 * @license   proprietary
 */
class Core_Module_ResourceFinder
{
    /**
     * @var string
     */
    protected $_modulesDir;

    /**
     * @var string
     */
    protected $_appRootDir;

    protected $_patterns = array(
        'modulepath'              => '{modulesDir|raw}/{module|normalize}',
        'controllerpath'          => '{modulePath|raw}/{controller}Controller.php',
        'controllerclass'         => '{module}_{controller}Controller',
        'controlleraction'        => '{action}Action',
        'templatespath'           => '{modulePath|raw}/templates',
        'template'                => '{controller|normalize}/{action|normalize}.tpl',
        'form'                    => '{modulePath|raw}/forms/{controller|normalize}/{name|raw}',
        'sharedform'              => '{appRootDir|raw}/shared/forms/{name|raw}',
        'angularresource'         => '{controller|normalize}/{ngmodule|raw}/{resource|raw}.tpl',
        'angularmodules'          => '{templatesPath|raw}/{controller|normalize}',
        'angularcontrollersuffix' => 'Controller.js.tpl',
        'angularcontrollerglob'   => '{AngularModules|raw}/{ngmodule|normalize}/*{AngularControllerSuffix|raw}',
        'angulardirectivesuffix'  => 'Directive.js.tpl',
        'angulardirectiveglob'    => '{AngularModules|raw}/{ngmodule|normalize}/*{AngularDirectiveSuffix|raw}',
    );

    /**
     * @param array|Zend_Config $options
     */
    public function __construct($options = null)
    {
        if (is_array($options)) {
            $this->setOptions($options);
        } elseif ($options instanceof Zend_Config) {
            $this->setConfig($options);
        }
    }

    /**
     * @param string $appRootDir
     *
     * @return \Core_Module_ResourceFinder
     */
    public function setAppRootDir($appRootDir)
    {
        $this->_appRootDir = $appRootDir;
        return $this;
    }

    /**
     * @return string
     */
    public function getAppRootDir()
    {
        if (null === $this->_appRootDir) {
            $this->setAppRootDir(Core_Application::getInstance()->getRoot());
        }
        return $this->_appRootDir;
    }

    /**
     * @param string $modulesDir
     *
     * @return Core_Module_ResourceFinder
     * @throws InvalidArgumentException If modulesDir is not a valid directory.
     */
    public function setModulesDir($modulesDir)
    {
        if (!file_exists($modulesDir)) {
            throw new InvalidArgumentException(sprintf('Failed setting modules dir %s, it does not exist', $modulesDir));
        } else if (!is_dir($modulesDir)) {
            throw new InvalidArgumentException(sprintf('Failed setting modules dir %s, it is not a directory', $modulesDir));
        }
        $this->_modulesDir = rtrim($modulesDir, '\\/');
        return $this;
    }

    /**
     * @return string
     * @throws InvalidArgumentException If modulesDir is empty.
     */
    public function getModulesDir()
    {
        if (null === $this->_modulesDir) {
            throw new InvalidArgumentException('Failed to fetch modules dir, not set');
        }
        return $this->_modulesDir;
    }

    /**
     * @param array $patterns
     *
     * @return \Core_Module_ResourceFinder
     */
    public function setPatterns(array $patterns)
    {
        foreach ($patterns as $name => $value)  {
            $this->setPattern($name, $value);
        }
        return $this;
    }

    /**
     * @return array
     */
    public function getPatterns()
    {
        return $this->_patterns;
    }

    /**
     * @param string $name  Pattern name
     * @param string $value Pattern
     *
     * @return \Core_Module_ResourceFinder
     */
    public function setPattern($name, $value)
    {
        $this->_patterns[strtolower($name)] = $value;
        return $this;
    }

    /**
     * @param string $name
     *
     * @return string
     */
    public function getPattern($name)
    {
        $name = strtolower($name);
        if (!array_key_exists($name, $this->_patterns)) {
            throw new InvalidArgumentException(sprintf('Failed fetching pattern %s, no such pattern defined', $name));
        }
        return $this->_patterns[$name];
    }

    /**
     * @param array $options
     *
     * @return Core_Module_ResourceFinder
     * @throws InvalidArgumentException
     */
    public function setOptions(array $options)
    {
        $forbidden = array('Options');

        foreach ($options as $key => $value) {
            $normalized = ucfirst(strtolower($key));
            if (in_array($normalized, $forbidden)) {
                continue;
            }

            $method     = 'set' . $normalized;
            $callable   = array($this, $method);
            if (method_exists($this, $method) && is_callable($callable)) {
                call_user_func($callable, $value);
            } else {
                throw new InvalidArgumentException(sprintf('Invalid option "%s" passed', $key));
            }
        }
        return $this;
    }

    /**
     * @param Zend_Config $config
     *
     * @return Core_Module_ResourceFinder
     */
    public function setConfig(Zend_Config $config)
    {
        return $this->setOptions($config->toArray());
    }

    /**
     * @param string                             $resource Resource type (eg. "template")
     * @param Core_Dispatcher_Location_Interface $location The current location (i.e. where to look for stuff)
     *
     * @return type
     */
    public function find($resource, Core_Dispatcher_Location_Interface $location, array $values = array())
    {
        return $this->_format($this->getPattern($resource), $location, $values);
    }

    /**
     *
     * @param string $name   Method name
     * @param array  $params Method params
     *
     * @return string
     * @throws InvalidArgumentException If calling a method not prefixed with "find"
     */
    public function __call($name, $params)
    {
        // accessing container items!
        // findTemplate($param) => find('template', $param)
        // ...
        if (0 === strpos($name, 'find')) {
            array_unshift($params, strtolower(substr($name, 4)));
            return call_user_func_array(array($this, 'find'), $params);
        } else if (preg_match('/^(?<action>set|get)(?<name>.+)Pattern$/i', $name, $matches)) {
            switch(strtolower($matches['action'])) {
                case 'get':
                    $response = $this->getPattern($matches['name']);
                    break;
                case 'set':
                    $response = $this->setPattern($matches['name'], current($params));
                    break;
            }
            return $response;
        }

        // no such method!
        throw new BadMethodCallException(sprintf('Call to undefined method %s::%s()', get_class($this), $name));
    }

    /**
     * @param Core_Dispatcher_Location_Interface $location
     *
     * @return string
     */
    protected function _format($format, Core_Dispatcher_Location_Interface $location, array $values = array())
    {
        if (!preg_match_all('/{(?P<name>[^|}]+)(?:\|(?<filter>[^}]+))?}/s', $format, $tokens, PREG_SET_ORDER)) {
            return $format;
        }

        $search  = array();
        $replace = array();
        foreach($tokens as $token) {
            $name             = $token['name'];
            $method           = 'get'. ucfirst($name);
            $locationCallback = array($location, $method);
            $selfCallback     = array($this, $method);

            if (array_key_exists($name, $values)) {
                $value = $values[$name];
            } else if (is_callable($locationCallback)) {
                $value = call_user_func($locationCallback);
            } else if (is_callable($selfCallback) && method_exists($this, $method)) {
                $value = call_user_func($selfCallback);
            } else {
                $value = $this->find($name, $location);
            }

            $filter           = '_'. (isset($token['filter']) ? $token['filter'] : 'capitalize');
            $filterCallback   = array($this, $filter);
            if (!method_exists($this, $filter) || !is_callable($filterCallback)) {
                throw new InvalidArgumentException(sprintf('Unknown %s filter %s', $token['name'], $filter));
            }

            $search[]  = current($token);
            $replace[] = call_user_func($filterCallback, $value);
        }

        return str_replace($search, $replace, $format);
    }

    /**
     * @param string $string
     *
     * @return string
     */
    protected function _capitalize($string)
    {
        return str_replace(' ', '', ucwords(str_replace('-', ' ', strtolower($this->_sanetize($string)))));
    }

    /**
     * @param string $string
     *
     * @return string
     */
    protected function _normalize($string)
    {
        return strtolower(str_replace(' ', '-', $this->_sanetize($string)));
    }

    /**
     * @param string $string
     *
     * @return string
     */
    protected function _sanetize($string)
    {
        return preg_replace('/[^a-zA-Z0-9-_ ]/', '', $string);
    }

    /**
     * @param string $string
     *
     * @return string
     */
    protected function _raw($string)
    {
        return $string;
    }
}
