<?php
/**
 * A class for dispatching requests.
 */
class front
{
    /**
     * @var request The request.
     */
    protected $request;
    
    /**
     * @var array The array of responses (recursive dispatching).
     */
    protected $response;
    
    /**
     * @var string The directory of controllers.
     */
    protected $directory;
    
    /**
     * @var boolean Whether the front controller is currently executing or not.
     */
    protected $executing;
    
    /**
     * @var array The controller parameters.
     */
    protected $controller;

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

    /**
     * @param request $request The request.
     * @param string (optional) $directory The directory of controllers.
     * @param array (optional) $controller The controller parameters.
     * @throws invalidargumentexception If the $controller parameter is not an
     * array of controller construct parameters exclusively.
     */
    public function __construct(request $request, $directory = '', array $controller = array())
    {
        $this->request = $request;
        $this->response = array();
        $this->directory = $directory;
        $this->executing = false;
        $controller = array_merge(array('logic' => array(), 'filter' => array(), 'directory' => null, 'session' => null), $controller);
        
        if (count($controller) != 4)
        {
            throw new invalidargumentexception('Argument 3 passed to ' . __METHOD__ . '() must be an array of controller construct parameters exclusively.');
        }
        
        $this->controller = $controller;
    }

    ////////////////////////////////////////////////////////////////////////////
    // Overload functions.

    ////////////////////////////////////////////////////////////////////////////
    // Interface functions.

    /**
     * Executes a request path.
     *
     * @param string $path The path.
     * @throws logicexception If the controller class is not defined in the
     * controller file.
     * @return response The response.
     */
    public function execute($path)
    {
        $first = !$this->executing;
        $this->executing = true;
        $map = $this->map($path);
        $directory = $this->controller['directory'];
        
        if ($map['file'])
        {
            ob_start();
            include_once $map['file'];
            ob_end_clean();
            
            if (!class_exists($map['class'], false))
            {
                throw new logicexception('Controller class ' . $map['class'] . ' is not defined in file ' . var_export($map['file'], true) . '.');
            }
            
            $directory = substr($map['file'], 0, -4) . '/';
        }
        
        $controller = null;
        
        if ($this->controller['session'] !== null)
        {
            $controller = new $map['class']($this->request, $this->controller['logic'], $this->controller['filter'], $directory, $this->controller['session']);
        }
        else
        {
            $controller = new $map['class']($this->request, $this->controller['logic'], $this->controller['filter'], $directory);
        }
        
        $response = $controller->execute($map['action']);
        
        if ($first)
        {
            foreach (array_reverse($this->response) as $r)
            {
                $response->merge($r);
            }
            
            $this->response = array();
            $this->executing = false;
            return $response;
        }
        else
        {
            $body = (string) $response;
            $response->clear();
            $this->response[] = $response;
            return $body;
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    // Internal functions.
    
    /**
     * Maps a request path to a controller class and action.
     *
     * @param string $path The path.
     * @return array The controller class and action.
     */
    protected function map($path)
    {
        $map = array('file' => null, 'class' => 'controller', 'action' => '');
        $file = null;
        $crumb = '';
        $class = strtok($path, '/');
        
        while ($class !== false)
        {
            $crumb = ltrim($crumb . '/' . $class, '/');
            $file = $this->directory . $crumb . '.php';
            
            if (is_file($file))
            {
                break;
            }
            
            $class = strtok('/');
        }
        
        if ($file === null)
        {
            $class = 'index';
            $crumb = ltrim($crumb . '/' . $class, '/');
            $file = $this->directory . $crumb . '.php';
            
            if (!is_file($file))
            {
                return $map;
            }
        }
        
        $map['file'] = $file;
        $map['class'] = 'controller_' . $class;
        $action = strtok('');
        
        if ($action !== false)
        {
            $map['action'] = $action;
        }
        
        return $map;
    }
}
