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

/**
 * @category  Core
 * @package   Core_Controller
 * @copyright Copyright (c) 2011. Burza d.o.o. (http://web.burza.hr/en/)
 * @license   proprietary
 */
class Core_Dispatcher
{
    const EVENT_PREDISPATCH_EARLY = 'application.dispatcher.preDispatchEarly';
    const EVENT_PREDISPATCH       = 'application.dispatcher.preDispatch';
    const EVENT_POSTDISPATCH      = 'application.dispatcher.postDispatch';

    /**
     * @var Core_Event_Dispatcher
     */
    protected $_eventDispatcher;

    /**
     * @var Core_Module_ResourceFinder
     */
    protected $_resourceFinder;

    /**
     * @param Core_Event_Dispatcher $eventDispatcher
     *
     * @return \Core_Dispatcher
     */
    public function setEventDispatcher(Core_Event_Dispatcher $eventDispatcher)
    {
        $this->_eventDispatcher = $eventDispatcher;
        return $this;
    }

    /**
     * @return Core_Event_Dispatcher
     */
    public function getEventDispatcher()
    {
        if (null === $this->_eventDispatcher) {
            $this->setEventDispatcher(Core_Application::get('EventDispatcher'));
        }
        return $this->_eventDispatcher;
    }

    /**
     * @param Core_Module_ResourceFinder $resourceFinder
     *
     * @return \Core_Dispatcher
     */
    public function setResourceFinder(Core_Module_ResourceFinder $resourceFinder)
    {
        $this->_resourceFinder = $resourceFinder;
        return $this;
    }

    /**
     * @return Core_Module_ResourceFinder
     */
    public function getResourceFinder()
    {
        if (null === $this->_resourceFinder) {
            $this->setResourceFinder(Core_Application::get('ResourceFinder'));
        }
        return $this->_resourceFinder;
    }

    /**
     * @param Core_Dispatcher_Location_Interface $location
     *
     * @return string|array|Core_Response
     * @throws InvalidArgumentException if dispatch is not possible
     */
    public function dispatch(Core_Dispatcher_Location_Interface $location)
    {
        $params    = array(
            'application' => Core_Application::getInstance(),
            'location'    => $location,
        );
        $event     = new Core_Event($this, self::EVENT_PREDISPATCH_EARLY, $params);
        $this->getEventDispatcher()->dispatchUntil($event);
        
        $module = $location->getModule();
        if (empty($module)) {
            throw new InvalidArgumentException('Dispatch failed, no module to dispatch to set');
        }

        $resourceFinder = $this->getResourceFinder();
        $modulePath     = $resourceFinder->findModulePath($location);
        if (!file_exists($modulePath)) {
            throw new InvalidArgumentException(sprintf('Dispatch failed, module %s does not exists', $modulePath));
        } else if (!is_dir($modulePath)) {
            throw new InvalidArgumentException(sprintf('Dispatch failed, module %s is not a directory', $modulePath));
        }

        $controllerPath  = $resourceFinder->findControllerPath($location);
        if (empty($controllerPath)) {
            throw new InvalidArgumentException('Dispatch failed, no controller to dispatch to set');
        } else if (!file_exists($controllerPath)) {
            throw new InvalidArgumentException(sprintf('Dispatch failed, controller %s does not exists', $controllerPath));
        }

        require_once $controllerPath;
        $controllerClass = $resourceFinder->findControllerClass($location);
        if (!class_exists($controllerClass)) {
            throw new InvalidArgumentException(sprintf('Dispatch failed, controller file %s does not contain controller class %s', $controllerPath, $controllerClass));
        }

        $controllerInstance = new $controllerClass;
        if (!($controllerInstance instanceof Core_Controller_Interface)) {
            throw new InvalidArgumentException(sprintf('Dispatch failed, controller class %s does not implement Core_Controller_Interface', $controllerClass));
        }

        $params['controller'] = $controllerInstance;
        
        $event     = new Core_Event($this, self::EVENT_PREDISPATCH, $params);
        $response  = $this->getEventDispatcher()->filterUntil($event, null);
        if (!$event->isProcessed()) {
            // do the dispatch
            $response = $controllerInstance->invoke($location);

            // filter the response by post-dispatch listeners
            $event    = new Core_Event($this, self::EVENT_POSTDISPATCH, $params);
            $response = $this->getEventDispatcher()->filter($event, $response);
        }

        return $response;
    }
}
