<?php

/*
 * Copyright (C) 2002-2013 AfterLogic Corp. (www.afterlogic.com)
 * Distributed under the terms of the license described in LICENSE
 *
 */

/**
 * @package Api
 */
class CApiPluginManager
{
	/**
	 * @var array
	 */
	private $_aHooks;

	/**
	 * @var array
	 */
	private $_aServiceHooks;
	
	/**
	 * @var array
	 */
	private $_aJsFiles;

	/**
	 * @var array
	 */
	private $_aTemplates;

	/**
	 * @var array
	 */
	private $_aTemplatesStrings;

	/**
	 * @var array
	 */
	private $_aAddTemplates;

	/**
	 * @var array
	 */
	private $_aJsonHooks;

	/**
	 * @var array
	 */
	private $_aPlugins;

	/**
	 * @var mixed
	 */
	private $_mState;

	/**
	 * @var bool
	 */
	protected $bIsEnabled;

	/**
	 * @var \ProjectSeven\Actions
	 */
	protected $_oActions;

	/**
	 * @var \ProjectS
	 */
	protected $_oApiGlobalManager;

	public function __construct(CApiGlobalManager $oApiGlobalManager)
	{
		$this->_aHooks = array();
		$this->_aServiceHooks = array();
		$this->_aJsFiles = array();
		$this->_aJsonHooks = array();
		$this->_aPlugins = array();
		$this->_aTemplates = array();
		$this->_aAddTemplates = array();
		$this->_mState = null;

		$this->_oApiGlobalManager = $oApiGlobalManager;
		$this->_oActions = null;

		$this->bIsEnabled = (bool) CApi::GetConf('plugins', false);

		if ($this->bIsEnabled)
		{
			$sPluginsPath = CApi::DataPath().'/plugins/';
			if (@is_dir($sPluginsPath))
			{
				if (false !== ($rDirHandle = @opendir($sPluginsPath)))
				{
					while (false !== ($sFile = @readdir($rDirHandle)))
					{
						if (0 < strlen($sFile) && '.' !== $sFile{0} && preg_match('/^[a-z0-9\-]+$/', $sFile) &&
							(CApi::GetConf('plugins.config.include-all', false) ||
								CApi::GetConf('plugins.'.$sFile, false)) &&
							@file_exists($sPluginsPath.$sFile.'/index.php'))
						{
							$oPlugin = include $sPluginsPath.$sFile.'/index.php';
							if ($oPlugin instanceof AApiPlugin)
							{
								$oPlugin->SetName($sFile);
								$oPlugin->SetPath($sPluginsPath.$sFile);
								$oPlugin->Init();
//								$oPlugin->Log('INIT > '.get_class($oPlugin));
								$this->_aPlugins[] = $oPlugin;
							}
						}
					}
					
					@closedir($rDirHandle);
				}
			}
		}
	}

	/**
	 * @return CApiGlobalManager
	 */
	public function SetActions($oActions)
	{
		$this->_oActions = $oActions;
	}

	/**
	 * @return \ProjectSeven\Actions|null
	 */
	public function Actions()
	{
		return $this->_oActions;
	}

	/**
	 * @return CApiGlobalManager
	 */
	public function GlobalManager()
	{
		return $this->_oApiGlobalManager;
	}

	/**
	 * @return string
	 */
	public function Hash()
	{
		$sResult = md5(CApi::Version());
		foreach ($this->_aPlugins as $oPlugin)
		{
			$sResult = md5($sResult.$oPlugin->GetPath().$oPlugin->GetName().$oPlugin->GetHash());
		}

		return $sResult;
	}

	/**
	 * @param string $sHookName
	 * @param mixed $mHookCallbak
	 */
	public function AddHook($sHookName, $mHookCallbak)
	{
		if ($this->bIsEnabled)
		{
			if (!isset($this->_aHooks[$sHookName]))
			{
				$this->_aHooks[$sHookName] = array();
			}

			$this->_aHooks[$sHookName][] = $mHookCallbak;
		}
	}

	/**
	 * @param string $sServiceName
	 * @param mixed $mHookCallbak
	 */
	public function AddServiceHook($sServiceName, $mHookCallbak)
	{
		if ($this->bIsEnabled)
		{
			if (!isset($this->_aServiceHooks[$sServiceName]))
			{
				$this->_aServiceHooks[$sServiceName] = array();
			}

			$this->_aServiceHooks[$sServiceName][] = $mHookCallbak;
		}
	}

	/**
	 * @param string $sJsFileName
	 */
	public function AddJsFile($sJsFileName)
	{
		if ($this->bIsEnabled)
		{
			$this->_aJsFiles[] = $sJsFileName;
		}
	}

	/**
	 * @param string $sParsedTemplateID
	 * @param string $sParsedPlace
	 * @param string $sTemplateFileName
	 */
	public function IncludeTemplate($sParsedTemplateID, $sParsedPlace, $sTemplateFileName)
	{
		if ($this->bIsEnabled)
		{
			if (!isset($this->_aTemplates[$sParsedTemplateID]))
			{
				$this->_aTemplates[$sParsedTemplateID] = array();
			}
			
			$this->_aTemplates[$sParsedTemplateID][] = array(
				$sParsedPlace, $sTemplateFileName
			);
		}
	}

	/**
	 * @param string $sParsedTemplateID
	 * @param string $sParsedPlace
	 * @param string $sTemplateString
	 */
	public function IncludeTemplateAsString($sParsedTemplateID, $sParsedPlace, $sTemplateString)
	{
		if ($this->bIsEnabled)
		{
			if (!isset($this->_aTemplatesStrings[$sParsedTemplateID]))
			{
				$this->_aTemplatesStrings[$sParsedTemplateID] = array();
			}

			$this->_aTemplatesStrings[$sParsedTemplateID][] = array(
				$sParsedPlace, $sTemplateString
			);
		}
	}

	/**
	 * @param string $sTemplateName
	 * @param string $sTemplateFileName
	 */
	public function AddTemplate($sTemplateName, $sTemplateFileName)
	{
		if ($this->bIsEnabled)
		{
			$this->_aAddTemplates[] = array(
				$sTemplateName, $sTemplateFileName
			);
		}
	}

	/**
	 * @return bool
	 */
	public function HasJsFiles()
	{
		return 0 < count($this->_aJsFiles);
	}

	/**
	 * @return string
	 */
	public function CompileJs()
	{
		$aResult = array();
		if ($this->bIsEnabled)
		{
			foreach ($this->_aJsFiles as $sFile)
			{
				if (file_exists($sFile))
				{
					$aResult[] = file_get_contents($sFile);
				}
			}
		}

		return implode("\n", $aResult);
	}

	/**
	 * @return string
	 */
	public function ParseTemplate($sTemplateID, $sTemplateSource)
	{
		if ($this->bIsEnabled)
		{
			if (isset($this->_aTemplates[$sTemplateID]) && is_array($this->_aTemplates[$sTemplateID]))
			{
				foreach ($this->_aTemplates[$sTemplateID] as $aItem)
				{
					if (!empty($aItem[0]) && !empty($aItem[1]) && file_exists($aItem[1]))
					{
						$sTemplateSource = str_replace('{%INCLUDE-START/'.$aItem[0].'/INCLUDE-END%}',
							file_get_contents($aItem[1]).'{%INCLUDE-START/'.$aItem[0].'/INCLUDE-END%}', $sTemplateSource);
					}
				}
			}
			
			if (isset($this->_aTemplatesStrings[$sTemplateID]) && is_array($this->_aTemplatesStrings[$sTemplateID]))
			{
				foreach ($this->_aTemplatesStrings[$sTemplateID] as $aItem)
				{
					if (!empty($aItem[0]) && isset($aItem[1]))
					{
						$sTemplateSource = str_replace('{%INCLUDE-START/'.$aItem[0].'/INCLUDE-END%}',
							$aItem[1].'{%INCLUDE-START/'.$aItem[0].'/INCLUDE-END%}', $sTemplateSource);
					}
				}
			}
		}

		return $sTemplateSource;
	}

	/**
	 * @return string
	 */
	public function GetPluginsTemplates()
	{
		$aTemplates = array();
		if ($this->bIsEnabled)
		{
			$aTemplates = $this->_aAddTemplates;
		}

		return $aTemplates;
	}

	/**
	 * @param string $sServiceNameLover
	 * @param array $aParts
	 */
	public function RunServiceHandle($sServiceNameLover, $aParts)
	{
		if (isset($this->_aServiceHooks[$sServiceNameLover]) && is_array($this->_aServiceHooks[$sServiceNameLover]))
		{
			foreach ($this->_aServiceHooks[$sServiceNameLover] as $mCallbak)
			{
				call_user_func_array($mCallbak, $aParts);
			}
		}
	}

	/**
	 * @deprecated
	 * @param string $sAction
	 * @param string $sRequest
	 * @param mixed $mXmlHookCallbak
	 */
	public function AddXmlHook($sXmlHookName, $mXmlHookCallbak)
	{
		// @deprecated
	}
	
	/**
	 * @param string $sHookName
	 * @param mixed $mJsonHookCallback
	 */
	public function AddJsonHook($sHookName, $mJsonHookCallback)
	{
		if ($this->bIsEnabled)
		{
			if (!isset($this->_aJsonHooks[$sHookName]))
			{
				$this->_aJsonHooks[$sHookName] = array();
			}

			$this->_aJsonHooks[$sHookName][] = $mJsonHookCallback;
		}
	}

	/**
	 * @param string $sAction
	 * @param array $aArg = array()
	 */
	public function RunHook($sAction, $aArg = array())
	{
		if ($this->bIsEnabled)
		{
			if (isset($this->_aHooks[$sAction]))
			{
				foreach ($this->_aHooks[$sAction] as $mHookCallbak)
				{
					$this->logCallback('HOOK', $sAction, $mHookCallbak);
					call_user_func_array($mHookCallbak, is_array($aArg) ? $aArg : array());
				}
			}
		}
	}

	/**
	 * @param string $sHookName
	 * @return bool
	 */
	public function HookExist($sHookName)
	{
		return isset($this->_aHooks[$sHookName]);
	}

	/**
	 * @deprecated
	 * @param string $sXmlHookName
	 * @return bool
	 */
	public function XmlHookExist($sXmlHookName)
	{
		return false;
	}

	/**
	 * @deprecated
	 * @param CAppServer $oServer
	 * @param string $sXmlHookName
	 */
	public function RunXmlHook(&$oServer, $sXmlHookName)
	{
		// deprecated
	}

	/**
	 * @param string $sJsonHookName
	 * @return bool
	 */
	public function JsonHookExists($sJsonHookName)
	{
		return isset($this->_aJsonHooks[$sJsonHookName]);
	}

	/**
	 * @param \ProjectSeven\Actions $oServer
	 * @param string $sJsonHookName
	 */
	public function RunJsonHook(&$oServer, $sJsonHookName)
	{
		if ($this->bIsEnabled)
		{
			if (isset($this->_aJsonHooks[$sJsonHookName]))
			{
				foreach ($this->_aJsonHooks[$sJsonHookName] as $mHookCallbak)
				{
					$this->logCallback('JSONHOOK', $sJsonHookName, $mHookCallbak);
					return call_user_func_array($mHookCallbak, array(&$oServer));
				}
			}
		}

		return null;
	}

	/**
	 * @param string $sLogPrefix
	 * @param string $sName
	 * @param mixed $mHookCallbak
	 */
	protected function logCallback($sLogPrefix, $sName, $mHookCallbak)
	{
		CApi::Log($sLogPrefix.' > '.
			(is_string($mHookCallbak) ? $mHookCallbak :
			(is_array($mHookCallbak)
				&& is_object($mHookCallbak[0])
				&& is_string($mHookCallbak[1]) ?
					get_class($mHookCallbak[0]).'->'.$mHookCallbak[1] :
					'action = '.$sName)));
	}
}

/**
 * @package Api
 */
abstract class AApiPlugin
{
	/**
	 * @var string
	 */
	protected $sName;

	/**
	 * @var string
	 */
	protected $sPath;

	/**
	 * @var string
	 */
	protected $sVersion;

	/**
	 * @var CApiPluginManager
	 */
	protected $oPluginManager;

	/**
	 * @param string $oPluginManager
	 * @param CApiPluginManager $oPluginManager
	 */
	public function __construct($sVersion, CApiPluginManager $oPluginManager)
	{
		$this->sVersion = (string) $sVersion;
		$this->oPluginManager = $oPluginManager;

		$this->sName = '';
		$this->sPath = '';
	}

	public function Init()
	{
	}

	/**
	 * @staticvar string $oApiSieveManager
	 * @return CApiSieveManager
	 */
	protected function getSieveManager()
	{
		static $oApiSieveManager = null;
		if (null === $oApiSieveManager)
		{
			$oApiSieveManager = CApi::Manager('sieve');
		}
		return $oApiSieveManager;
	}

	/**
	 * @param string $sName
	 */
	final public function SetName($sName)
	{
		$this->sName = $sName;
	}

	/**
	 * @param string $sPath
	 */
	final public function SetPath($sPath)
	{
		$this->sPath = $sPath;
	}

	/**
	 * @return string
	 */
	public function GetHash()
	{
		return '';
	}

	/**
	 * @return string
	 */
	public function GetName()
	{
		return $this->sName;
	}

	/**
	 * @return string
	 */
	public function GetPath()
	{
		return $this->sPath;
	}

	/**
	 * @return string
	 */
	public function GetVersion()
	{
		return $this->sVersion;
	}

	/**
	 * @return string
	 */
	public function GetFullName()
	{
		return $this->sName.'-'.$this->sVersion;
	}

	/**
	 * @param string $sHookName
	 * @param string $sFunctionName
	 */
	public function AddHook($sHookName, $sFunctionName)
	{
		$this->oPluginManager->AddHook($sHookName, array(&$this, $sFunctionName));
	}

	/**
	 * @param string $sXmlHookName
	 * @param string $sFunctionName
	 */
	public function AddXmlHook($sXmlHookName, $sFunctionName)
	{
		$this->Log('deprecated plugin function AddXmlHook', ELogLevel::Warning);
		//$this->oPluginManager->AddXmlHook($sXmlHookName, array(&$this, $sFunctionName));
	}

	/**
	 * @param string $sHookName
	 * @param string $sFunctionName
	 */
	public function AddJsonHook($sHookName, $sFunctionName)
	{
		$this->oPluginManager->AddJsonHook($sHookName, array(&$this, $sFunctionName));
	}

	/**
	 * @param string $sServiceName
	 * @param string $sFunctionName
	 */
	public function AddServiceHook($sServiceName, $sFunctionName)
	{
		$this->oPluginManager->AddServiceHook($sServiceName, array(&$this, $sFunctionName));
	}

	/**
	 * @param string $sJsFileName
	 */
	public function AddJsFile($sJsFileName)
	{
		if (file_exists($this->sPath.'/'.$sJsFileName))
		{
			$this->oPluginManager->AddJsFile($this->sPath.'/'.$sJsFileName);
		}
	}

	/**
	 * @param string $sParsedTemplateID
	 * @param string $sParsedPlace
	 * @param string $sTemplateFileName
	 */
	public function IncludeTemplate($sParsedTemplateID, $sParsedPlace, $sTemplateFileName)
	{
		if (0 < strlen($sParsedTemplateID) && 0 < strlen($sParsedPlace) && file_exists($this->sPath.'/'.$sTemplateFileName))
		{
			$this->oPluginManager->IncludeTemplate($sParsedTemplateID, $sParsedPlace, $this->sPath.'/'.$sTemplateFileName);
		}
	}

	/**
	 * @param string $sParsedTemplateID
	 * @param string $sParsedPlace
	 * @param string $sTemplateHtml
	 */
	public function IncludeTemplateAsString($sParsedTemplateID, $sParsedPlace, $sTemplateHtml)
	{
		if (0 < strlen($sParsedTemplateID) && 0 < strlen($sParsedPlace))
		{
			$this->oPluginManager->IncludeTemplateAsString($sParsedTemplateID, $sParsedPlace, $sTemplateHtml);
		}
	}

	/**
	 * @param string $sTemplateName
	 * @param string $sTemplateFileName
	 * @param string $sLayoutName = 'Layout'
	 * @param string $sLayoutPosition = 'Screens-Middle'
	 */
	public function AddTemplate($sTemplateName, $sTemplateFileName, $sLayoutName = 'Layout', $sLayoutPosition = 'Screens-Middle')
	{
		if (0 < strlen($sTemplateName) && file_exists($this->sPath.'/'.$sTemplateFileName))
		{
			$sTemplateName = 'Plugin_'.preg_replace('/[^a-zA-Z0-9_]/', '', str_replace(array('/', '\\'), '_', $sTemplateName));

			$this->IncludeTemplateAsString($sLayoutName, 'Layout-'.$sLayoutPosition,
				'<div data-view-model="'.$sTemplateName.'" class="screen" style="display: none;"></div>');
			
			$this->oPluginManager->AddTemplate($sTemplateName, $this->sPath.'/'.$sTemplateFileName);
		}
	}

	/**
	 * @param string $sDesc
	 * @param int $iLogLevel = ELogLevel::Full
	 */
	public function Log($sDesc, $iLogLevel = ELogLevel::Full)
	{
		CApi::Log('PLUGIN > '.$sDesc, $iLogLevel);
	}
}
