<?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\Service;

use 
Zend\Config;
use 
Zend\Mvc\Router\RouteMatch;
use 
Zend\Mvc\Router\RouteStackInterface as Router;
use 
Zend\Navigation\Exception;
use 
Zend\Navigation\Navigation;
use 
Zend\ServiceManager\FactoryInterface;
use 
Zend\ServiceManager\ServiceLocatorInterface;

/**
 * Abstract navigation factory
 */
abstract class AbstractNavigationFactory implements FactoryInterface
{
    
/**
     * @var array
     */
    
protected $pages;

    
/**
     * @param ServiceLocatorInterface $serviceLocator
     * @return \Zend\Navigation\Navigation
     */
    
public function createService(ServiceLocatorInterface $serviceLocator)
    {
        
$pages $this->getPages($serviceLocator);
        return new 
Navigation($pages);
    }

    
/**
     * @abstract
     * @return string
     */
    
abstract protected function getName();

    
/**
     * @param ServiceLocatorInterface $serviceLocator
     * @return array
     * @throws \Zend\Navigation\Exception\InvalidArgumentException
     */
    
protected function getPages(ServiceLocatorInterface $serviceLocator)
    {
        if (
null === $this->pages) {
            
$configuration $serviceLocator->get('Config');

            if (!isset(
$configuration['navigation'])) {
                throw new 
Exception\InvalidArgumentException('Could not find navigation configuration key');
            }
            if (!isset(
$configuration['navigation'][$this->getName()])) {
                throw new 
Exception\InvalidArgumentException(sprintf(
                    
'Failed to find a navigation container by the name "%s"',
                    
$this->getName()
                ));
            }

            
$pages       $this->getPagesFromConfig($configuration['navigation'][$this->getName()]);
            
$this->pages $this->preparePages($serviceLocator$pages);
        }
        return 
$this->pages;
    }

    
/**
     * @param ServiceLocatorInterface $serviceLocator
     * @param array|\Zend\Config\Config $pages
     * @throws \Zend\Navigation\Exception\InvalidArgumentException
     */
    
protected function preparePages(ServiceLocatorInterface $serviceLocator$pages)
    {
        
$application $serviceLocator->get('Application');
        
$routeMatch  $application->getMvcEvent()->getRouteMatch();
        
$router      $application->getMvcEvent()->getRouter();

        return 
$this->injectComponents($pages$routeMatch$router);
    }

    
/**
     * @param string|\Zend\Config\Config|array $config
     * @return array|null|\Zend\Config\Config
     * @throws \Zend\Navigation\Exception\InvalidArgumentException
     */
    
protected function getPagesFromConfig($config null)
    {
        if (
is_string($config)) {
            if (
file_exists($config)) {
                
$config Config\Factory::fromFile($config);
            } else {
                throw new 
Exception\InvalidArgumentException(sprintf(
                    
'Config was a string but file "%s" does not exist',
                    
$config
                
));
            }
        } elseif (
$config instanceof Config\Config) {
            
$config $config->toArray();
        } elseif (!
is_array($config)) {
            throw new 
Exception\InvalidArgumentException('
                Invalid input, expected array, filename, or Zend\Config object'
            
);
        }

        return 
$config;
    }

    
/**
     * @param array $pages
     * @param RouteMatch $routeMatch
     * @param Router $router
     * @return mixed
     */
    
protected function injectComponents(array $pagesRouteMatch $routeMatch nullRouter $router null)
    {
        foreach (
$pages as &$page) {
            
$hasMvc = isset($page['action']) || isset($page['controller']) || isset($page['route']);
            if (
$hasMvc) {
                if (!isset(
$page['routeMatch']) && $routeMatch) {
                    
$page['routeMatch'] = $routeMatch;
                }
                if (!isset(
$page['router'])) {
                    
$page['router'] = $router;
                }
            }

            if (isset(
$page['pages'])) {
                
$page['pages'] = $this->injectComponents($page['pages'], $routeMatch$router);
            }
        }
        return 
$pages;
    }
}