<?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_CodeGenerator
 * @subpackage PHP
 * @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_CodeGenerator_Php_Abstract
 */
require_once 'Zend/CodeGenerator/Php/Abstract.php';

/**
 * @see Zend_CodeGenerator_Php_Member_Container
 */
require_once 'Zend/CodeGenerator/Php/Member/Container.php';

/**
 * @see Zend_CodeGenerator_Php_Method
 */
require_once 'Zend/CodeGenerator/Php/Method.php';

/**
 * @see Zend_CodeGenerator_Php_Property
 */
require_once 'Zend/CodeGenerator/Php/Property.php';

/**
 * @see Zend_CodeGenerator_Php_Docblock
 */
require_once 'Zend/CodeGenerator/Php/Docblock.php';

/**
 * @category   Zend
 * @package    Zend_CodeGenerator
 * @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_CodeGenerator_Php_Class extends Zend_CodeGenerator_Php_Abstract
{

    /**
     * @var Zend_CodeGenerator_Php_Docblock
     */
    protected $_docblock = null;

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

    /**
     * @var bool
     */
    protected $_isAbstract = false;

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

    /**
     * @var array Array of string names
     */
    protected $_implementedInterfaces = [];

    /**
     * @var array Array of properties
     */
    protected $_properties = null;

    /**
     * @var array Array of constants
     */
    protected $_constants = null;

    /**
     * @var array Array of methods
     */
    protected $_methods = null;

    /**
     * fromReflection() - build a Code Generation PHP Object from a Class Reflection
     *
     * @param Zend_Reflection_Class $reflectionClass
     * @return Zend_CodeGenerator_Php_Class
     */
    public static function fromReflection(Zend_Reflection_Class $reflectionClass)
    {
        $class = new self();

        $class->setSourceContent($class->getSourceContent());
        $class->setSourceDirty(false);

        if ($reflectionClass->getDocComment() != '') {
            $class->setDocblock(Zend_CodeGenerator_Php_Docblock::fromReflection($reflectionClass->getDocblock()));
        }

        $class->setAbstract($reflectionClass->isAbstract());
        $class->setName($reflectionClass->getName());

        if ($parentClass = $reflectionClass->getParentClass()) {
            $class->setExtendedClass($parentClass->getName());
            $interfaces = array_diff($reflectionClass->getInterfaces(), $parentClass->getInterfaces());
        } else {
            $interfaces = $reflectionClass->getInterfaces();
        }

        $interfaceNames = [];
        foreach($interfaces AS $interface) {
            $interfaceNames[] = $interface->getName();
        }

        $class->setImplementedInterfaces($interfaceNames);

        $properties = [];
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
            if ($reflectionProperty->getDeclaringClass()->getName() == $class->getName()) {
                $properties[] = Zend_CodeGenerator_Php_Property::fromReflection($reflectionProperty);
            }
        }
        $class->setProperties($properties);

        $methods = [];
        foreach ($reflectionClass->getMethods() as $reflectionMethod) {
            if ($reflectionMethod->getDeclaringClass()->getName() == $class->getName()) {
                $methods[] = Zend_CodeGenerator_Php_Method::fromReflection($reflectionMethod);
            }
        }
        $class->setMethods($methods);

        return $class;
    }

    /**
     * setDocblock() Set the docblock
     *
     * @param Zend_CodeGenerator_Php_Docblock|array|string $docblock
     * @return Zend_CodeGenerator_Php_Class
     */
    public function setDocblock($docblock)
    {
        if (is_string($docblock)) {
            $docblock = ['shortDescription' => $docblock];
        }

        if (is_array($docblock)) {
            $docblock = new Zend_CodeGenerator_Php_Docblock($docblock);
        } elseif ((!is_null($docblock)) && (!$docblock instanceof Zend_CodeGenerator_Php_Docblock)) {
            require_once 'Zend/CodeGenerator/Php/Exception.php';
            throw new Zend_CodeGenerator_Php_Exception('setDocblock() is expecting either a string, array or an instance of Zend_CodeGenerator_Php_Docblock');
        }

        $this->_docblock = $docblock;
        return $this;
    }

    /**
     * getDocblock()
     *
     * @return Zend_CodeGenerator_Php_Docblock
     */
    public function getDocblock()
    {
        return $this->_docblock;
    }

    /**
     * setName()
     *
     * @param string $name
     * @return Zend_CodeGenerator_Php_Class
     */
    public function setName($name)
    {
        $this->_name = $name;
        return $this;
    }

    /**
     * getName()
     *
     * @return string
     */
    public function getName()
    {
        return $this->_name;
    }

    /**
     * setAbstract()
     *
     * @param bool $isAbstract
     * @return Zend_CodeGenerator_Php_Class
     */
    public function setAbstract($isAbstract)
    {
        $this->_isAbstract = ($isAbstract) ? true : false;
        return $this;
    }

    /**
     * isAbstract()
     *
     * @return bool
     */
    public function isAbstract()
    {
        return $this->_isAbstract;
    }

    /**
     * setExtendedClass()
     *
     * @param string $extendedClass
     * @return Zend_CodeGenerator_Php_Class
     */
    public function setExtendedClass($extendedClass)
    {
        $this->_extendedClass = $extendedClass;
        return $this;
    }

    /**
     * getExtendedClass()
     *
     * @return string
     */
    public function getExtendedClass()
    {
        return $this->_extendedClass;
    }

    /**
     * setImplementedInterfaces()
     *
     * @param array $implementedInterfaces
     * @return Zend_CodeGenerator_Php_Class
     */
    public function setImplementedInterfaces(Array $implementedInterfaces)
    {
        $this->_implementedInterfaces = $implementedInterfaces;
        return $this;
    }

    /**
     * getImplementedInterfaces
     *
     * @return array
     */
    public function getImplementedInterfaces()
    {
        return $this->_implementedInterfaces;
    }

    /**
     * setProperties()
     *
     * @param array $properties
     * @return Zend_CodeGenerator_Php_Class
     */
    public function setProperties(Array $properties)
    {
        foreach ($properties as $property) {
            $this->setProperty($property);
        }

        return $this;
    }

    /**
     * setConstants()
     *
     * @param array $constants
     * @return Zend_CodeGenerator_Php_Class
     */
    public function setConstants(Array $constants)
    {
        foreach ($constants as $const) {
            $this->setConstant($const);
        }

        return $this;
    }

    /**
     * setProperty()
     *
     * @param array|Zend_CodeGenerator_Php_Property $property
     * @return Zend_CodeGenerator_Php_Class
     */
    public function setProperty($property)
    {
        if (is_array($property)) {
            $property = new Zend_CodeGenerator_Php_Property($property);
            $propertyName = $property->getName();
        } elseif ($property instanceof Zend_CodeGenerator_Php_Property) {
            $propertyName = $property->getName();
        } else {
            require_once 'Zend/CodeGenerator/Php/Exception.php';
            throw new Zend_CodeGenerator_Php_Exception('setProperty() expects either an array of property options or an instance of Zend_CodeGenerator_Php_Property');
        }

        if ($property->isConst()) {
            return $this->setConstant($property);
        }
        if (isset($this->_properties[$propertyName])) {
            require_once 'Zend/CodeGenerator/Php/Exception.php';
            throw new Zend_CodeGenerator_Php_Exception('A property by name ' . $propertyName . ' already exists in this class.');
        }

        $this->_properties[$propertyName] = $property;
        return $this;
    }

    /**
     * setConstant()
     *
     * @param array|Zend_CodeGenerator_Php_Property $const
     * @return Zend_CodeGenerator_Php_Class
     */
    public function setConstant($const)
    {
        if (is_array($const)) {
            $const = new Zend_CodeGenerator_Php_Property($const);
            $constName = $const->getName();
        } elseif ($const instanceof Zend_CodeGenerator_Php_Property) {
            $constName = $const->getName();
        } else {
            require_once 'Zend/CodeGenerator/Php/Exception.php';
            throw new Zend_CodeGenerator_Php_Exception('setConstant() expects either an array of property options or an instance of Zend_CodeGenerator_Php_Property');
        }

        if (!$const->isConst()) {
            require_once 'Zend/CodeGenerator/Php/Exception.php';
            throw new Zend_CodeGenerator_Php_Exception('setProperty() expects argument to define a constant');
        }
        if (isset($this->_constants[$constName])) {
            require_once 'Zend/CodeGenerator/Php/Exception.php';
            throw new Zend_CodeGenerator_Php_Exception('A constant by name ' . $constName . ' already exists in this class.');
        }

        $this->_constants[$constName] = $const;
        return $this;
    }

    /**
     * getProperties()
     *
     * @return array
     */
    public function getProperties()
    {
        return $this->_properties;
    }

    /**
     * getConstants()
     *
     * @return array
     */
    public function getConstants()
    {
        return $this->_constants;
    }

    /**
     * getProperty()
     *
     * @param string $propertyName
     * @return Zend_CodeGenerator_Php_Property
     */
    public function getProperty($propertyName)
    {
        foreach ($this->_properties as $property) {
            if ($property->getName() == $propertyName) {
                return $property;
            }
        }
        return false;
    }

    /**
     * getConstant()
     *
     * @param string $constName
     * @return Zend_CodeGenerator_Php_Property
     */
    public function getConstant($constName)
    {
        foreach ($this->_constants as $const) {
            if ($const->getName() == $constName) {
                return $const;
            }
        }
        return false;
    }

    /**
     * hasProperty()
     *
     * @param string $propertyName
     * @return bool
     */
    public function hasProperty($propertyName)
    {
        return isset($this->_properties[$propertyName]);
    }

    /**
     * hasConstant()
     *
     * @param string $constName
     * @return bool
     */
    public function hasConstant($constName)
    {
        return isset($this->_constants[$constName]);
    }

    /**
     * setMethods()
     *
     * @param array $methods
     * @return Zend_CodeGenerator_Php_Class
     */
    public function setMethods(Array $methods)
    {
        foreach ($methods as $method) {
            $this->setMethod($method);
        }
        return $this;
    }

    /**
     * setMethod()
     *
     * @param array|Zend_CodeGenerator_Php_Method $method
     * @return Zend_CodeGenerator_Php_Class
     */
    public function setMethod($method)
    {
        if (is_array($method)) {
            $method = new Zend_CodeGenerator_Php_Method($method);
            $methodName = $method->getName();
        } elseif ($method instanceof Zend_CodeGenerator_Php_Method) {
            $methodName = $method->getName();
        } else {
            require_once 'Zend/CodeGenerator/Php/Exception.php';
            throw new Zend_CodeGenerator_Php_Exception('setMethod() expects either an array of method options or an instance of Zend_CodeGenerator_Php_Method');
        }

        if (isset($this->_methods[$methodName])) {
            require_once 'Zend/CodeGenerator/Php/Exception.php';
            throw new Zend_CodeGenerator_Php_Exception('A method by name ' . $methodName . ' already exists in this class.');
        }

        $this->_methods[$methodName] = $method;
        return $this;
    }

    /**
     * getMethods()
     *
     * @return array
     */
    public function getMethods()
    {
        return $this->_methods;
    }

    /**
     * getMethod()
     *
     * @param string $methodName
     * @return Zend_CodeGenerator_Php_Method
     */
    public function getMethod($methodName)
    {
        foreach ($this->_methods as $method) {
            if ($method->getName() == $methodName) {
                return $method;
            }
        }
        return false;
    }

    /**
     * hasMethod()
     *
     * @param string $methodName
     * @return bool
     */
    public function hasMethod($methodName)
    {
        return isset($this->_methods[$methodName]);
    }

    /**
     * isSourceDirty()
     *
     * @return bool
     */
    public function isSourceDirty()
    {
        if (($docblock = $this->getDocblock()) && $docblock->isSourceDirty()) {
            return true;
        }

        foreach ($this->_properties as $property) {
            if ($property->isSourceDirty()) {
                return true;
            }
        }

        foreach ($this->_constants as $constant) {
            if ($constant->isSourceDirty()) {
                return true;
            }
        }

        foreach ($this->_methods as $method) {
            if ($method->isSourceDirty()) {
                return true;
            }
        }

        return parent::isSourceDirty();
    }

    /**
     * generate()
     *
     * @return string
     */
    public function generate()
    {
        if (!$this->isSourceDirty()) {
            return $this->getSourceContent();
        }

        $output = '';

        if (null !== ($docblock = $this->getDocblock())) {
            $docblock->setIndentation('');
            $output .= $docblock->generate();
        }

        if ($this->isAbstract()) {
            $output .= 'abstract ';
        }

        $output .= 'class ' . $this->getName();

        if ( !empty( $this->_extendedClass) ) {
            $output .= ' extends ' . $this->_extendedClass;
        }

        $implemented = $this->getImplementedInterfaces();
        if (!empty($implemented)) {
            $output .= ' implements ' . implode(', ', $implemented);
        }

        $output .= self::LINE_FEED . '{' . self::LINE_FEED . self::LINE_FEED;

        $constants = $this->getConstants();
        if (!empty($constants)) {
            foreach ($constants as $const) {
                $output .= $const->generate() . self::LINE_FEED . self::LINE_FEED;
            }
        }

        $properties = $this->getProperties();
        if (!empty($properties)) {
            foreach ($properties as $property) {
                $output .= $property->generate() . self::LINE_FEED . self::LINE_FEED;
            }
        }

        $methods = $this->getMethods();
        if (!empty($methods)) {
            foreach ($methods as $method) {
                $output .= $method->generate() . self::LINE_FEED;
            }
        }

        $output .= self::LINE_FEED . '}' . self::LINE_FEED;

        return $output;
    }

    /**
     * _init() - is called at construction time
     *
     */
    protected function _init()
    {
        $this->_properties = new Zend_CodeGenerator_Php_Member_Container(Zend_CodeGenerator_Php_Member_Container::TYPE_PROPERTY);
        $this->_constants = new Zend_CodeGenerator_Php_Member_Container(Zend_CodeGenerator_Php_Member_Container::TYPE_PROPERTY);
        $this->_methods = new Zend_CodeGenerator_Php_Member_Container(Zend_CodeGenerator_Php_Member_Container::TYPE_METHOD);
    }

}
