<?php
/**
* @version	$Id: factory.php 14699 2011-10-26 11:39:38Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

	defined('FULL_PATH') or die('restricted access!');

	class kFactory extends kBase implements kiCacheable {

		/**
		 * Where all created objects are stored
		 *
		 * @var Array
		 * @access private
		 */
		var $Storage = Array ();

		/**
		 * Map between class name and file name
		 * where class definition can be found.
		 * File path absolute!
		 *
		 * @var Array
		 * @access private
		 */
		var $Files = Array ();

		/**
		 * Map class names to their pseudo
		 * class names, e.g. key=pseudo_class,
		 * value=real_class_name
		 *
		 * @var Array
		 * @access private
		 */
		var $realClasses = Array ();

		/**
		 * class name vs other classnames it's require for existing
		 *
		 * @var Array
		 */
		var $Dependencies = Array ();

		/**
		 * Sets data from cache to object
		 *
		 * @param Array $data
		 * @access public
		 */
		public function setFromCache(&$data)
		{
			$this->Files = $data['Factory.Files'];
			$this->realClasses = $data['Factory.realClasses'];
			$this->Dependencies = $data['Factory.Dependencies'];
		}

		/**
		 * Gets object data for caching
		 *
		 * @access public
		 * @return Array
		 */
		public function getToCache()
		{
			return Array (
				'Factory.Files' => $this->Files,
				'Factory.realClasses' => $this->realClasses,
				'Factory.Dependencies' => $this->Dependencies,
			);
		}

		/**
		 * Splits any mixing of prefix and
		 * special into correct ones
		 *
		 * @param string $prefix_special
		 * @return Array
		 * @access public
		 */
		function processPrefix($prefix_special)
		{
			// l.pick, l, m.test_TagProcessor

			//preg_match("/(.*)\.*(.*)(_*)(.*)/", $prefix_special, $regs);
			//return Array('prefix'=>$regs[1].$regs[3].$regs[4], 'special'=>$regs[2]);

			$tmp=explode('_',$prefix_special,2);
			$tmp[0]=explode('.',$tmp[0]);

			$prefix=$tmp[0][0];
			$prefix_special=$prefix;	// new1
			if( isset($tmp[1]) )
			{
				$prefix.='_'.$tmp[1];
			}
			$special= isset($tmp[0][1]) ? $tmp[0][1] : '';
			$prefix_special.='.'.$special;	// new2

			return Array('prefix'=>$prefix,'special'=>$special,'prefix_special'=>$prefix_special);
		}


		/**
		 * Returns object using params specified, creates it if is required
		 *
		 * @param string $name
		 * @param string $pseudo_class
		 * @param Array $event_params
		 * @param Array $arguments
		 * @return kBase
		 */
		function &getObject($name, $pseudo_class = '', $event_params = Array (), $arguments = Array ())
		{
			$name = rtrim($name, '.');

			if ( isset($this->Storage[$name]) ) {
				return $this->Storage[$name];
			}

			$ret = $this->processPrefix($name);

			if (!$pseudo_class) {
				$pseudo_class = $ret['prefix'];
			}

			if ( !isset($this->realClasses[$pseudo_class]) ) {
				$error_msg = 'RealClass not defined for pseudo_class <strong>' . $pseudo_class . '</strong>';

				if ( $this->Application->isInstalled() ) {
					throw new Exception($error_msg);
				}
				else {
					if ( $this->Application->isDebugMode() ) {
						$this->Application->Debugger->appendTrace();
					}

					trigger_error($error_msg, E_USER_WARNING);
				}

				$false = false;
				return $false;
			}

			if ( defined('DEBUG_MODE') && defined('DBG_FACTORY') && DBG_FACTORY && $this->Application->isDebugMode() ) {
				$this->Application->Debugger->appendHTML('<b>Creating object:</b> Pseudo class: '.$pseudo_class.' Prefix: '.$name);
				$this->Application->Debugger->appendTrace();
			}

			$this->Storage[$name] =& $this->makeClass($pseudo_class, $arguments);
			$this->Storage[$name]->Init($ret['prefix'], $ret['special']);
			$this->Application->EventManager->runBuildEvent($ret['prefix_special'], $pseudo_class, $event_params);

			return $this->Storage[$name];
		}

		/**
		 * Removes object from storage, so next time it could be created from scratch
		 *
		 * @param string $name Object's name in the Storage
		 */
		function DestroyObject($name)
		{
			unset($this->Storage[$name]);
		}

		/**
		 * Includes file containing class
		 * definition for real class name
		 *
		 * @param string $real_class
		 * @param string $pseudo_class
		 * @access private
		 */
		function includeClassFile($real_class, $pseudo_class = null)
		{
			if ( class_exists($real_class) ) {
				return;
			}

			if( !isset($this->Files[$real_class]) ) {
				throw new Exception('Real Class <strong>' . $real_class . '</strong> (for pseudo class <strong>' . $pseudo_class . '</strong>) is not registered with the Factory');
			}

			if( !file_exists(FULL_PATH . $this->Files[$real_class]) ) {
				throw new Exception('Include file for class <strong>' . $real_class . '</strong> (<strong>' . FULL_PATH . $this->Files[$real_class] . '</strong>) does not exists');
			}

			if( isset( $this->Dependencies[$real_class] ) )
			{
				foreach($this->Dependencies[$real_class] as $dep_class_name)
				{
					$this->includeClassFile($dep_class_name, $pseudo_class);
				}
			}

			kUtil::includeOnce(FULL_PATH.$this->Files[$real_class]);
		}

		/**
		 * Get's real class name for pseudo class,
		 * includes class file and creates class
		 * instance.
		 * All parameters except first one are passed to object constuctor
		 * through mediator method makeClass that creates instance of class
		 *
		 * Pattern: Factory Method
		 *
		 * @param string $pseudo_class
		 * @param Array $arguments
		 * @return kBase
		 * @access public
		 */
		public function &makeClass($pseudo_class, $arguments = Array ())
		{
			$real_class = $this->realClasses[$pseudo_class];

			if ( !class_exists($real_class) ) {
				$this->includeClassFile($real_class, $pseudo_class);
			}

			$mem_before = memory_get_usage();
			$time_before = microtime(true);

			if ( !is_array($arguments) ) {
				// fallback for code, that doesn't know how to pass arguments
				$arguments = Array ($arguments);
			}

			if (!$arguments) {
				$class = new $real_class();
			}
			else {
				$reflection = new ReflectionClass($real_class);
				$class = $reflection->newInstanceArgs($arguments);
			}

			if ( defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_PROFILE_MEMORY') && DBG_PROFILE_MEMORY && $this->Application->isDebugMode() ) {
				$mem_after = memory_get_usage();
				$time_after = microtime(true);
				$mem_used = $mem_after - $mem_before;
				$time_used = $time_after - $time_before;

				$this->Application->Debugger->appendHTML('Factroy created <b>'.$real_class.'</b> - used '.round($mem_used/1024, 3).'Kb time: '.round($time_used, 5));
				$this->Application->Debugger->profilerAddTotal('objects', null, $mem_used);
			}

			return $class;
		}

		/**
		 * Registers new class in the factory
		 *
		 * @param string $real_class Real name of class as in class declaration
		 * @param string $file Filename in what $real_class is declared
		 * @param string $pseudo_class Name under this class object will be accessed using getObject method
		 * @param Array $dependecies List of classes required for this class functioning
		 * @access public
		 */
		function registerClass($real_class, $file, $pseudo_class=null, $dependecies = Array() )
		{
			if ( !isset($pseudo_class) ) {
				$pseudo_class = $real_class;
			}

			if ( !isset($this->Files[$real_class]) ) {
				$this->Files[$real_class] = preg_replace('/^'.preg_quote(FULL_PATH, '/').'/', '', $file, 1);
			}

			if ( isset($this->realClasses[$pseudo_class]) ) {
				$this->registerDependency($real_class, $pseudo_class);
			}

			if ($dependecies) {
				if ( !is_array($dependecies) ) {
					$dependecies = Array ($dependecies);
				}

				foreach ($dependecies as $required_class) {
					$this->registerDependency($real_class, $required_class);
				}
			}

			$this->realClasses[$pseudo_class] = $real_class;
		}

		/**
		 * Unregisters existing class from factory
		 *
		 * @param string $real_class Real name of class as in class declaration
		 * @param string $pseudo_class Name under this class object is accessed using getObject method
		 */
		function unregisterClass($real_class, $pseudo_class = null)
		{
			if ( !isset($pseudo_class) ) {
				$pseudo_class = $real_class;
			}

			unset($this->Files[$real_class]);

			if ( !array_key_exists($real_class, $this->Dependencies) ) {
				return ;
			}

			$this->realClasses[$pseudo_class] = $this->Dependencies[$real_class][0];
			unset( $this->Dependencies[$real_class] );
		}

		/**
		 * Add $class_name to required classes list for $depended_class class.
		 * All required class files are included before $depended_class file is included
		 *
		 * @param string $depended_class
		 * @param string $class_name
		 * @author Alex
		 */
		function registerDependency($depended_class, $class_name)
		{
			if ($this->realClasses[$class_name] == $depended_class) {
				// same class
				return ;
			}

			$dependencies =& $this->Dependencies[$depended_class];
			$dependency_exists = is_array($dependencies) && in_array($this->realClasses[$class_name], $dependencies);

			if ( !$dependency_exists ) {
				$dependencies[] = $this->realClasses[$class_name];
			}
		}
	}