<?php
/**
* Web Application Component Toolkit
*
* @link http://www.phpwact.org/
*
* @author Wact Development Team
* @link http://www.phpwact.org/team
*
* @copyright Copyright 2006, Jeff Moore
* @license http://opensource.org/licenses/mit-license.php MIT
*/

/**
*/
class Wact_Controller_Base implements ArrayAccess {

    /**
    * Controllers that have published access level can be used during the
    * dispatch process.  This means that it is possible to craft an HTTP
    * request that targets a specific published controller.
    */
    const PUBLISHED_ACCESS = TRUE;

    /**
    * Controllers that have private access are not visible to the dispatch
    * mechanism.  These are used internally by the application and cannot
    * be directly reached by constructing an HTTP request.
    * Controllers that have private access may receive focus only when they
    * are the target of a forward.
    */
    const PRIVATE_ACCESS = FALSE;
    
    /**
    * The name of this controller
    */
    public $name;

    /**
    * Parent controller
    */
    public $parent = NULL;

    /**
    * Array of child controllers
    */
    protected $children = array();

    /**
    * 
    */
    protected $config;

    /**
    * Enumerates the access level of each child controller.
    */
    protected $accessVisibility = array();

    /**
    * a list of input parameters associated with this controller
    */
    protected $parameters = array();

    /**
    * An object that dispatches to selected child controller
    */
    protected $dispatcher;
    
    /**
    * A View corresponding to this node in the controller tree
    */
    protected $view;
    
    /*
    * A name identifying the view
    */
    protected $partialViewName;

    /**
    * Event handlers for focus events.
    */
    protected $focusListeners;

    /**
    * Event handlers for unFocus events
    */
    protected $unFocusListeners;

    /**
    * Event hanlders for execute events
    */
    protected $executeListeners;
    
    /**
    * construct this object
    */
    function __construct(Wact_Config_Registry $registry) {
        $this->config = $registry;
        $this->setup();
    }
    
    /**
    * Template Method allowing for this object to be configured.
    */
    function setup() {
    }

    /**
    * Checks the existence of a child controller.
    *
    * @param string $offset name of the child to check
    * @return boolean
    */
    public function offsetExists($offset) {
        return isset($this->children[$offset]);
    }

    /**
    * implements ArrayAccess::offsetGet()
    *
    * Returns the child with the name "$offset"
    *
    * @param string $offset name of the child to get
    * @return Wact_Controller_Base
    * @throws WactControllerNotFoundException if the specified child does not exist.
    */
    public function offsetGet($offset) {
        if (isset($this->children[$offset])) {
            return $this->children[$offset];
        } else {
            $controller = new Wact_Controller_Base($this->config);
            $this->addChild($offset, $controller);
            return $controller;
        }
    }

    /**
    * implements ArrayAccess::offsetSet()
    *
    * Adds child "$value" with the name "$offset" to the internal children array.
    *
    * @param string $offset name for the child to set
    * @param Wact_Controller_Base $value The child object
    * @throws WactDuplicateControllerException if the name already exists
    * @return void
    */
    public function offsetSet($offset, $value) {
        if (is_null($offset)) {
            throw new Wact_Controller_Exception('Anonymous children are not supported');
        }
        return $this->addChild($offset, $value);
    }

    /**
    * @param string $offset
    */
    public function offsetUnset($offset) {
        throw new WactCannotRemoveControllerException();
    }

    /**
    * Add a named child controller
    */
    function addChild($name, $controller, $visibility = self::PUBLISHED_ACCESS) {
        $this->children[$name] = $controller;
        $this->accessVisibility[$name] = $visibility;
        $controller->setParent($this, $name);
    }

    /**
    * Change access visibility of a child or list of children to published.
    */
    function publish($children) {
        if (is_string($children)) {
            $this->accessVisibility[$children] = self::PUBLISHED_ACCESS;
        } else {
            foreach($children as $child) {
                $this->accessVisibility[$child] = self::PUBLISHED_ACCESS;
            }
        }
    }

    /**
    * Change access visibility of a child or list of children to private.
    */
    function hide($children) {
        if (is_string($children)) {
            $this->accessVisibility[$children] = self::PRIVATE_ACCESS;
        } else {
            foreach($children as $child) {
                $this->accessVisibility[$child] = self::PRIVATE_ACCESS;
            }
        }
    }

    /**
    * Does this controller have an immediate child of the specified name?
    */
    function hasChild($name) {
        return isset($this->children[$name]);
    }

    /**
    * Return an instance of the named child controller.
    */
    function getChild($name) {
        if (isset($this->children[$name])) {
            return $this->children[$name];
        }
        return NULL;
    }

    /**
    * returns a list of child names.
    */
    function getChildNames() {
        return array_keys($this->children);
    }

    /**
    * Part of the protocol whereby a controller is added to the tree of 
    * controllers
    */
    function setParent($parent, $name) {
        $this->parent = $parent;
        $this->name = $name;
    }

    /**
    * @return string The name of this controller.
    */
    function getName() {
        return $this->name;
    }

    /**
    * @return string The name of this controller.
    */
    function getCommand() {
        if (isset($this->parent)) {
            $parentCommand = $this->parent->getCommand();
            if ($parentCommand == '/') {
                return '/' . $this->name;
            } else {
                return $parentCommand . '/' . $this->name;
            }
        } else {
            return '/';
        }
    }

    /**
    * define an input parameter for this controller
    * @return Wact_Request_Parameter for fluid interface
    */
    function defineInput($parameter) {
        $this->parameters[] = $parameter;
        $parameter->bindToController($this);
        return $parameter;
    }
    
    /**
    * @return array of parameters associated with this controller.
    */
    function getParameters() {
        return $this->parameters;
    }
   
    /**
    * @return void
    */
    protected function reqisterParametersWithRequest($request) {
        foreach($this->getParameters() as $parameter) {
            $request->addParameter($parameter);
        }
    }
    
    /**
    * @return void
    */
    protected function copyParameterValuesToModel($responseModel) {
        foreach($this->parameters as $parameter) {
            $parameter->copyToModel($responseModel);
        }
    }

    /**
    */
    function dispatchOn($parameter) {

        $this->defineInput($parameter);
        $this->dispatcher = $parameter->getName();
        return $parameter; // fluid interface
    }
    
    /**
    */
    function isDispatcher($name) {
        return ($this->dispatcher == $name);
    }

    /**
    * Associate a view with this controller.
    */
    function setView($view) {
        $this->partialViewName = NULL;
        if (is_string($view) && strpos($view, '.tpl.') !== FALSE) {
            $this->view = func_get_args();
        } else {
            $this->view = $view;
        }
    }

    /**
    * Associate a named partial view with this controller.  A parent controller
    * must define a master view.  This partial view will then be inserted into the master
    * view.
    */
    function setPartialView($name, $view) {
        $this->partialViewName = $name;
        if (is_string($view) && strpos($view, '.tpl.') !== FALSE) {
            $this->view = func_get_args();
            array_shift($this->view); // throw away the partial name
        } else {
            $this->view = $view;
        }
    }
    
    /**
    */
    function getView() {
    
        // Convenience for templates
        if (is_array($this->view)) {
            $loader = $this->config->Wact_Template_Loader;
            $spec = $this->view;
            $this->view = call_user_func_array(array($loader, 'load'), $spec);
        }

        // Convenience for class names
        if (is_string($this->view)) {
            $class = $this->view;
            $this->view = new $class();
        }
        
        if (is_object($this->view)) {
            return $this->view;
        }
        
        return NULL;
    }
    
    /**
    */
    function getPartialViewName() {
        return $this->partialViewName;
    }
 
    /**
    * Dispatch the request to a child of this component.
    */
    protected function passFocusToChild($composer, $request, $responseModel) {

        if (count($this->children) > 0) {

            // could make this a loop across severel dispatching parameters
            if (isset($request[$this->dispatcher])) {
                $childName = $request->{$this->dispatcher};
                if (is_string($childName) && isset($this->children[$childName]) && 
                        $this->accessVisibility[$childName] === self::PUBLISHED_ACCESS) {
                        
                    $child = $this->children[$childName];
                    $child->receiveFocus($composer, $request, $responseModel);
                    return;
                }

                // What 404 behavior should we take?

            } else {
                throw new Wact_Controller_Exception(
                    'Dispatching on an undeclared parameter "{Parameter}"', 
                    $this->dispatcher);
            }

        }
    }

    /**
    * register a listener to receive focus events
    */
    function onFocusDo() {
        if (!isset($this->focusListeners)) {
            $this->focusListeners = new Wact_Event_Notifier();
        }
        $listener = func_get_args();
        $this->focusListeners->addListener($listener);
    }

    /**
    * Trigger Focus event
    */
    function triggerFocusEvent($request, $responseModel) {
        if (isset($this->focusListeners)) {
            return $this->focusListeners->notifyUntil($this, $request, $responseModel);
        }
    }

    /**
    * register a listener to receive UnFocus events
    */
    function onUnFocusDo() {
        if (!isset($this->unFocusListeners)) {
            $this->unFocusListeners = new Wact_Event_Notifier();
        }
        $listener = func_get_args();
        $this->unFocusListeners->addListener($listener);
    }

    /**
    * Trigger UnFocus event
    */
    function triggerUnFocusEvent($request, $responseModel) {
        if (isset($this->unFocusListeners)) {
            $this->unFocusListeners->notify($this, $request, $responseModel);
        }
    }

    /**
    * register a listener to receive execute events
    */
    function onExecuteDo() {
        if (!isset($this->executeListeners)) {
            $this->executeListeners = new Wact_Event_Notifier();
        }
        $listener = func_get_args();
        $this->executeListeners->addListener($listener);
    }

    /**
    * Trigger Execute event
    */
    function triggerExecuteEvent($request, $responseModel) {
        if (isset($this->executeListeners)) {
            return $this->executeListeners->notifyUntil($this, $request, $responseModel);
        }
    }

    /**
    * Receive the HTTP Request event and process it.
    * This involves delegating the handling of the request to the
    * child controllers.
    * The raw request may be passed down to the child controllers,
    * or this method may trigger more fine grained events which
    * handle the request.
    * The child controllers cooporate in handling the event by
    * building a ResponseModel.  The ResponseModel is then
    * rendered by a view to produce an HTTP response for the
    * incoming HTTP request.
    */
    function receiveFocus($composer, $request, $responseModel) {

        $this->reqisterParametersWithRequest($request);

        $composer->addToFocusPath($this);

        $forward = $this->triggerFocusEvent($request, $responseModel);

        if (is_null($forward)) {
            $this->copyParameterValuesToModel($responseModel);
            $forward = $this->triggerExecuteEvent($request, $responseModel);
        }

        if (is_null($forward)) {
            $this->passFocusToChild($composer, $request, $responseModel);
        } else {
            $composer->forwardFocus($forward, $request, $responseModel);
        }

    }
    
    /**
    * Begin the process of handling the current PHP request
    * This method may only be called on the root controller of a tree
    * of controllers.  (Page controller or Front controller)
    * @return void
    */
    function start() {
        $composer = new Wact_Controller_ViewComposer();
        $request = new Wact_Request_Compound('');
        $response = new Wact_Response_Model();
        $this->receiveFocus($composer, $request, $response);
        $composer->renderView($request, $response);
    }
}

?>