<?php
/**
* Web Application Component Toolkit
*
* @link http://www.phpwact.org/
*
* @author Wact Development Team
* @link http://www.phpwact.org/team
*
* @copyright Copyright 2006, Jeff Moore
* @license http://opensource.org/licenses/mit-license.php MIT
*/

/**
* Provides an API for generating PHP classes
* This class starts out in PHP mode thus the code it returns
* always assumes PHP context (i.e. adds NO PHP tags internally).
*/
class Wact_Php_Writer_Class extends Wact_Php_Writer_Block {

    /**
    * Name of the class
    * @var string
    */
    protected $name;

    /**
    * Name of the ancestor class
    * @var string
    */
    protected $extends;

    /**
    * Comma-seperated list of the interfaces this class implements
    * @var string
    */
    protected $implements;

    /**
    * Count of the current number of temporary variables in the block
    * @var int
    */
    protected $tempPropertyCount = 1;

    /**
    * List of the registered properties
    * @var array of Wact_Php_Writer_Property
    */
    protected $properties = array();

    /**
    * List of the registered methods
    * @var array of Wact_Php_Writer_Method
    */
    protected $methods = array();

    /**
    * Internal pointer to the currently selected block
    * @var Wact_Php_Writer_Method
    */
    protected $scope = null;

    /**
    * Class constructor
    * @param string $name Name of the class this class represents
    * @param string $extends Name of the ancestor class
    * @param string $implements Comma-seperated list of the interfaces this class implements
    */
    public function __construct($name, $extends = null, $implements = null) {
        if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $name)) {
            throw new Wact_Php_Writer_Exception('Invalid Identifier "{Identifier}"', $name);
        }
        $this->name = $name;
        $this->extends = $extends;
        $this->implements = $implements;
    }

    /**
    */
    public function getClassName() {
        return $this->name;
    }

    /**
    */
    public function getMethodName() {
        if ($this->scope) {
            return $this->scope->getMethodName();
        }
    }

    /**
    */
    public function getFunctionName() {
        return NULL;
    }

    /**
    * Sets another scope to be active.
    * @param Wact_Php_Writer_Method $scope The scope to set
    * @return The scope that was active when calling this function
    */
    public function setScope($scope) {
        $oldScope = $this->scope;

        $this->scope = $scope;

        return $oldScope;
    }

    /**
    * Sets another scope to be active.
    * The method has to be registered using registerMethod() first.
    * @param string $MethodName Name of the method to select
    * @return The scope that was active when calling this function
    * @see self::setScope()
    */
    public function switchToMethodScope($methodName) {
        $oldScope = $this->scope;

        if ($methodName) {
            // check that the method has been registered with this class
            $method = $this->findMethod($methodName);
            if (!$method) {
                throw new Wact_Php_Writer_Exception(
                    'Function "{FunctionName}" does not exist', $method);
            }
            $this->scope = $method;
        } else {
            $this->scope = NULL;  // Is this really a good idea?
        }


        return $oldScope;
    }

    /**
    * Utility method, which returns a unique property name for custom use.
    * @return string Temp property name. You need to apply the appropriate prefix ('$'/'$this->' etc.) manually if needed.
    */
    public function createTempProperty($value = null, $visibility = 'protected') {
        $cnt = $this->tempPropertyCount++;
        if ($cnt > 675) {
            $var = chr(65 + ($cnt/26)/26) . chr(65 + ($cnt/26)%26) . chr(65 + $cnt%26);
        } elseif ($cnt > 26) {
            $var = chr(65 + ($cnt/26)%26) . chr(65 + $cnt%26);
        } else {
            $var = chr(64 + $cnt);
        }

        return $this->createProperty($var);
    }

    /**
    * Creates a new Wact_Php_Writer_Property and registers it with this class.
    * @param string $name Property name
    * @param mixed $value Default value
    * @param string $visibility public, private or protected
    * @return string Name of the property. You need to apply the appropriate prefix ('$'/'$this->' etc.) manually if needed.
    */
    public function createProperty($name, $value = null, $visibility = 'public') {
        $property = new Wact_Php_Writer_Property($name, $value, $visibility);
        $this->registerProperty($property);

        return $name;
    }

    /**
    * Registers a property with this class.
    * Checks that no property with this name has been registered yet.
    * @param Wact_Php_Writer_Property $property
    * @return string Name of the property. You need to apply the appropriate prefix ('$'/'$this->' etc.) manually if needed.
    */
    public function registerProperty($property) {
        $name = $property->getName();
        if ($this->propertyExists($name)) {
            throw new Wact_Php_Writer_Exception(
                'Property "{PropertyName}" already exists', $name);
        }

        $this->properties[$name] = $property;

        return $name;
    }

    /**
    * Returns a property that previously has been registered with this class.
    * @param string $name Name of the property to find
    * @return Wact_Php_Writer_Property or null
    */
    public function findProperty($name) {
        if (array_key_exists($name, $this->properties)) {
            return $this->properties[$name];
        }

        return null;
    }

    /**
    * Checks if a property with the specified name is already registered with this class.
    * @param string $name The property name to check
    * @return boolean
    */
    public function propertyExists($name) {
        return ($this->findProperty($name) != null);
    }

    /**
    * Creates a new Wact_Php_Writer_Method and registers it with this class.
    * Calling this function results in an implicit scope switch
    * (sets the active scope to the newly created method).
    * @param string $name Name of the method
    * @param string $params Parameter list (without the brackets)
    * @return string method name. You need to apply the appropriate prefix ('$'/'$this->' etc.) manually if needed.
    */
    public function createMethod($name, $params = null) {
        $method = new Wact_Php_Writer_Method($name, $params);

        return $this->registerMethod($method);
    }

    /**
    * Registers a method with this class.
    * Checks that no method with this name has been registered yet.
    * Calling this function results in an implicit scope switch.
    * (sets the active scope to the newly registered method)
    * @param Wact_Php_Writer_Method $method
    * @return string method name. You need to apply the appropriate prefix ('$'/'$this->' etc.) manually if needed.
    */
    public function registerMethod(Wact_Php_Writer_Method $method) {
        $name = $method->getName();
        if ($this->methodExists($name)) {
            throw new Wact_Php_Writer_Exception(
                'Function "{FunctionName}" already exists', $name);
        }

        $this->methods[$name] = $method;
        $this->scope = $method;

        return $name;
    }

    /**
    * Returns a method that previously has been registered with this class.
    * @param string $name Name of the method to find
    * @return Wact_Php_Writer_Method|NULL
    */
    public function findMethod($name) {
        if (array_key_exists($name, $this->methods)) {
            return $this->methods[$name];
        }

        return null;
    }

    /**
    * Checks if a method with the specified name is registered with this class.
    * @param string $name The name to check
    * @return boolean
    */
    public function methodExists($name) {
        return ($this->findMethod($name) != null);
    }

    /**
    * Utility method, which generates a unique variable name
    * for custom use within the current scope.
    * Needs a scope to be active.
    * @return string Temp variable name. You need to apply the appropriate prefix ('$'/'$this->' etc.) manually if needed.
    */
    public function createTempVariable() {
        if (!$this->scope) {
            throw new Wact_Php_Writer_Exception('No scope specified');
        }

        return $this->scope->createTempVariable();
    }


    /**
    * Switches the current scope's writer into HTML mode
    */
    public function switchToHTML($context = NULL) {
        if (!$this->scope) {
            throw new Wact_Php_Writer_Exception('No scope specified');
        }

        $this->scope->switchToHTML($context);
    }

    /**
    * Switches the current scope's writer into PHP mode
    */
    public function switchToPHP() {
        if (!$this->scope) {
            throw new Wact_Php_Writer_Exception('No scope specified');
        }

        return $this->scope->switchToPHP();
    }

    /**
    * Writes some PHP to the active scope.
    * A scope has to be selected when calling this function.
    * @param string PHP to write
    */
    public function writePHP($text) {
        if (!$this->scope) {
            throw new Wact_Php_Writer_Exception('No scope specified');
        }

        return $this->scope->writePHP($text);
    }

    /**
    * Writes a PHP Literal String to the active scope.
    * Makes sure that escape characters are
    * proper for source code escaping of string literal.
    * A scope has to be selected when calling this function.
    * @param string PHP to write
    * @param boolean optional - text requires escape, defaults to true
    */
    public function writePHPLiteral($value) {
        if (!$this->scope) {
            throw new Wact_Php_Writer_Exception('No scope specified');
        }

        return $this->scope->writePHPLiteral($value);
    }

    /**
    * Writes some HTML to the active scope
    * A scope has to be selected when calling this function.
    * @param string HTML to write
    */
    public function writeHTML($text) {
        if (!$this->scope) {
            throw new Wact_Php_Writer_Exception('No scope specified');
        }

        return $this->scope->writeHTML($text);
    }

    /**
    * Returns the finished class code.
    * This class is designed to output into PHP context.
    * No PHP tags are added internally.
    * @return string
    */
    public function getBlockCode() {
        $code = 'class ' . $this->name;

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

        $code .= ' {' . "\n";

        foreach ($this->properties as $property) {
            $code .= "\n" . $property->getDeclaration() . "\n";
        }

        foreach ($this->methods as $method) {
            $code .= "\n" . $method->getBlockCode() . "\n";
        }

        $code .= "\n}\n";

        return $code;
    }

    /**
    * Returns the finished block code
    * This class is designed to output into PHP context.
    * No PHP tags are added internally.
    * @return string
    */
    public function getCode() {
        return $this->getBlockCode();
    }

    /**
    * Returns the name of the class
    * @return string Name of the class
    */
    public function getName() {
        return $this->name;
    }
}

?>