<?php
/**
 * Short description for file
 *
 * Long description for file (if any)...
 *
 * LICENSE: LGPL
 *
 * @category   Sabai
 * @package    Sabai_Application
 * @copyright  Copyright (c) 2008 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
*/

/**
 * Short description for class
 *
 * Long description for class (if any)...
 *
 * @category   Sabai
 * @package    Sabai_Application
 * @copyright  Copyright (c) 2008 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_Controller
{
    /**
     * @var Sabai_Application
     */
    protected $_application;
    /**
     * Array of global filters
     *
     * @var array
     */
    private $_filters = array();
    /**
     * @var Sabai_Application_RoutingController
     */
    protected $_parent;
    /**
     * @var string
     */
    protected $_route;

    /**
     * Sets the parent controller
     *
     * @param Sabai_Application_RoutingController $controller
     */
    public function setParent(Sabai_Application_RoutingController $controller)
    {
        $this->_parent = $controller;
    }

    /**
     * Gets the parent controller
     *
     * @return Sabai_Application_RoutingController
     */
    public function getParent()
    {
        if (isset($this->_parent)) {
            return $this->_parent;
        }
        return false;
    }

    /**
     * Adds a filter for all actions in the controller
     *
     * @param mixed $filter Sabai_Handle object or string
     */
    public function addFilter($filter)
    {
        $this->_filters[] = $filter;
    }
    
    /**
     * Adds filters for all actions in the controller
     *
     * @param array $filters array of Sabai_Handle object or string
     */
    public function addFilters(array $filters)
    {
        foreach ($filters as $filter) {
            $this->addFilter($filter);
        }
    }

    /**
     * Adds a filter to the first index for all actions in the controller
     *
     * @param mixed $filter Sabai_Handle object or string
     */
    public function prependFilter($filter)
    {
        array_unshift($this->_filters, $filter);
    }

    /**
     * Sets filters for all actions in the controller
     *
     * @param array $filters
     */
    public function setFilters(array $filters)
    {
        $this->_filters = $filters;
    }

    /**
     * Executes the controller
     *
     * @param Sabai_Application_Context $context
     */
    public function execute(Sabai_Application_Context $context)
    {
        $this->_filterBefore($this->_filters, $context);
        $this->_doExecute($context);
        $this->_filterAfter(array_reverse($this->_filters), $context);
    }

    /**
     * Executes the controller
     *
     * @abstract
     * @access protected
     * @param Sabai_Application_Context $context
     */
    abstract protected function _doExecute(Sabai_Application_Context $context);

    /**
     * Executes pre-filters
     *
     * @access private
     * @param array $filters
     * @param Sabai_Application_Context $context
     */
    final protected function _filterBefore(array $filters, Sabai_Application_Context $context)
    {
        foreach (array_keys($filters) as $i) {
            if (is_object($filters[$i])) {
                $filters[$i]->instantiate()->before($context, $this->_application);
            } else {
                $method = $filters[$i] . 'BeforeFilter';
                $this->_filterByMethod($context, $method);
            }
        }
    }

    /**
     * Executes post-filters
     *
     * @access private
     * @param array $filters
     * @param Sabai_Application_Context $context
     */
    final protected function _filterAfter(array $filters, Sabai_Application_Context $context)
    {
        foreach (array_keys($filters) as $i) {
            if (is_object($filters[$i])) {
                $filters[$i]->instantiate()->after($context, $this->_application);
            } else {
                $method = $filters[$i] . 'AfterFilter';
                $this->_filterByMethod($context, $method);
            }
        }
    }

    /**
     * Executes a filter by a local method or recursively checks parent controllers
     * for the filter and processes it if exists.
     *
     * @access protected
     * @param Sabai_Application_Context $context
     * @param string $method
     */
    private function _filterByMethod(Sabai_Application_Context $context, $method)
    {
        if (method_exists($this, $method)) {
            $this->$method($context);
        } else {
            if ($parent = $this->getParent()) {
                do {
                    if (method_exists($parent, $method)) {
                        $parent->$method($context);
                        return;
                    }
                } while ($parent = $parent->getParent());
            }
            throw new Exception(sprintf('Call to undefined filter method %s in class %s', $method, get_class($this)));
        }
    }

    /**
     * Recursively call parent method until the method is found and executed.
     */
    public function __call($method, $args)
    {
        if ($parent = $this->getParent()) {
            do {
                if (method_exists($parent, $method)) {
                    return call_user_func_array(array($parent, $method), $args);
                }
            } while ($parent = $parent->getParent());
        }
        throw new Exception('Call to undefined function: ' . $method);
    }
    
    /**
     * Sets an application instance
     *
     * @param Sabai_Application $application
     */
    public function setApplication(Sabai_Application $application)
    {
        $this->_application = $application;
    }
    
    public function getRoute()
    {
        return $this->_route;
    }
    
    public function setRoute($route)
    {
        $this->_route = $route;
    }
    
    public function __get($name)
    {
        return $this->_application->$name;   
    }
}