<?php

/**
 * @package HaloMVC
 */
namespace HaloMVC;
 

/**
 * @author main dispatcher class
 */

class Dispatcher
{
    /**
     * The suffix used to append to the class name
     * @var string
     */
    private $suffix;

    /**
     * The path to look for classes (or controllers)
     * @var string
     */
    private $classPath;

    /**
     * Class constructor
     */
    public function __construct()
    {
        $this->setSuffix('Controller');
        //$this->buildRotues();
    }

    public function buildRotues()
    {
        $router = Router::getInstance();
        
        $std_route = new Route('/:class/:method/:id');
        $std_route->addDynamicElement(':class', ':class')
                  ->addDynamicElement(':method', ':method')
                  ->addDynamicElement(':id', ':id');
        $router->addRoute( 'std', $std_route );
    
        //Set up your default route:
        $default_route = new Route('/');
        $default_route->setMapClass('Default')->setMapMethod('index');
        $router->addRoute( 'default', $default_route );

        
        
        foreach(Config::instance()->getRoutes() as $r) {
            //Define matching rules to match class and method
            $route = new Route( $r['route'] );
            $route->setMapClass( $r['class'] )->setMapMethod( $r['method'] );
            if ($r['element_tag']!=false) {
                $route->addDynamicElement( $r['element_tag'],  $r['element_pattern'] ); //matches regexp
            }
            
            $router->addRoute( $r['route'], $route );


        }
    }


    /**
     * Attempts to dispatch the supplied Route object. Returns false if it fails
     * @param Route $route
     * @throws classFileNotFoundException
     * @throws badClassNameException
     * @throws classNameNotFoundException
     * @throws classMethodNotFoundException
     * @throws classNotSpecifiedException
     * @throws methodNotSpecifiedException
     * @return mixed - result of controller method or FALSE on error
     */
    public function dispatch( Route $route )
    {
         $class      = trim($route->getMapClass());
          $method     = trim($route->getMapMethod());
          $arguments  = $route->getMapArguments();

        if( '' === $class )
            throw new classNotSpecifiedException('Class Name not specified');

        if( '' === $method )
            throw new methodNotSpecifiedException('Method Name not specified');

        //Because the class could have been matched as a dynamic element,
        // it would mean that the value in $class is untrusted. Therefore,
        // it may only contain alphanumeric characters. Anything not matching
        // the regexp is considered potentially harmful.
        $class = str_replace('\\', '', $class);
        preg_match('/^[a-zA-Z0-9_]+$/', $class, $matches);
        if( count($matches) !== 1 )
            throw new badClassNameException('Disallowed characters in class name ' . $class);

        //Apply the suffix
        $file_name = $this->classPath . $class . $this->suffix;
        $class = $class . str_replace('.php', '', $this->suffix);
        
        //At this point, we are relatively assured that the file name is safe
        // to check for it's existence and require in.
        if( FALSE === file_exists($file_name) )
            throw new classFileNotFoundException('Class file not found');
        else
            require_once($file_name);

        //Check for the class class
        if( FALSE === class_exists($class) )
            throw new classNameNotFoundException('class not found ' . $class);

        //Check for the method
        if( FALSE === method_exists($class, $method))
            throw new classMethodNotFoundException('method not found ' . $method);

        //All above checks should have confirmed that the class can be instatiated
        // and the method can be called
        $obj = new $class;
        return call_user_func(array($obj, $method), $arguments);
    }

    /**
     * Sets a suffix to append to the class name being dispatched
     * @param string $suffix
     * @return Dispatcher
     */
    public function setSuffix( $suffix )
    {
        $this->suffix = $suffix . $this->getFileExtension();

        return $this;
    }

    /**
     * Set the path where dispatch class (controllers) reside
     * @param string $path
     * @return Dispatcher
     */
    public function setClassPath( $path )
    {
        $this->classPath = preg_replace('/\/$/', '', $path) . '/';

        return $this;
    }

    private function getFileExtension()
    {
        return '.php';
    }
}
 
class badClassNameException extends \Exception{}
class classFileNotFoundException extends \Exception{}
class classNameNotFoundException extends \Exception{}
class classMethodNotFoundException extends \Exception{}
class classNotSpecifiedException extends \Exception{}
class methodNotSpecifiedException extends \Exception{}

?>