<?php
/**
* @version	$Id: factory.php 12734 2009-10-20 19:28:11Z 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 {

		/**
		 * 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();

		/**
		 * 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
		 * @return Object
		 */
		function &getObject($name,$pseudo_class='',$event_params=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]))
			{
				if( $this->Application->isDebugMode() ) $this->Application->Debugger->appendTrace();
				$error_level = $this->Application->isInstalled() ? E_USER_ERROR : E_USER_WARNING;
				trigger_error('RealClass not defined for pseudo_class <b>'.$pseudo_class.'</b>', $error_level);
				$false = false;
				return $false;
			}

			if (defined('DEBUG_MODE') && defined('DBG_FACTORY') && DBG_FACTORY && $this->Application->isDebugMode()) {
				global $debugger;

				$debugger->appendHTML('<b>Creating object:</b> Pseudo class: '.$pseudo_class.' Prefix: '.$name);
				$debugger->appendTrace();
			}

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

			$prefix=$this->Storage[$name]->Prefix;
			$special=$this->Storage[$name]->Special;

//			$event_manager =& $this->Storage['EventManager'];
			$event =& $this->Application->EventManager->getBuildEvent($pseudo_class);
			if($event)
			{
				$event->Init($prefix,$special);
				foreach($event_params as $param_name=>$param_value)
				{
					$event->setEventParam($param_name,$param_value);
				}
				$this->Application->HandleEvent($event);
			}

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

		/**
		 * Returns object using Variable number of params specified,
		 * creates it if is required
		 * all params after 4th are passed to object constructor
		 *
		 * @param string $name
		 * @param string $pseudo_class
		 * @param Array $event_params
		 * @return Object
		 */
		function &getObjectP($name,$pseudo_class='',$event_params=Array())
		{
			$ret=$this->processPrefix($name);
			if (!$pseudo_class) $pseudo_class = $ret['prefix'];
			$name=rtrim($name,'.');
			if( isset($this->Storage[$name]) ) return $this->Storage[$name];

			if (!isset($this->realClasses[$pseudo_class]))
			{
				if( $this->Application->isDebugMode() ) $this->Application->Debugger->appendTrace();
				$error_level = $this->Application->isInstalled() ? E_USER_ERROR : E_USER_WARNING;
				trigger_error('RealClass not defined for pseudo_class <b>'.$pseudo_class.'</b>', $error_level);
				$false = false;
				return $false;
			}

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

			$funs_args = func_get_args();
			array_splice($funs_args, 0, 3, Array($pseudo_class) );

			$this->Storage[$name] =& ref_call_user_func_array( Array(&$this,'makeClass'), $funs_args);
			$this->Storage[$name]->Init($ret['prefix'],$ret['special'],$event_params);

			$prefix=$this->Storage[$name]->Prefix;
			$special=$this->Storage[$name]->Special;

			$event_manager =& $this->getObject('EventManager');
			$event =& $event_manager->getBuildEvent($pseudo_class);
			if($event)
			{
				$event->Init($prefix,$special);
				foreach($event_params as $param_name=>$param_value)
				{
					$event->setEventParam($param_name,$param_value);
				}
				$this->Application->HandleEvent($event);
			}

			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
		 * @access private
		 */
		function includeClassFile($real_class)
		{
			if (class_exists($real_class)) return;
			if(!isset($this->Files[$real_class])) {
				trigger_error('Real Class <b>'.$real_class.'</b> is not registered with the Factory', E_USER_ERROR);
			}
			if(!file_exists(FULL_PATH.$this->Files[$real_class])) {
				if ($this->Application->isDebugMode()) {
					$this->Application->Debugger->appendTrace();
				}

				trigger_error('Include file for class <b>'.$real_class.'</b> (<b>'.FULL_PATH.$this->Files[$real_class].'</b>) does not exists', E_USER_ERROR);
			}

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

			k4_include_once(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
		 *
		 * @param string $pseudo_class
		 * @return Object
		 * @access private
		 */
		function &makeClass($pseudo_class)
		{
			$real_class = $this->realClasses[$pseudo_class];
			if (!class_exists($real_class)) $this->includeClassFile($real_class);

			$mem_before = memory_get_usage();
			$time_before = getmicrotime();

			if( func_num_args() == 1 )
			{
				$class = new $real_class();
			}
			else
			{
				$func_args = func_get_args();
				$pseudo_class = array_shift($func_args);
				$class =& ref_call_user_func_array( Array($real_class,'makeClass'), $func_args );
			}
			if( defined('DEBUG_MODE') && DEBUG_MODE && constOn('DBG_PROFILE_MEMORY') )
			{
				$mem_after = memory_get_usage();
				$time_after = getmicrotime();
				$mem_used = $mem_after - $mem_before;
				$time_used = $time_after - $time_before;
				global $debugger;
				$debugger->appendHTML('Factroy created <b>'.$real_class.'</b> - used '.round($mem_used/1024, 3).'Kb time: '.round($time_used, 5));
				$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;
		}

		/**
		 * 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)
		{
			$dependencies =& $this->Dependencies[$depended_class];

			$conditions = Array();
			$conditions['exists'] = is_array($dependencies) && in_array($this->realClasses[$class_name], $dependencies);
			$conditions['same_class'] = $this->realClasses[$class_name] == $depended_class;

			if (!$conditions['exists'] && !$conditions['same_class']) {
				$dependencies[] = $this->realClasses[$class_name];
			}
		}

	}