<?php
/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

namespace Zend\Navigation\Page;

use 
Zend\Mvc\Router\RouteMatch;
use 
Zend\Mvc\Router\RouteStackInterface;
use 
Zend\Navigation\Exception;
use 
Zend\Mvc\ModuleRouteListener;

/**
 * Represents a page that is defined using controller, action, route
 * name and route params to assemble the href
 */
class Mvc extends AbstractPage
{
    
/**
     * Action name to use when assembling URL
     *
     * @var string
     */
    
protected $action;

    
/**
     * Controller name to use when assembling URL
     *
     * @var string
     */
    
protected $controller;

    
/**
     * URL query part to use when assembling URL
     *
     * @var array|string
     */
    
protected $query;

    
/**
     * Params to use when assembling URL
     *
     * @see getHref()
     * @var array
     */
    
protected $params = array();

    
/**
     * RouteInterface name to use when assembling URL
     *
     * @see getHref()
     * @var string
     */
    
protected $route;

    
/**
     * Cached href
     *
     * The use of this variable minimizes execution time when getHref() is
     * called more than once during the lifetime of a request. If a property
     * is updated, the cache is invalidated.
     *
     * @var string
     */
    
protected $hrefCache;

    
/**
     * RouteInterface matches; used for routing parameters and testing validity
     *
     * @var RouteMatch
     */
    
protected $routeMatch;

    
/**
     * Router for assembling URLs
     *
     * @see getHref()
     * @var RouteStackInterface
     */
    
protected $router null;

    
/**
     * Default router to be used if router is not given.
     *
     * @see getHref()
     *
     * @var RouteStackInterface
     */
    
protected static $defaultRouternull;

    
// Accessors:

    /**
     * Returns whether page should be considered active or not
     *
     * This method will compare the page properties against the route matches
     * composed in the object.
     *
     * @param  bool $recursive  [optional] whether page should be considered
     *                          active if any child pages are active. Default is
     *                          false.
     * @return bool             whether page should be considered active or not
     */
    
public function isActive($recursive false)
    {
        if (!
$this->active) {
            
$reqParams = array();
            if (
$this->routeMatch instanceof RouteMatch) {
                
$reqParams  $this->routeMatch->getParams();

                if (isset(
$reqParams[ModuleRouteListener::ORIGINAL_CONTROLLER])) {
                    
$reqParams['controller'] = $reqParams[ModuleRouteListener::ORIGINAL_CONTROLLER];
                }

                
$myParams   $this->params;
                if (
null !== $this->controller) {
                    
$myParams['controller'] = $this->controller;
                }
                if (
null !== $this->action) {
                    
$myParams['action'] = $this->action;
                }

                if (
null !== $this->getRoute()
                    && 
$this->routeMatch->getMatchedRouteName() === $this->getRoute()
                    && (
count(array_intersect_assoc($reqParams$myParams)) == count($myParams))
                ) {
                    
$this->active true;
                    return 
true;
                }
            }

            
$myParams $this->params;

            if (
null !== $this->controller) {
                
$myParams['controller'] = $this->controller;
            } else {
                
/**
                 * @todo In ZF1, this was configurable and pulled from the front controller
                 */
                
$myParams['controller'] = 'index';
            }

            if (
null !== $this->action) {
                
$myParams['action'] = $this->action;
            } else {
                
/**
                 * @todo In ZF1, this was configurable and pulled from the front controller
                 */
                
$myParams['action'] = 'index';
            }

            if (
count(array_intersect_assoc($reqParams$myParams)) == count($myParams)) {
                
$this->active true;
                return 
true;
            }
        }

        return 
parent::isActive($recursive);
    }

    
/**
     * Returns href for this page
     *
     * This method uses {@link RouteStackInterface} to assemble
     * the href based on the page's properties.
     *
     * @see RouteStackInterface
     * @return string  page href
     * @throws Exception\DomainException if no router is set
     */
    
public function getHref()
    {
        if (
$this->hrefCache) {
            return 
$this->hrefCache;
        }

        
$router $this->router;
        if (
null === $router) {
            
$router = static::$defaultRouter;
        }

        if (!
$router instanceof RouteStackInterface) {
            throw new 
Exception\DomainException(
                
__METHOD__
                
' cannot execute as no Zend\Mvc\Router\RouteStackInterface instance is composed'
            
);
        }

        if (
$this->getRouteMatch() !== null) {
            
$params array_merge($this->getRouteMatch()->getParams(), $this->getParams());
        } else {
            
$params $this->getParams();
        }


        if ((
$param $this->getController()) != null) {
            
$params['controller'] = $param;
        }

        if ((
$param $this->getAction()) != null) {
            
$params['action'] = $param;
        }

        switch (
true) {
            case (
$this->getRoute() !== null):
                
$name $this->getRoute();
                break;
            case (
$this->getRouteMatch() !== null):
                
$name $this->getRouteMatch()->getMatchedRouteName();
                break;
            default:
                throw new 
Exception\DomainException('No route name could be found');
        }

        
$options = array('name' => $name);

        
// Add the fragment identifier if it is set
        
$fragment $this->getFragment();
        if (
null !== $fragment) {
            
$options['fragment'] = $fragment;
        }

        if (
null !== ($query $this->getQuery())) {
            
$options['query'] = $query;
        }

        
$url $router->assemble($params$options);

        return 
$this->hrefCache $url;
    }

    
/**
     * Sets action name to use when assembling URL
     *
     * @see getHref()
     *
     * @param  string $action             action name
     * @return Mvc   fluent interface, returns self
     * @throws Exception\InvalidArgumentException  if invalid $action is given
     */
    
public function setAction($action)
    {
        if (
null !== $action && !is_string($action)) {
            throw new 
Exception\InvalidArgumentException(
                
'Invalid argument: $action must be a string or null'
            
);
        }

        
$this->action    $action;
        
$this->hrefCache null;
        return 
$this;
    }

    
/**
     * Returns action name to use when assembling URL
     *
     * @see getHref()
     *
     * @return string|null  action name
     */
    
public function getAction()
    {
        return 
$this->action;
    }

    
/**
     * Sets controller name to use when assembling URL
     *
     * @see getHref()
     *
     * @param  string|null $controller    controller name
     * @return Mvc   fluent interface, returns self
     * @throws Exception\InvalidArgumentException  if invalid controller name is given
     */
    
public function setController($controller)
    {
        if (
null !== $controller && !is_string($controller)) {
            throw new 
Exception\InvalidArgumentException(
                
'Invalid argument: $controller must be a string or null'
            
);
        }

        
$this->controller $controller;
        
$this->hrefCache  null;
        return 
$this;
    }

    
/**
     * Returns controller name to use when assembling URL
     *
     * @see getHref()
     *
     * @return string|null  controller name or null
     */
    
public function getController()
    {
        return 
$this->controller;
    }

    
/**
     * Sets URL query part to use when assembling URL
     *
     * @see getHref()
     * @param  array|string|null $query    URL query part
     * @return self   fluent interface, returns self
     */
    
public function setQuery($query)
    {
        
$this->query      $query;
        
$this->hrefCache  null;
        return 
$this;
    }

    
/**
     * Returns URL query part to use when assembling URL
     *
     * @see getHref()
     *
     * @return array|string|null  URL query part (as an array or string) or null
     */
    
public function getQuery()
    {
        return 
$this->query;
    }

    
/**
     * Sets params to use when assembling URL
     *
     * @see getHref()
     * @param  array|null $params [optional] page params. Default is null
     *                            which sets no params.
     * @return Mvc  fluent interface, returns self
     */
    
public function setParams(array $params null)
    {
        if (
null === $params) {
            
$this->params = array();
        } else {
            
// TODO: do this more intelligently?
            
$this->params $params;
        }

        
$this->hrefCache null;
        return 
$this;
    }

    
/**
     * Returns params to use when assembling URL
     *
     * @see getHref()
     *
     * @return array  page params
     */
    
public function getParams()
    {
        return 
$this->params;
    }

    
/**
     * Sets route name to use when assembling URL
     *
     * @see getHref()
     *
     * @param  string $route              route name to use when assembling URL
     * @return Mvc   fluent interface, returns self
     * @throws Exception\InvalidArgumentException  if invalid $route is given
     */
    
public function setRoute($route)
    {
        if (
null !== $route && (!is_string($route) || strlen($route) < 1)) {
            throw new 
Exception\InvalidArgumentException(
                
'Invalid argument: $route must be a non-empty string or null'
            
);
        }

        
$this->route     $route;
        
$this->hrefCache null;
        return 
$this;
    }

    
/**
     * Returns route name to use when assembling URL
     *
     * @see getHref()
     *
     * @return string  route name
     */
    
public function getRoute()
    {
        return 
$this->route;
    }

    
/**
     * Get the route match.
     *
     * @return \Zend\Mvc\Router\RouteMatch
     */
    
public function getRouteMatch()
    {
        return 
$this->routeMatch;
    }

    
/**
     * Set route match object from which parameters will be retrieved
     *
     * @param  RouteMatch $matches
     * @return Mvc fluent interface, returns self
     */
    
public function setRouteMatch(RouteMatch $matches)
    {
        
$this->routeMatch $matches;
        return 
$this;
    }

    
/**
     * Get the router.
     *
     * @return null|RouteStackInterface
     */
    
public function getRouter()
    {
        return 
$this->router;
    }

    
/**
     * Sets router for assembling URLs
     *
     * @see getHref()
     *
     * @param  RouteStackInterface $router Router
     * @return Mvc    fluent interface, returns self
     */
    
public function setRouter(RouteStackInterface $router)
    {
        
$this->router $router;
        return 
$this;
    }

    
/**
     * Sets the default router for assembling URLs.
     *
     * @see getHref()
     * @param  RouteStackInterface $router Router
     * @return void
     */
    
public static function setDefaultRouter($router)
    {
        static::
$defaultRouter $router;
    }

    
/**
     * Gets the default router for assembling URLs.
     *
     * @return RouteStackInterface
     */
    
public static function getDefaultRouter()
    {
        return static::
$defaultRouter;
    }

    
// Public methods:

    /**
     * Returns an array representation of the page
     *
     * @return array  associative array containing all page properties
     */
    
public function toArray()
    {
        return 
array_merge(
            
parent::toArray(),
            array(
                 
'action'     => $this->getAction(),
                 
'controller' => $this->getController(),
                 
'params'     => $this->getParams(),
                 
'route'      => $this->getRoute(),
                 
'router'     => $this->getRouter(),
                 
'route_match' => $this->getRouteMatch(),
            )
        );
    }
}