<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Reflection
 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id$
 */

/**
 * @see Zend_Reflection_Docblock_Tag
 */
require_once 'Zend/Reflection/Docblock/Tag.php';

/**
 * @category   Zend
 * @package    Zend_Reflection
 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Reflection_Docblock implements Reflector
{
    /**
     * @var Reflector
     */
    protected $_reflector = null;

    /**#@+
     * @var int
     */
    protected $_startLine = null;
    protected $_endLine   = null;
    /**#@-*/

    /**
     * @var string
     */
    protected $_docComment = null;

    /**
     * @var string
     */
    protected $_cleanDocComment = null;

    /**
     * @var string
     */
    protected $_longDescription = null;

    /**
     * @var string
     */
    protected $_shortDescription = null;

    /**
     * @var array
     */
    protected $_tags = [];

    /**
     * Export reflection
     *
     * Reqired by the Reflector interface.
     *
     * @todo   What should this do?
     * @return void
     */
    public static function export()
    {

    }

    /**
     * Serialize to string
     *
     * Required by the Reflector interface
     *
     * @todo   What should this return?
     * @return string
     */
    public function __toString()
    {
        $str = "Docblock [ /* Docblock */ ] {".PHP_EOL.PHP_EOL;
        $str .= "  - Tags [".count($this->_tags)."] {".PHP_EOL;

        foreach($this->_tags AS $tag) {
            $str .= "    ".$tag;
        }

        $str .= "  }".PHP_EOL;
        $str .= "}".PHP_EOL;

        return $str;
    }

    /**
     * Constructor
     *
     * @param Reflector|string $commentOrReflector
     */
    public function __construct($commentOrReflector)
    {
        if ($commentOrReflector instanceof Reflector) {
            $this->_reflector = $commentOrReflector;
            if (!method_exists($commentOrReflector, 'getDocComment')) {
                require_once 'Zend/Reflection/Exception.php';
                throw new Zend_Reflection_Exception('Reflector must contain method "getDocComment"');
            }
            $docComment = $commentOrReflector->getDocComment();

            $lineCount = substr_count($docComment, "\n");

            $this->_startLine = $this->_reflector->getStartLine() - $lineCount - 1;
            $this->_endLine   = $this->_reflector->getStartLine() - 1;

        } elseif (is_string($commentOrReflector)) {
            $docComment = $commentOrReflector;
        } else {
            require_once 'Zend/Reflection/Exception.php';
            throw new Zend_Reflection_Exception(get_class($this) . ' must have a (string) DocComment or a Reflector in the constructor');
        }

        if ($docComment == '') {
            require_once 'Zend/Reflection/Exception.php';
            throw new Zend_Reflection_Exception('DocComment cannot be empty');
        }

        $this->_docComment = $docComment;
        $this->_parse();
    }

    /**
     * Retrieve contents of docblock
     *
     * @return string
     */
    public function getContents()
    {
        return $this->_cleanDocComment;
    }

    /**
     * Get start line (position) of docblock
     *
     * @return int
     */
    public function getStartLine()
    {
        return $this->_startLine;
    }

    /**
     * Get last line (position) of docblock
     *
     * @return int
     */
    public function getEndLine()
    {
        return $this->_endLine;
    }

    /**
     * Get docblock short description
     *
     * @return string
     */
    public function getShortDescription()
    {
        return $this->_shortDescription;
    }

    /**
     * Get docblock long description
     *
     * @return string
     */
    public function getLongDescription()
    {
        return $this->_longDescription;
    }

    /**
     * Does the docblock contain the given annotation tag?
     *
     * @param  string $name
     * @return bool
     */
    public function hasTag($name)
    {
        foreach ($this->_tags as $tag) {
            if ($tag->getName() == $name) {
                return true;
            }
        }
        return false;
    }

    /**
     * Retrieve the given docblock tag
     *
     * @param  string $name
     * @return Zend_Reflection_Docblock_Tag|false
     */
    public function getTag($name)
    {
        foreach ($this->_tags as $tag) {
            if ($tag->getName() == $name) {
                return $tag;
            }
        }

        return false;
    }

    /**
     * Get all docblock annotation tags
     *
     * @param string $filter
     * @return array Array of Zend_Reflection_Docblock_Tag
     */
    public function getTags($filter = null)
    {
        if ($filter === null || !is_string($filter)) {
            return $this->_tags;
        }

        $returnTags = [];
        foreach ($this->_tags as $tag) {
            if ($tag->getName() == $filter) {
                $returnTags[] = $tag;
            }
        }
        return $returnTags;
    }

    /**
     * Parse the docblock
     *
     * @return void
     */
    protected function _parse()
    {
        $docComment = $this->_docComment;

        // First remove doc block line starters
        $docComment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ ]{0,1}(.*)?#', '$1', $docComment);
        $docComment = ltrim($docComment, "\r\n"); // @todo should be changed to remove first and last empty line

        $this->_cleanDocComment = $docComment;

        // Next parse out the tags and descriptions
        $parsedDocComment = $docComment;
        $lineNumber = $firstBlandLineEncountered = 0;
        while (($newlinePos = strpos($parsedDocComment, "\n")) !== false) {
            $lineNumber++;
            $line = substr($parsedDocComment, 0, $newlinePos);

            $matches = [];

            if ((strpos($line, '@') === 0)
                && (preg_match('#^(@\w+.*?)(\n)(?:@|\r?\n|$)#s', $parsedDocComment, $matches))
            ) {
                $this->_tags[] = Zend_Reflection_Docblock_Tag::factory($matches[1]);
                $parsedDocComment = str_replace($matches[1] . $matches[2], '', $parsedDocComment);
            } else {
                if ($lineNumber < 3 && !$firstBlandLineEncountered) {
                    $this->_shortDescription .= $line . "\n";
                } else {
                    $this->_longDescription .= $line . "\n";
                }

                if ($line == '') {
                    $firstBlandLineEncountered = true;
                }

                $parsedDocComment = substr($parsedDocComment, $newlinePos + 1);
            }

        }

        $this->_shortDescription = rtrim((string) $this->_shortDescription);
        $this->_longDescription  = rtrim((string) $this->_longDescription);
    }
}
