<?php
/**
 * Short description for file
 *
 * Long description for file (if any)...
 *
 * LICENSE: LGPL
 *
 * @category   Sabai
 * @package    Sabai_Application
 * @copyright  Copyright (c) 2006 myWeb Japan (http://www.myweb.ne.jp/)
 * @license    http://opensource.org/licenses/lgpl-license.php GNU LGPL
 * @version    CVS: $Id:$
 * @link
 * @since      File available since Release 0.1.8
*/

/**
 * Sabai_Application_Controller
 */
require_once 'Sabai/Application/Controller.php';

/**
 * Front Controller
 *
 * Long description for class (if any)...
 *
 * @category   Sabai
 * @package    Sabai_Application
 * @copyright  Copyright (c) 2006 myWeb Japan (http://www.myweb.ne.jp/)
 * @author     Kazumi Ono <onokazu@gmail.com>
 * @license    http://opensource.org/licenses/lgpl-license.php GNU LGPL
 * @version    CVS: $Id:$
 * @link
 * @since      Class available since Release 0.1.8
 */
abstract class Sabai_Application_RoutingController extends Sabai_Application_Controller
{
    /**
     * Name of the default controller
     *
     * @access private
     * @var string
     */
    protected $_defaultController;
    /**
     * Arguments passed to the default controller if any
     *
     * @access private
     * @var array
     */
    protected $_defaultControllerArgs;
    /**
     * Path to the the default controller class file
     *
     * @access private
     * @var string
     */
    protected $_defaultControllerFile;
    /**
     * Class prefix for controllers
     *
     * @access private
     * @var string
     */
    protected $_controllerPrefix;
    /**
     * Path to directory where controller class files are located
     *
     * @access private
     * @var string
     */
    protected $_controllerDir;
    /**
     * Array of Sabai_Handle filter objects
     *
     * @access private
     * @var array
     */
    protected $_controllerFilters = array();
    /**
     * Front router
     *
     * @var Sabai_Application_FrontControllerRouter
     */
    protected $_router;
    /**
     * @access protected
     * @var array
     */
    protected $_controllers = array();

    /**
     * Constructor
     *
     * @param string $defaultController
     * @param string $controllerPrefix
     * @param string $controllerDir
     * @param array $defaultControllerArgs
     * @param string $defaultControllerFile
     * @return Sabai_Application_RoutingController
     */
    protected function __construct($defaultController, $controllerPrefix, $controllerDir, array $defaultControllerArgs = array(), $defaultControllerFile = null)
    {
        $this->_defaultController = $defaultController;
        $this->_controllerPrefix = $controllerPrefix;
        $this->_controllerDir = $controllerDir;
        $this->_defaultControllerArgs = $defaultControllerArgs;
        $this->_defaultControllerFile = $defaultControllerFile;
    }

    /**
     * Adds a filter for a specific controller
     *
     * @param mixed $controllerName string or array
     * @param mixed $filter Sabai_Handle object or string
     */
    public function addControllerFilter($controllerName, $filter)
    {
        foreach ((array)$controllerName as $controller_name) {
            $this->_controllerFilters[$controller_name][] = $filter;
        }
    }

    /**
     * Sets filters for a specific controller
     *
     * @param mixed $controllerName string or array
     * @param array $filters
     */
    public function setControllerFilters($controllerName, $filters)
    {
        foreach ((array)$controllerName as $controller_name) {
            $this->_controllerFilters[$controller_name] = $filters;
        }
    }

    /**
     * Runs the controller
     *
     * @access protected
     * @param Sabai_Application_Context $context
     */
    protected function _doExecute(Sabai_Application_Context $context)
    {
        $router = $this->_getRouter($context);
        $route = $this->getRoute();
        if ($router->isRoutable($route)) {
            if ($controller_name = $router->getController()) {
                foreach ($router->getParams() as $key => $value) {
                    $context->request->set($key, $value);
                }
                foreach ($router->getContext() as $key => $value) {
                    $context->$key = $value;
                }
                $next_route = substr($route, strlen($router->getRouteMatched()));
                $this->_executeController($controller_name, $context, $router->getArgs(), $router->getFile(), $next_route);
            } elseif ($forward = $router->getForward()) {
                $this->_doForward($forward, $context);
            }
        } else {
            $this->executeDefault($context, $route);
        }
    }

    /**
     * Forwards request to another route
     *
     * @param string $route
     * @param Sabai_Application_Context $context
     * @param bool $stackContentName
     */
    public function forward($route, Sabai_Application_Context $context, $stackContentName = false)
    {
        Sabai_Log::info(sprintf('Forwarding request to route "%s" by %s', $route, get_class($this)), __FILE__, __LINE__);
        if (!$stackContentName) {
            $context->response->popContentName();
        }
        $this->_doForward($route, $context);
    }

    /**
     * Forwards request to another route
     *
     * @access private
     * @param string $route
     * @param Sabai_Application_Context $context
     */
    protected function _doForward($route, Sabai_Application_Context $context)
    {
        $router = $this->_getRouter($context);
        if ($router->isRoutable($route)) {
            if ($controller_name = $router->getController()) {
                foreach ($router->getParams() as $key => $value) {
                    $context->request->set($key, $value);
                }
                foreach ($router->getContext() as $key => $value) {
                    $context->$key = $value;
                }
                $next_route = substr($route, strlen($router->getRouteMatched()));
                $this->_executeController($controller_name, $context, $router->getArgs(), $router->getFile(), $next_route);
            } elseif ($forward = $router->getForward()) {
                $this->_doForward($forward, $context);
            }
        } else {
            // forward to the parent controller if any
            if (isset($this->_parent)) {
                $this->_parent->forward($route, $context, true);
            } else {
                // use the default route if no parent
                $this->executeDefault($context, $route);
            }
        }
    }

    /**
     * Runs the controller
     *
     * @access private
     * @param string $controllerName
     * @param Sabai_Application_Context $context
     * @param array $controllerArgs
     * @param string $controllerFile
     */
    protected function _executeController($controllerName, Sabai_Application_Context $context, array $controllerArgs = array(), $controllerFile = null, $route = '')
    {
        if (!empty($this->_controllerFilters[$controllerName])) {
            $this->_filterBefore($this->_controllerFilters[$controllerName], $context);
            $this->_doExecuteController($controllerName, $context, $controllerArgs, $controllerFile, $route);
            $this->_filterAfter(array_reverse($this->_controllerFilters[$controllerName]), $context);
        } else {
            $this->_doExecuteController($controllerName, $context, $controllerArgs, $controllerFile, $route);
        }
    }

    /**
     * Runs the controller if any
     *
     * @access private
     * @param string $controllerName
     * @param Sabai_Application_Context $context
     * @param array $controllerArgs
     * @param string $controllerFile
     */
    protected function _doExecuteController($controllerName, Sabai_Application_Context $context, array $controllerArgs = array(), $controllerFile = null, $route = '')
    {
        if (!empty($controllerFile)) {
            $controller_class = $controllerName;
            $controller_class_file = $controllerFile;
        } else {
            $controller_class = $this->_controllerPrefix . $controllerName;
            $controller_class_file = $this->_controllerDir . '/' . $controllerName . '.php';
        }
        $context->response->pushContentName(strtolower($controller_class));
        if (file_exists($controller_class_file)) {
            Sabai_Log::info(sprintf('Executing controller %s(%s)', $controller_class, $controller_class_file), __FILE__, __LINE__);
            require_once $controller_class_file;
            $controller = $this->_getControllerInstance($controller_class, $controllerArgs);
            $controller->setParent($this);
            $controller->setApplication($this->_application);
            $controller->setRoute($route);
            $controller->execute($context);
            Sabai_Log::info(sprintf('Controller %s(%s) executed', $controllerName, $controller_class), __FILE__, __LINE__);
        }
    }

    protected function _getControllerInstance($controllerClass, array $controllerArgs)
    {
        if (!isset($this->_controllers[$controllerClass])) {
            if (!empty($controllerArgs)) {
                $args_str = '$controllerArgs[' . implode('], $controllerArgs[', range(0, count($controllerArgs) - 1)) . ']';
                eval("\$this->_controllers['$controllerClass'] = new $controllerClass($args_str);");
            } else {
                $this->_controllers[$controllerClass] = new $controllerClass();
            }
        }
        return $this->_controllers[$controllerClass];
    }

    /**
     * Returns a Sabai_Application_RoutingControllerRouter
     *
     * @return Sabai_Application_RoutingControllerRouter
     * @param Sabai_Application_Context $context
     */
    protected function _getRouter(Sabai_Application_Context $context)
    {
        if (!isset($this->_router)) {
            $this->_router = $this->_doGetRouter($context);
        }
        return $this->_router;
    }

    /**
     * Returns a Sabai_Application_RoutingControllerRouter instance
     *
     * @access protected
     * @return Sabai_Application_RoutingControllerRouter
     * @param Sabai_Application_Context $context
     */
    protected function _doGetRouter(Sabai_Application_Context $context)
    {
        require_once 'Sabai/Application/RoutingControllerRouter.php';
        $router = new Sabai_Application_RoutingControllerRouter();
        $router->setRoutes($this->_getRoutes($context));
        return $router;
    }

    /**
     * Returns all route data for the default router as an associative array
     *
     * @access protected
     * @return array
     * @param Sabai_Application_Context $context
     */
    abstract protected function _getRoutes(Sabai_Application_Context $context);

    /**
     * Executes the default controller
     *
     * @param Sabai_Application_Context $context
     * @param string $route
     */
    public function executeDefault(Sabai_Application_Context $context, $route = null)
    {
        $this->_executeController($this->_defaultController, $context, $this->_defaultControllerArgs, $this->_defaultControllerFile, $route);
    }
}