<?php
if (!defined('VB_ENTRY')) die('Access denied.');
/*======================================================================*\
   || #################################################################### ||
   || # vBulletin 5.0.0 Beta 10
   || # ---------------------------------------------------------------- # ||
   || # Copyright 2000-2012 vBulletin Solutions Inc. All Rights Reserved. ||
   || # This file may not be redistributed in whole or significant part. # ||
   || # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
   || # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
   || #################################################################### ||
   \*======================================================================*/

abstract class vB_Api
{
	/**
	 * We want API subclasses to access the instances only through getters
	 * @var array
	 */
	private static $instancesRaw;
	private static $instances;
	private static $wrappers;
	// configuration

	/**
	 * Database object.
	 *
	 * @var	vB_Database
	 */
	protected $db;

	protected static function getApiClass($controller, $errorCheck = true)
	{
		if (!$controller)
		{
			//The error originally referred to php 5.2, but the minumum requirement for vB5 is php 5.3
			throw new Exception("The API shoulc be called as vB_Api::instance('Name'), not vB_Api_Name::instance()");
		}
		else
		{
			$c = 'vB_Api_' . ucfirst($controller);

			// Need to bypass this if not internal as calls may be to a custom API extension class.
			if ($errorCheck)
			{
				if (!class_exists($c))
				{
					throw new Exception(sprintf("Can't find class %s", htmlspecialchars($c)));
				}

				if (!is_subclass_of($c, 'vB_Api'))
				{
					throw new Exception(sprintf('Class %s is not a subclass of vB_Api', htmlspecialchars($c)));
				}
			}
		}

		return $c;
	}

	/**
	 *	Wrap the api object with the log wrapper class if needed.
	 */
	private static function wrapLoggerIfNeeded($controller, $api_object)
	{
		//only check the options once
		static $needLog = NULL;
		if (is_null($needLog))
		{
			$config = vB::getConfig();
			$needLog = (!empty($config['Misc']['debuglogging']));
		}

		if ($needLog)
		{
			return new vB_Api_Logwrapper($controller,  $api_object);
		}
		else
		{
			return $api_object;
		}
	}

	/**
	 * Returns an instance of the API object which doesn't handle exceptions
	 * This should only be used in other API objects, not for clients of the API
	 * @param string $controller -- name of the API controller to load
	 * @param bool $refresh_cache -- true if we want to force the cache to update with a new api object
	 *   primarily intended for testing
	 * @return vB_Api
	 */
	public static function instanceInternal($controller, $refresh_cache = false)
	{
		$c = self::getApiClass($controller);
		
		if (!isset(self::$instances[$c]) OR $refresh_cache)
		{
			if (!isset(self::$instancesRaw[$c]) OR $refresh_cache)
			{
				self::$instancesRaw[$c] = new $c;
			}
			
			self::$instances[$c] = self::wrapLoggerIfNeeded($controller, self::$instancesRaw[$c]);
		}
		
		return self::$instances[$c];
	}

	/**
	 * Returns an instance of the API object which translates exceptions to an array
	 * Use this method for API clients.
	 * @param string $controller -- name of the API controller to load
	 * @param bool $refresh_cache -- true if we want to force the cache to update with a new api object
	 *   primarily intended for testing
	 * @return vB_Api
	 */
	public static function instance($controller, $refresh_cache = false)
	{
		$c = self::getApiClass($controller, false);
		
		if (!isset(self::$wrappers[$c]) OR $refresh_cache)
		{
			if (!isset(self::$instancesRaw[$c]) OR $refresh_cache)
			{
				if (class_exists($c))
				{
					self::$instancesRaw[$c] = new $c;
				}
				else
				{
					self::$instancesRaw[$c] = true;
				}
			}
			
			self::$wrappers[$c] = new vB_Api_Wrapper($controller, self::$instancesRaw[$c]);
			self::$wrappers[$c] = self::wrapLoggerIfNeeded($controller, self::$wrappers[$c]);
		}

		return self::$wrappers[$c];
	}


	/**
	*	Clears all previously loaded API objects.
	*
	* Intended for use in tests where the loading pattern can cause issues
	*	with objects that cache thier own data.
	*
	*/
	public static function clearCache()
	{
		self::$wrappers = array();
		self::$instances = array();
		self::$instancesRaw = array();
		vB_Api_Extensions::resetExtensions();
	}

	/**
	 * Call the given api function by name with a named arguments list.
	 * Used primarily to translate REST requests into API calls.
	 *
	 * @param string $method -- the name of the method to call
	 * @param array $args -- The list of args to call.  This is a name => value map that will
	 *   be matched up to the names of the API method.  Order is not important.  The names are
	 *   case sensitive.
	 *
	 * @return The return of the method or an error if the method doesn't exist, or is
	 *   static, a constructor or destructor, or otherwise shouldn't be callable as
	 *   and API method.  It is also an error if the value of a paramater is not provided
	 *   and that parameter doesn't have a default value.
	 */
	public function callNamed($method, array $args = array())
	{
			$reflection = new ReflectionMethod($this, $method);

			if($reflection->isConstructor() || $reflection->isDestructor() ||
				$reflection->isStatic() || $method == "callNamed"
			)
			{
				//todo return error message
				return;
			}

			$php_args = array();
			foreach($reflection->getParameters() as $param)
			{
				if(isset($args[$param->getName()]))
				{
					$php_args[] = &$args[$param->getName()];
				}
				else
				{
					if ($param->isDefaultValueAvailable())
					{
						$php_args[] = $param->getDefaultValue();
					}
					else
					{
						throw new Exception('Required argument missing: ' . htmlspecialchars($param->getName()));
						//todo: return error message
						return;
					}
				}
			}

			return $reflection->invokeArgs($this, $php_args);
	}


	// THIS CODE IS WAS EXTRACTED FROM DIFFERENT FILES OF VB4 BOOTSTRAP AND IS DUPLICATED
	protected function __construct()
	{
		// This is a dummy object $vbulletin just to avoid rewriting all code
		global $vbulletin;

		if (empty($vbulletin))
		{
			$vbulletin = vB::get_registry();
		}
		if (empty($vbulletin->db) AND class_exists('vB') AND !empty(vB::$db))
		{
			$vbulletin->db = vB::$db;
		}
	}
}
