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

/**
 * @category   Core
 * @package    Core_Loader
 * @subpackage PluginLoader
 * @copyright  Copyright (c) 2011. Burza d.o.o. (http://web.burza.hr/en/)
 * @license    proprietary
 */
class Core_Loader_PluginLoader
{
    /**
     * @var array Plugin prefixes
     */
    protected $_prefixes      = array('Core');

    /**
     * @var array Resolved name to plugin cache.
     */
    protected $_resolvedNames = array();

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

    /**
     * @param array $prefixes
     *
     * @return Core_Loader_PluginLoader
     */
    public function setPrefixes(array $prefixes)
    {
        // new prefixes, new plugins!
        $this->_resolvedNames = array();

        $this->_prefixes      = $prefixes;
        return $this;
    }

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

    /**
     * @param array $options
     *
     * @return Core_Loader_PluginLoader
     * @throws InvalidArgumentException if an invalid option passed.
     */
    public function setOptions(Array $options)
    {
        foreach ($options as $key => $value) {
            $normalized = ucfirst($key);

            $method = 'set' . $normalized;
            if (method_exists($this, $method)) {
                $this->$method($value);
            } else {
                throw new InvalidArgumentException(sprintf('Invalid option "%s" specified', $key));
            }
        }
        return $this;
    }

    /**
     * Load plugin by short name.
     *
     * It will go through all the prefixes and try to find the plugin there.
     *
     * @param string $pluginType Short plugin name.
     *
     * @return string Full plugin name, including the prefix.
     */
    public function loadPlugin($pluginType)
    {
        if (array_key_exists($pluginType, $this->_resolvedNames)) {
            // this means the plugin is resolved and loaded
            return $this->_resolvedNames[$pluginType];
        }

        foreach ($this->getPrefixes() as $prefix) {
            $plugin = $this->_separateCamelCase($prefix . $pluginType, '_');
            if (Core_Loader::hasClass($plugin)) {
                Core_Loader::loadClass($plugin);
                $this->_resolvedNames[$pluginType] = $plugin;
                return $plugin;
            }
        }
        throw new UnexpectedValueException(sprintf('Plugin %s could not be found', $pluginType));
    }

    /**
     * Initialize a new plugin by short name.
     *
     * @param string $pluginType Short plugin name
     *
     * @return object Plugin instance.
     */
    public function initializePlugin($pluginType/*, arg1, arg2,...*/)
    {
        $plugin     = $this->loadPlugin($pluginType);
        $args       = func_get_args();
        array_shift($args); // strip plugin type from args

        // create the new instance and return it
        if (!empty($args)) {
            $reflection = new ReflectionClass($plugin);
            $instance   = $reflection->newInstanceArgs($args);
        } else {
            $instance   = new $plugin;
        }

        return $instance;
    }

    /**
     * Magic method to resolve plugin loading to params.
     *
     * Resolve
     *   - loadFormElementPlugin('text') to loadPlugin('FormElementText');
     *   - initializeFormElementPlugin('text', 'aaa') to initializePlugin('FormElementText', 'aaa');
     *
     * @param string $name      Method name
     * @param string $arguments Method arguments
     *
     * @return mixed
     * @throws InvalidArgumentException if it doesn't resolve to loadPlugin or initializePlugin
     */
    public function  __call($name, $arguments)
    {
        $matches = array();
        if (preg_match('/(?P<action>load|initialize)(?P<type>.*)Plugin/', $name, $matches)) {
            // will call $this->initializePlugin or $this->loadPlugin
            $method       = $matches['action'] .'Plugin';
            $callback     = array($this, $method);

            // concat type from method name called and the first param, if exists
            if (isset($arguments[0])) {
                $arguments[0] = $matches['type'] . $arguments[0];
            } else {
                $arguments    = array($matches['type']);
            }
            return call_user_func_array($callback, $arguments);
        }

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

    /**
     * Separate a camel-case string with a separator
     *
     * @param string $str       String to separate
     * @param string $separator String separator
     *
     * @return string Separated string
     */
    protected function _separateCamelCase($str, $separator = DIRECTORY_SEPARATOR)
    {
        return preg_replace('/([a-z])([A-Z])/', '$1'. $separator .'$2', $str);
    }
}