<?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\Code\Annotation\Parser;

use 
Traversable;
use 
Zend\Code\Annotation\AnnotationInterface;
use 
Zend\Code\Exception;
use 
Zend\EventManager\EventInterface;

/**
 * Generic annotation parser
 *
 * Expects registration of AnnotationInterface instances. Such instances
 * will be passed annotation content to their initialize() method, which
 * they are then responsible for parsing.
 */
class GenericAnnotationParser implements ParserInterface
{
    
/**
     * @var array
     */
    
protected $aliases = array();

    
/**
     * @var array
     */
    
protected $annotationNames = array();

    
/**
     * @var AnnotationInterface[]
     */
    
protected $annotations = array();

    
/**
     * Listen to onCreateAnnotation, and attempt to return an annotation object
     * instance.
     *
     * If the annotation class or alias is not registered, immediately returns
     * false. Otherwise, resolves the class, clones it, and, if any content is
     * present, calls {@link AnnotationInterface::initialize()} with the
     * content.
     *
     * @param  EventInterface $e
     * @return false|AnnotationInterface
     */
    
public function onCreateAnnotation(EventInterface $e)
    {
        
$class $e->getParam('class'false);
        if (!
$class || !$this->hasAnnotation($class)) {
            return 
false;
        }

        
$content $e->getParam('content''');
        
$content trim($content'()');

        if (
$this->hasAlias($class)) {
            
$class $this->resolveAlias($class);
        }

        
$index      array_search($class$this->annotationNames);
        
$annotation $this->annotations[$index];

        
$newAnnotation = clone $annotation;
        if (
$content) {
            
$newAnnotation->initialize($content);
        }

        return 
$newAnnotation;
    }

    
/**
     * Register annotations
     *
     * @param  string|AnnotationInterface $annotation String class name of an
     *         AnnotationInterface implementation, or actual instance
     * @return GenericAnnotationParser
     * @throws Exception\InvalidArgumentException
     */
    
public function registerAnnotation($annotation)
    {
        
$class false;
        if (
is_string($annotation) && class_exists($annotation)) {
            
$class      $annotation;
            
$annotation = new $annotation();
        }

        if (!
$annotation instanceof AnnotationInterface) {
            throw new 
Exception\InvalidArgumentException(sprintf(
                
'%s: expects an instance of %s\AnnotationInterface; received "%s"',
                
__METHOD__,
                
__NAMESPACE__,
                (
is_object($annotation) ? get_class($annotation) : gettype($annotation))
            ));
        }

        
$class $class ?: get_class($annotation);

        if (
in_array($class$this->annotationNames)) {
            throw new 
Exception\InvalidArgumentException(sprintf(
                
'An annotation for this class %s already exists',
                
$class
            
));
        }

        
$this->annotations[]     = $annotation;
        
$this->annotationNames[] = $class;
    }

    
/**
     * Register many annotations at once
     *
     * @param  array|Traversable $annotations
     * @throws Exception\InvalidArgumentException
     * @return GenericAnnotationParser
     */
    
public function registerAnnotations($annotations)
    {
        if (!
is_array($annotations) && !$annotations instanceof Traversable) {
            throw new 
Exception\InvalidArgumentException(sprintf(
                
'%s: expects an array or Traversable; received "%s"',
                
__METHOD__,
                (
is_object($annotations) ? get_class($annotations) : gettype($annotations))
            ));
        }

        foreach (
$annotations as $annotation) {
            
$this->registerAnnotation($annotation);
        }

        return 
$this;
    }

    
/**
     * Checks if the manager has annotations for a class
     *
     * @param  string $class
     * @return bool
     */
    
public function hasAnnotation($class)
    {
        if (
in_array($class$this->annotationNames)) {
            return 
true;
        }

        if (
$this->hasAlias($class)) {
            return 
true;
        }

        return 
false;
    }

    
/**
     * Alias an annotation name
     *
     * @param  string $alias
     * @param  string $class May be either a registered annotation name or another alias
     * @throws Exception\InvalidArgumentException
     * @return GenericAnnotationParser
     */
    
public function setAlias($alias$class)
    {
        if (!
in_array($class$this->annotationNames) && !$this->hasAlias($class)) {
            throw new 
Exception\InvalidArgumentException(sprintf(
                
'%s: Cannot alias "%s" to "%s", as class "%s" is not currently a registered annotation or alias',
                
__METHOD__,
                
$alias,
                
$class,
                
$class
            
));
        }

        
$alias $this->normalizeAlias($alias);
        
$this->aliases[$alias] = $class;

        return 
$this;
    }

    
/**
     * Normalize an alias name
     *
     * @param  string $alias
     * @return string
     */
    
protected function normalizeAlias($alias)
    {
        return 
strtolower(str_replace(array('-''_'' ''\\''/'), ''$alias));
    }

    
/**
     * Do we have an alias by the provided name?
     *
     * @param  string $alias
     * @return bool
     */
    
protected function hasAlias($alias)
    {
        
$alias $this->normalizeAlias($alias);

        return (isset(
$this->aliases[$alias]));
    }

    
/**
     * Resolve an alias to a class name
     *
     * @param  string $alias
     * @return string
     */
    
protected function resolveAlias($alias)
    {
        do {
            
$normalized $this->normalizeAlias($alias);
            
$class      $this->aliases[$normalized];
        } while (
$this->hasAlias($class));

        return 
$class;
    }
}