<?php
/**
 * A class for controlling the application layer.
 *
 * This class provides a basic mechanism for controlling requests and generating
 * responses. It also features a filter mechanism and a shortcut for using
 * controller logics.
 */
class controller
{
    /**
     * @var request The request.
     */
    protected $request;
    
    /**
     * @var response The response.
     */
    protected $response;

    /**
     * @var array The logics.
     */
    protected $logic;
    
    /**
     * @var array The pre/post filters.
     */
    protected $filter;
    
    /**
     * @var string The directory of resources.
     */
    protected $directory;
    
    /**
     * @var session The session (persistent data between requests).
     */
    protected $session;

    ////////////////////////////////////////////////////////////////////////////
    // Construct and destruct functions.

    /**
     * @param request $request The request.
     * @param array (optional) $logic The logics.
     * @param controller_filter/array (optional) $filter The pre/post filter(s).
     * @param string (optional) $directory The directory of resources.
     * @param session (optional) $session The session.
     * @throws invalidargumentexception If the $logic or $filter parameters are
     * not arrays of controller_logic or controller_filter.
     */
    public function __construct(request $request, array $logic = array(), $filter = array(), $directory = null, session $session = null)
    {
        $this->request = $request;
        
        foreach (array(2 => 'logic', 3 => 'filter') as $k => $v)
        {
            if (!is_array($$v))
            {
                $$v = array($$v);
            }
            
            $class = 'controller_' . $v;
            
            foreach ($$v as $w)
            {
                if (!$w instanceof $class)
                {
                    throw new invalidargumentexception('Argument ' . $k . ' passed to ' . __METHOD__ . '() must be an array of ' . $class . ', ' . ((is_object($w)) ? get_class($w) : gettype($w)) . ' item given.');
                }
            }
            
            $this->$v = $$v;
        }
        
        $this->directory = $directory;
        $this->session = $session;
        $this->initialize();
    }

    /**
     * Initializes the controller (extended constructor).
     */
    protected function initialize()
    {
    }
    
    ////////////////////////////////////////////////////////////////////////////
    // Overload functions.

    /**
     * A shortcut for using a logic object.
     * 
     * @param string $function The class name.
     * @param array $parameter The parameters.
     * @throws logicexception If the logic object is not a controller_logic.
     * @return controller A fluent interface.
     */
    public function __call($function, array $parameter)
    {
        if (!array_key_exists($function, $this->logic))
        {
            $class = 'controller_logic_' . $function;
            $logic = new $class();
            
            if (!$logic instanceof controller_logic)
            {
                throw new logicexception('Class ' . $class . ' is not a controller_logic.');
            }
            
            $this->logic[$function] = $logic;
        }
        
        $this->logic[$function]->set_controller($this)
                               ->set_property(get_object_vars($this));
        
        return call_user_func_array(array($this->logic[$function], 'execute'), $parameter);
    }

    ////////////////////////////////////////////////////////////////////////////
    // Interface functions.
    
    /**
     * Executes a controller action path.
     * 
     * @param string $path The path.
     * @return response The response.
     */
    public function execute($path)
    {
        $this->response = new response;
        $map = $this->map($path);
        
        if (!method_exists($this, $map['function']))
        {
            $this->notfound();
            return $this->response;
        }
        
        if ($this->pre_execute())
        {
            return $this->response;
        }
        
        if (call_user_func_array(array($this, $map['function']), $map['parameter']))
        {
            return $this->response;
        }
        
        if ($this->post_execute())
        {
            return $this->response;
        }
        
        return $this->response;
    }
    
    ////////////////////////////////////////////////////////////////////////////
    // Internal functions.
    
    /**
     * Maps an action path to a controller function and parameters.
     *
     * @param string $path The path.
     * @return array The function and parameters.
     */
    protected function map($path)
    {
        $map = array('function' => 'index', 'parameter' => array());
        $path = explode('/', $path);
        $function = array_shift($path);
        
        if ($function !== '')
        {
            $map['function'] = str_replace('-', '_', $function);
            
            if ($path)
            {
                $map['parameter'] = $path;
            }
        }
        
        $map['function'] = 'execute_' . $map['function'];
        return $map;
    }
    
    /**
     * Executes pre-action filters.
     *
     * @return boolean True if the rest of the execution is to be aborted, else
     * false.
     */
    protected function pre_execute()
    {
        foreach ($this->filter as $f)
        {
            $f->set_controller($this)
              ->set_property(get_object_vars($this));
            
            if ($f->pre_execute())
            {
                return true;
            }
        }
    }

    /**
     * Executes post-action filters.
     *
     * @return boolean True if the rest of the execution is to be aborted, else
     * false.
     */
    protected function post_execute()
    {
        foreach (array_reverse($this->filter) as $f)
        {
            $f->set_controller($this)
              ->set_property(get_object_vars($this));
            
            if ($f->post_execute())
            {
                return true;
            }
        }
    }
}
