<?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 files with OOP support.
* This class starts out in HTML mode (we are generating a whole PHP file).
* The code it returns assumes HTML context.
* PHP tags are added automatically where necessary.
*/
class Wact_Php_Writer {
    /**
    * List of the registered functions
    * @var array of Wact_Php_Writer_Function
    */
    protected $functions = array();

    /**
    * List of the registered classes
    * @var array of Wact_Php_Writer_Class
    */
    protected $classes = array();

    /**
    * The global code block of the file
    * @var Wact_Php_Writer_Global
    */
    protected $globalBlock;

    /**
    * The currently active scope
    * @var Wact_Php_Writer_Block
    */
    protected $scope;

    /**
    * Class constructor.
    */
    public function __construct() {
        $this->globalBlock = new Wact_Php_Writer_Global(Wact_Php_Writer_Block::MODE_HTML);
        $this->setScope($this->globalBlock);
    }

    /**
    * Sets another scope to be active.
    * @param object $scope The scope to set
    * @return The scope that was active when calling this function
    */
    public function setScope($scope) {
        if (!($scope instanceof Wact_Php_Writer_Block)) {
            throw new Wact_Php_Writer_Exception('Invalid Scope');
        }
        $oldScope = $this->scope;

        $this->scope = $scope;

        return $oldScope;
    }

    /**
    * Switch code writing to the global scope
    * @return The scope that was active when calling this function
    * @see self::setScope()
    */
    public function switchToGlobalScope() {
        $oldScope = $this->scope;

        $this->scope = $this->globalBlock;
        
        return $oldScope;
    }

    /**
    * Switch code writing to the a function scope
    * @return The scope that was active when calling this function
    * @param string $functionName
    * @see self::setScope()
    */
    public function switchToFunctionScope($functionName) {
        $oldScope = $this->scope;

        $function = $this->findFunction($functionName);
        if (!$function) {
            throw new Wact_Php_Writer_Exception(
                'Function "{FunctionName}" does not exist', $name);
        }

        $this->scope = $function;

        return $oldScope;
    }
    
    /**
    * Switch code writing to a method scope
    * @param string $methodName
    * @return The scope that was active when calling this function
    * @see self::setScope()
    */
    public function switchToMethodScope($methodName) {
        $oldScope = $this->scope;

        $this->scope->switchToMethodScope($methodName);

        return $oldScope;
    }

    /**
    * Switch code writing to a specific class scope
    * @param string $className
    * @return The scope that was active when calling this function
    * @see self::setScope()
    */
    public function switchToClassScope($className) {
        $oldScope = $this->scope;

        // switch to class scope
        $class = $this->findClass($className);
        if (!$class) {
            throw new Wact_Php_Writer_Exception(
                'Class "{ClassName}" does not exist', $className);
        }
        $this->scope = $class;

        return $oldScope;
    }

    /**
    * Switch code writing to a method scope
    * @param string $className
    * @param string $methodName
    * @return The scope that was active when calling this function
    * @see self::setScope()
    */
    public function switchToClassMethodScope($className, $methodName) {
        $oldScope = $this->scope;

        $this->switchToClassScope($className);
        $this->scope->switchToMethodScope($methodName);

        return $oldScope;
    }


    /**
    * Utility method, which returns a unique class property name for custom use.
    * @return string The name of the property. You need to apply the appropriate prefix ('$'/'$this->' etc.) manually if needed.
    */
    public function createTempProperty($value = null, $visibility = 'protected') {
        return $this->scope->createTempProperty($value, $visibility);
    }

    /**
    * Registers a property with the current scope.
    * The current scope must be a Wact_Php_Writer_Class.
    * Checks that no property with this name has been registered yet.
    * @param Wact_Php_Writer_Property $property
    * @return string Property name. You need to apply the appropriate prefix ('$'/'$this->' etc.) manually if needed.
    */
    public function registerProperty($property) {
        return $this->scope->registerProperty($property);
    }

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

    /**
    * Returns a property that previously has been registered with the current scope.
    * The current scope must be a Wact_Php_Writer_Class.
    * @param string $name Name of the property to find
    * @return Wact_Php_Writer_Property or null
    */
    public function findProperty($name) {
        return $this->scope->findProperty($name);
    }

    /**
    * Checks if a property with the specified name is already registered with the current scope.
    * The current scope must be a Wact_Php_Writer_Class.
    * @param string $name The property name to check
    * @return boolean
    */
    public function propertyExists($name) {
        return $this->scope->propertyExists($name);
    }

    /**
    * Creates a new Wact_Php_Writer_Function.
    * (sets the active scope to the newly created function).
    * @param string $name Name of the function
    * @param string $params Parameter list (without the brackets)
    * @return The scope that was active when calling this function
    */
    public function createFunction($name, $params = null) {
        $oldScope = $this->scope;
        
        $function = new Wact_Php_Writer_Function($name, $params);
        $this->registerFunction($function);
        
        return $oldScope;
    }

    /**
    * Creates a new Wact_Php_Writer_Method.
    * Calling this function results in an implicit scope switch
    * (sets the active scope to the newly created function).
    * @param string $name Name of the function
    * @param string $params Parameter list (without the brackets)
    * @return The scope that was active when calling this function
    */
    public function createMethod($name, $params = null) {
        $oldScope = $this->scope;

        $this->scope->createMethod($name, $params);

        return $oldScope;
    }


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

        return $name;
    }

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

    /**
    * Returns a function that has been registered previously.
    * Checks either current class scope or global scope.
    * @param string $name Name of the function to find
    * @return Wact_Php_Writer_Function|NULL
    */
    public function findFunction($name) {
        if (array_key_exists($name, $this->functions)) {
            return $this->functions[$name];
        }

        return null;
    }

    /**
    * Returns a method that has been registered previously.
    * Checks either current class scope or global scope.
    * @param string $name Name of the function to find
    * @return Wact_Php_Writer_Method|NULL
    */
    public function findMethod($name) {
        return $this->scope->findMethod($name);
    }

    /**
    * Checks if a function with the specified name is registered.
    * Checks either current class scope or global scope.
    * @param string $name The name to check
    * @return boolean
    */
    public function functionExists($name) {
        return ($this->findFunction($name) != null);
    }

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

    /**
    * Creates a new Wact_Php_Writer_Class and registers it with this class.
    * Calling this function results in an implicit scope switch
    * (sets the active scope to the newly registered class).
    * @param string $name Name of the class
    * @param string $extends Name of the ancestor class
    * @param string $implements Comma-seperated list of the interfaces the class implements
    * @return The scope that was active when calling this function
    */
    public function createClass($name, $extends = null, $implements = null) {
        $oldScope = $this->scope;

        $class = new Wact_Php_Writer_Class($name, $extends, $implements);
        $this->registerClass($class);

        return $oldScope;
    }

    /**
    * Registers a class which will be rendered into the global block
    * when calling getCode().
    * Checks that no class with this name has been registered yet.
    * Calling this function results in an implicit scope switch
    * (sets the active scope to the newly registered class).
    * @param Wact_Php_Writer_Class $class
    * @return string Class name
    */
    public function registerClass($class) {
        $name = $class->getName();

        if ($this->classExists($name)) {
            throw new Wact_Php_Writer_Exception(
                'Class "{ClassName}" already exists', $name);
        }

        $this->classes[$name] = $class;
        $this->setScope($class);

        return $name;
    }

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

        return null;
    }

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

    /**
    * Adds an include file to the internal list.
    * Checks that file has not already been included.
    * Note that the path to the file to be included will need to
    * be in PHP's runtime include path.
    *
    * @param string $fileName
    */
    public function registerInclude($fileName) {
        $this->globalBlock->registerInclude($fileName);
    }

    /**
    * Returns the include file list.
    * Global file includes are merged with the child class includes
    * and the child function includes.
    * @return array
    */
    public function getIncludes() {
        return $this->globalBlock->getIncludes();
    }

    /**
    * Returns the finished include block
    * This class is designed to output into HTML context.
    * PHP tags are added internally.
    * @return string
    */
    public function getIncludeCode() {
        return $this->globalBlock->getIncludeCode($this->getIncludes());
    }

    /**
    * @eturn string|NULL the name of the current function, or NULL if current scope is not a class
    */
    public function getClassName() {
        return $this->scope->getClassName();        
    }

    /**
    * @eturn string|NULL the name of the current method, or NULL if current scope is not a class
    */
    public function getMethodName() {
        return $this->scope->getMethodName();       
    }

    /**
    * @eturn string|NULL the name of the current function, or NULL if current scope is not a function
    */
    public function getFunctionName() {
        return $this->scope->getFunctionName();     
    }

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

    /**
    * Switches the current scope's writer into HTML mode
    */
    public function switchToHTML($context = NULL) {
        $this->scope->switchToHTML($context);
    }

    /**
    * Switches the current scope's writer into PHP mode
    */
    public function switchToPHP() {
        return $this->scope->switchToPHP();
    }

    /**
    * Writes some PHP to the active scope.
    * Appends to the global block if no scope was selected.
    * @param string PHP to write
    */
    public function writePHP($text) {
        return $this->scope->writePHP($text);
    }

    /**
    * Write PHP Literal String to the active scope.
    * Appends to the global block if no scope was selected.
    * Make sure that escape characters are
    * proper for source code escaping of string literal.
    * @param string PHP to write
    */
    public function writePHPLiteral($value) {
        return $this->scope->writePHPLiteral($value);
    }

    /**
    * Writes some HTML to the active scope.
    * Appends to the global block if no scope was selected.
    * @param string HTML to write
    */
    public function writeHTML($text) {
        return $this->scope->writeHTML($text);
    }

    /**
    * Returns the finished code.
    * This class is designed to output into HTML context.
    * PHP tags are added where necessary.
    * @return string
    */
    public function getBlockCode() {
        $this->switchToGlobalScope();
        $this->switchToHTML();

        // render class and function code
        $phpCode = '';
        foreach ($this->classes as $class) {
            $phpCode .= $class->getBlockCode() . "\n";
        }
        foreach ($this->functions as $function) {
            $phpCode .= $function->getBlockCode() . "\n";
        }

        $code = '';

        if (!empty($phpCode)) {
            $code .= '<' . '?php' . "\n";
            $code .= $phpCode;
            $code .= "\n" . '?' . '>';
        }

        // render global code
        $code .= $this->globalBlock->getBlockCode();

        return $code;
    }

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

?>