// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.



#include "stdpch.h"

#include "actions.h"
#include "events_listener.h"
#include "interface_v3/interface_manager.h"

using namespace std;
using namespace NLMISC;

extern CEventsListener EventsListener;

////////////
// GLOBAL //
////////////

// Hierachical timer
H_AUTO_DECL ( RZ_Client_Actions_Context_Mngr_Update )

static bool getParam (CBaseAction::CParameter::TType type, ucstring &paramName, ucstring &paramValue, const std::string &argu, uint paramId);

/////////////////////////////////////////////////////
/////////////////////////////////////////////////////
// CAction //////////////////////////////////////////
/////////////////////////////////////////////////////
/////////////////////////////////////////////////////
//---------------------------------------------------
// CAction :
// Constructor
//---------------------------------------------------
CAction::CAction()
{
	Valide = false;
	Repeat = false;
	KeyDown = true;
	KeyUp = false;
}// CAction //


void CAction::runAction ()
{
	CInterfaceManager *IM = CInterfaceManager::getInstance ();
	if (IM)
	{
		IM->runActionHandler (Name.Name, NULL, Name.Argu);
	}
}

/////////////////////////////////////////////////////
/////////////////////////////////////////////////////
// CActionsManager //////////////////////////////////
/////////////////////////////////////////////////////
/////////////////////////////////////////////////////
//---------------------------------------------------
// CActionsManager :
// Constructor
//---------------------------------------------------
CActionsManager::CActionsManager()
{
	clear ();
	_Enabled = false;
}// CActionsManager //


//---------------------------------------------------
// addAction :
// Add a new Action in the context.
//---------------------------------------------------
bool CActionsManager::addAction(const CAction::CName &name)
{
	// Try to allocate memory for action
	CAction action;
	action.Name = name;

	// Find the base action
	const CBaseAction *baseAction = getBaseAction(name);
	if (baseAction)
	{
		// Copy the repeat flag
		action.Repeat = baseAction->Repeat;
		action.KeyUp = baseAction->KeyUp;
		action.KeyDown = baseAction->KeyDown;
	}

	// Try to insert the new action.
	pair<TActionsMap::iterator, bool> p = _Actions.insert(TActionsMap::value_type(name, action));

	if (!p.second)
	{
		nlwarning ("Action (%s %s) already exist in the action manager.", name.Name.c_str (), name.Argu.c_str ());
	}

	return p.second;
}// addAction //

// ***************************************************************************

CAction* CActionsManager::getAction(const CAction::CName &name)
{
	TActionsMap::iterator it = _Actions.find(name);
	if (it == _Actions.end()) return NULL;
	return &(it->second);
}

// ***************************************************************************

void CActionsManager::clear ()
{
	_Actions.clear ();
	_ActionForceDisplay.clear ();
	_ActionCombo.clear ();
	_ComboAction.clear ();
	_KeyAction.clear ();
	_WatchedActions.clear ();
	_ActionCategory.clear ();
	_Categories.clear ();
}

// ***************************************************************************

void CActionsManager::removeCombo (const CCombo &combo)
{
	// Get the old action of the combo
	TComboActionMap::iterator itePreviousCombo = _ComboAction.find (combo);
	if (itePreviousCombo != _ComboAction.end ())
	{
		const CAction::CName oldName = itePreviousCombo->second;

		// Remove all affected keys
		TKeyActionMap::iterator ite = _KeyAction.find (combo.Key);
		while ((ite != _KeyAction.end ()) && (ite->first == combo.Key))
		{
			TKeyActionMap::iterator copyToDelete = ite;
			ite++;
			if (copyToDelete->second == oldName)
				_KeyAction.erase (copyToDelete);
		}

		// Remove the action
		_ActionCombo.erase (oldName);

		// Remove the combo
		_ComboAction.erase (itePreviousCombo);
	}
}

// ***************************************************************************
void CActionsManager::removeAllCombos()
{
	_Actions.clear ();
	_ActionCombo.clear ();
	_ComboAction.clear ();
	_KeyAction.clear ();
	_WatchedActions.clear ();
	_ActionForceDisplay.clear();
}

// ***************************************************************************

bool CActionsManager::addCombo(const CAction::CName &name, const CCombo &combo, bool createAction)
{
	// If createAction == "true" -> Create the Action before to add te combo.
	if(createAction)
		addAction(name);

	// Erase previous values
	TActionComboMap::iterator itePreviousCombo = _ActionCombo.find (name);
	if (itePreviousCombo != _ActionCombo.end ())
	{
		// Remove the old action affected to the combo
		removeCombo (itePreviousCombo->second);
	}

	// Remove the new combo
	removeCombo (combo);

	_ComboAction.insert (TComboActionMap::value_type (combo, name));
	_ActionCombo.insert (TActionComboMap::value_type (name, combo));
	_KeyAction.insert (TKeyActionMap::value_type (combo.Key, name));

	return true;
}// addCombo //

// ***************************************************************************
bool CActionsManager::valide(const CAction::CName &name) const
{
	// Recover the pointer on "actionName" if it exists.
	TActionsMap::const_iterator it = _Actions.find(name);
	if(it != _Actions.end())
	{
		return it->second.Valide;
	}
	// No action of this name found.
	else
		return false;
}// valide //

// ***************************************************************************
bool CActionsManager::isActionPresentInContext(const CAction::CName &name) const
{
	CInterfaceManager *im = CInterfaceManager::getInstance();
	if (!im->isInGame()) return true; // no filtering done when outgame, because actions.xml not loaded yet (no base actions where added)
	{
		const CBaseAction *baseAction = getBaseAction(name);
		if (!baseAction) return false;
		// see if action valid in current context
		if (!ActionsContext.matchContext(baseAction->Contexts)) return false;
		// all parameters must be valid in current context
		for (uint i=0; i<baseAction->Parameters.size (); i++)
		{
			const CBaseAction::CParameter &parameter = baseAction->Parameters[i];
			if (parameter.Type == CBaseAction::CParameter::Constant)
			{
				ucstring paramName;
				ucstring paramValue = parameter.DefaultValue;

				// Get the param from the argu
				getParam (parameter.Type, paramName, paramValue, name.Argu, i);

				bool found = true;
				for (uint k = 0; k < parameter.Values.size(); ++k)
				{
					if (parameter.Values[k].Value == paramValue.toUtf8())
					{
						if (!ActionsContext.matchContext(parameter.Values[k].Contexts)) return false;
						found = true;
						break;
					}
				}
				if (!found) return false;
			}
		}
		return true;
	}
}// valide //


// ***************************************************************************

bool CActionsManager::keyPushed (const CEventKeyDown &keyDown)
{
	bool actionExist = false;
	if (_Enabled)
	{
		CCombo combo;
		combo.Key = keyDown.Key;
		combo.KeyButtons = keyDown.Button;

		// Scan action binded to this key
		TKeyActionMap::iterator iteKeyAction = _KeyAction.find (keyDown.Key);
		while ((iteKeyAction != _KeyAction.end ()) && (iteKeyAction->first == keyDown.Key))
		{
			if (isActionPresentInContext(iteKeyAction->second))
			{
				// Add it to the set of actions to watch
				_WatchedActions.insert (*iteKeyAction);
			}
			iteKeyAction++;
		}

		// Update special valide actions
		updateKeyButton (keyDown.Button);

		// Get the key down
		TComboActionMap::iterator ite = _ComboAction.find (combo);
		if (ite != _ComboAction.end ())
		{
			// Get the action
			TActionsMap::iterator iteAction = _Actions.find (ite->second);
			nlassert (iteAction != _Actions.end ());

			if (isActionPresentInContext(iteAction->first))
			{
				// Run the action
				if (iteAction->second.KeyDown && (iteAction->second.Repeat || keyDown.FirstTime))
				{
					iteAction->second.runAction ();
				}

				// The action exist
				actionExist = true;
			}
			else
			{
				// The action exist
				actionExist = false;
			}
		}
	}
	return actionExist;
}

// ***************************************************************************

void CActionsManager::keyReleased (const CEventKeyUp &keyUp)
{
	if (_Enabled)
	{
		// Update special keys state
		updateKeyButton (keyUp.Button);

		// For each watched actions
		TKeyActionMap::iterator iteWatchedAction = _WatchedActions.begin ();
		while (iteWatchedAction != _WatchedActions.end ())
		{
			TKeyActionMap::iterator iteToDelete = iteWatchedAction++;

			// Get the combo for this action
			TActionComboMap::iterator iteCombo = _ActionCombo.find (iteToDelete->second);
			nlassert (iteCombo != _ActionCombo.end());

			// This combo released ?
			if (iteCombo->second.Key == keyUp.Key)
			{
				// Get the action
				TActionsMap::iterator iteAction = _Actions.find (iteToDelete->second);
				nlassert (iteAction != _Actions.end());

				// Remove this action from watching
				_WatchedActions.erase (iteToDelete);

				// Invalidate the action
				bool LastValid = iteAction->second.Valide;
				iteAction->second.Valide = false;

				if ((LastValid == true) && (iteAction->second.Valide == false))
				{
					// Run the action
					if (iteAction->second.KeyUp)
					{
						iteAction->second.runAction ();
					}
				}
			}
		}
	}
}

// ***************************************************************************
void CActionsManager::releaseAllKeyNoRunning()
{
	// For each watched actions
	TKeyActionMap::iterator iteWatchedAction = _WatchedActions.begin ();
	while (iteWatchedAction != _WatchedActions.end ())
	{
		TKeyActionMap::iterator iteToDelete = iteWatchedAction++;

		// Invalidate the action
		TActionsMap::iterator iteAction = _Actions.find (iteToDelete->second);
		nlassert (iteAction != _Actions.end());
		iteAction->second.Valide = false;

		// Remove this action from watching
		_WatchedActions.erase (iteToDelete);
	}
}


// ***************************************************************************

sint getMatchingNote (sint keyButton, sint newButton)
{
	// At least all the needed key
	if ((keyButton & newButton) != keyButton)
		return -1;

	// If exactly the same, we want it
	if (keyButton == newButton)
		return 10;

	// Else count the number of bits used
	const uint flags[3] = { ctrlKeyButton, shiftKeyButton, altKeyButton };
	sint count = 0;
	for (uint i=0; i<3; i++)
	{
		if (keyButton & flags[i])
			count++;
	}
	return count;
}

// ***************************************************************************

void CActionsManager::updateKeyButton (NLMISC::TKeyButton newButtons)
{
	// For each watched actions
	TKeyActionMap::iterator iteWatchedAction = _WatchedActions.begin ();
	while (iteWatchedAction != _WatchedActions.end ())
	{
		// First action for this key
		TKeyActionMap::iterator iteWatchedActionBegin = iteWatchedAction;

		// Current Key
		NLMISC::TKey key = iteWatchedAction->first;

		// Best matching action
		CAction		*bestMatching = NULL;
		sint		bestMatchingNote = -1;

		// For each action with the same key, search the best combo
		while ((iteWatchedAction != _WatchedActions.end ()) && (key == iteWatchedAction->first))
		{
			// Get the action combo
			TActionComboMap::iterator iteCombo = _ActionCombo.find (iteWatchedAction->second);
			if (iteCombo == _ActionCombo.end())
				nlwarning("try to find Name:%s , Argu:%s",iteWatchedAction->second.Name.c_str(), iteWatchedAction->second.Argu.c_str());
			nlassert (iteCombo != _ActionCombo.end());

			// Get the matching note
			sint matchingNote = getMatchingNote (iteCombo->second.KeyButtons, newButtons);
			if (matchingNote > bestMatchingNote)
			{
				// Get the action
				TActionsMap::iterator iteAction = _Actions.find (iteWatchedAction->second);
				nlassert (iteAction != _Actions.end());

				// Memorise the best action
				bestMatching = &(iteAction->second);
				bestMatchingNote = matchingNote;
			}

			iteWatchedAction++;
		}

		// Invalide or valide actions
		while (iteWatchedActionBegin != iteWatchedAction)
		{
			// Get the action
			TActionsMap::iterator iteAction = _Actions.find (iteWatchedActionBegin->second);
			nlassert (iteAction != _Actions.end());

			// Valide or invalide it
			bool LastValid = iteAction->second.Valide;
			iteAction->second.Valide = (&(iteAction->second) == bestMatching);
			if ((LastValid == true) && (iteAction->second.Valide == false))
			{
				// Run the action on keyup
				if (iteAction->second.KeyUp)
				{
					iteAction->second.runAction ();
				}
			}


			iteWatchedActionBegin++;
		}
	}
}

// ***************************************************************************

void CCombo::init (NLMISC::TKey key, NLMISC::TKeyButton keyButtons)
{
	Key = key;
	KeyButtons = keyButtons;
}

// ***************************************************************************
ucstring CCombo::toUCString() const
{
	ucstring ret;
	if ((KeyButtons & shiftKeyButton) && (Key != 0x10))
		ret += CI18N::get("uiKeySHIFT") + "+";
	if ((KeyButtons & ctrlKeyButton) && (Key != 0x11))
		ret += CI18N::get("uiKeyCONTROL") + "+";
	if ((KeyButtons & altKeyButton) && (Key != 0x12))
		ret += CI18N::get("uiKeyMENU") + "+";
	if (CI18N::hasTranslation("ui"+CEventKey::getStringFromKey(Key)))
		ret += CI18N::get("ui"+CEventKey::getStringFromKey(Key));
	else
		ret += CEventKey::getStringFromKey(Key);
	return ret;
}

// ***************************************************************************

const std::vector<CCategory>	&CActionsManager::getCategories () const
{
	return _Categories;
}

// ***************************************************************************

void	CActionsManager::reserveCategories (uint space)
{
	_Categories.reserve (space);
}

// ***************************************************************************

void	CActionsManager::addCategory (const CCategory &category)
{
	_Categories.push_back (category);

	// Add an entry in the map to get the base action by the action name
	uint i;
	for (i=0; i<category.BaseActions.size (); i++)
	{
		CCategoryLocator locator;
		locator.CategoryId = (uint)_Categories.size ()-1;
		locator.BaseActionId = i;
		_ActionCategory.insert (TActionBaseActionMap::value_type (category.BaseActions[i].Name, locator));
	}
}

// ***************************************************************************

void	CActionsManager::removeCategory (const string &catName)
{
	// Search the category
	uint i, catNb;
	for (catNb=0; catNb < _Categories.size(); ++catNb)
		if (_Categories[catNb].Name == catName)
			break;

	if (catNb == _Categories.size()) return;

	// Remove all entries in the map to get the base action by the action name
	for (i=0; i<_Categories[catNb].BaseActions.size (); i++)
	{
		CCategoryLocator locator;
		locator.CategoryId = catNb;
		locator.BaseActionId = i;
		_ActionCategory.erase (_Categories[catNb].BaseActions[i].Name);
	}
	_Categories.erase (_Categories.begin()+catNb);
}

// ***************************************************************************

void	CActionsManager::enable (bool enable)
{
	_Enabled = enable;
}

// ***************************************************************************
// CBaseAction
// ***************************************************************************

CBaseAction::CBaseAction ()
{
	Repeat = false;
	KeyDown = true;
	KeyUp = false;
	WaitForServer = false;
	Macroisable= true;
}

// ***************************************************************************
bool CBaseAction::isUsableInCurrentContext() const
{
	if (ActionsContext.matchContext(Contexts))
	{
		bool cteParamFound = false;
		bool matchingParamFound = false;
		// now, see if for all the constant parameter for this action,
		// at least one is valid in current context
		for (uint l = 0; l < Parameters.size(); ++l)
		{
			const CParameter &param = Parameters[l];
			if (param.Type == CParameter::Constant)
			{
				cteParamFound = true;
				for (uint m = 0; m < param.Values.size(); ++m)
				{
					if (ActionsContext.matchContext(param.Values[m].Contexts))
					{
						matchingParamFound = true;
						break;
					}
				}
			}
			if (matchingParamFound) break;
		}
		if (!cteParamFound || matchingParamFound) return true;
	}
	return false;
}

// ***************************************************************************

CBaseAction::CParameter::CParameter ()
{
//	Visible = true;
	Type = Constant;
}

// ***************************************************************************

static bool getParam (CBaseAction::CParameter::TType type, ucstring &paramName, ucstring &paramValue, const std::string &argu, uint paramId)
{
	const string separator = "|";
	const string equal_separator = "=";

	// Go to param start
	uint index = 0;
	string::size_type pos = 0;
	while (index != paramId)
	{
		// Not found ?
		pos = argu.find_first_of(separator, pos);
		if (pos == string::npos)
			return false;

		pos++;
		index++;
	}

	// Pos is valid ?
	if (pos < argu.size ())
	{
		// End
		string::size_type end = argu.find_first_of(separator, pos);
		if (end == string::npos)
			end = argu.size();

		// Look for a '='
		string::size_type equal = argu.find_first_of(equal_separator, pos);
		if ((equal != string::npos) && (equal >= end))
			equal = string::npos;

		// Equal is present ?
		if (equal != string::npos)
		{
			// Extract parameter name
			paramName = argu.substr(pos, equal-pos);
			pos = equal+1;
		}

		// Value ?
		if(type==CBaseAction::CParameter::User || type==CBaseAction::CParameter::UserName)
			paramValue.fromUtf8(argu.substr(pos, end-pos));
		else
			paramValue = argu.substr(pos, end-pos);

		// Ok
		return true;
	}
	return false;
}

ucstring CBaseAction::getActionLocalizedText (const CAction::CName &name) const
{
	// Action base name
	ucstring temp = CI18N::get(LocalizedName);

	// Get the parameter
	uint i;
	for (i=0; i<Parameters.size (); i++)
	{
		bool parameterOk = false;
		const CParameter &parameter = Parameters[i];
		ucstring paramName;
		ucstring paramValue;

		// Get the param from the argu
		if (getParam (parameter.Type, paramName, paramValue, name.Argu, i))
		{
			switch (parameter.Type)
			{
			case CParameter::Hidden:
				if ((ucstring (parameter.DefaultValue) == paramValue) && (ucstring (parameter.Name) == paramName))
					parameterOk = true;
				break;
			case CParameter::Constant:
				{
					uint j;
					for (j=0; j<parameter.Values.size (); j++)
					{
						// This value ?
						const CParameter::CValue &value = parameter.Values[j];
						if (ucstring(value.Value) == paramValue)
						{
							temp += " ";

							if ((value.LocalizedValue.size() >= 2) &&
								(value.LocalizedValue[0]=='u') && (value.LocalizedValue[1]=='i'))
								temp += CI18N::get(value.LocalizedValue);
							else
								temp += value.LocalizedValue;
							parameterOk = true;
							break;
						}
					}
				}
				break;
			case CParameter::User:
			case CParameter::UserName:
				temp += " ";
				temp += paramValue;
				parameterOk = true;
				break;
			}

			// Parameter not found ? Next base action..
			if (!parameterOk)
				break;
		}
	}

	// Found ?
	if (i==Parameters.size ())
		return temp;

	return ucstring("");
}

// ***************************************************************************
// CActionsManager
// ***************************************************************************

const CActionsManager::TComboActionMap &CActionsManager::getComboActionMap () const
{
	return _ComboAction;
}

// ***************************************************************************

const CActionsManager::TActionComboMap &CActionsManager::getActionComboMap () const
{
	return _ActionCombo;
}

// ***************************************************************************

const CActionsManager::CCategoryLocator *CActionsManager::getActionLocator (const CAction::CName &name) const
{
	// Look for the base action
	TActionBaseActionMap::const_iterator ite = _ActionCategory.find (name.Name);
	while ((ite != _ActionCategory.end ()) && (ite->first == name.Name))
	{
		// Ref on the base action
		const CBaseAction &baseAction = _Categories[ite->second.CategoryId].BaseActions[ite->second.BaseActionId];

		// Check parameters
		uint i;
		for (i=0; i<baseAction.Parameters.size (); i++)
		{
			bool parameterOk = false;
			const CBaseAction::CParameter &parameter = baseAction.Parameters[i];
			ucstring paramName;
			ucstring paramValue;

			// Get the param from the argu
			if (getParam (parameter.Type, paramName, paramValue, name.Argu, i))
			{
				switch (parameter.Type)
				{
				case CBaseAction::CParameter::Hidden:
					if ((ucstring (parameter.DefaultValue) == paramValue) && (ucstring (parameter.Name) == paramName))
						parameterOk = true;
					break;
				case CBaseAction::CParameter::Constant:
					{
						// If the value of the action param match with one of the values of the base action param so its ok
						uint j;
						for (j=0; j<parameter.Values.size (); j++)
						{
							const CBaseAction::CParameter::CValue &value = parameter.Values[j];
							if (ucstring(value.Value) == paramValue)
							{
								parameterOk = true;
								break;
							}
						}
					}
					break;
				case CBaseAction::CParameter::User:
				case CBaseAction::CParameter::UserName:
					parameterOk = true;
					break;
				}
			}

			// Parameter not found ? Next base action..
			if (!parameterOk)
				break;
		}

		// Found ?
		if (i==baseAction.Parameters.size ())
			return &ite->second;

		ite++;
	}

	return NULL;
}

// ***************************************************************************
const CBaseAction *CActionsManager::getBaseAction (const CAction::CName &name) const
{
	const CCategoryLocator *pCL = getActionLocator(name);
	if (pCL == NULL) return NULL;
	return &_Categories[pCL->CategoryId].BaseActions[pCL->BaseActionId];
}

// ***************************************************************************
void CActionsManager::removeBaseAction(const CAction::CName &name)
{
	const CCategoryLocator *pCL = getActionLocator(name);
	if (pCL == NULL)
	{
		nlwarning("Action %s %s not found.", name.Name.c_str(), name.Argu.c_str());
		return;
	}
	std::vector<CBaseAction> &baseActions = _Categories[pCL->CategoryId].BaseActions;
	baseActions.erase(baseActions.begin() + pCL->BaseActionId);
}

// ***************************************************************************
const CCategory *CActionsManager::getCategory (const CAction::CName &name) const
{
	const CCategoryLocator *pCL = getActionLocator(name);
	if (pCL == NULL) return NULL;
	return &_Categories[pCL->CategoryId];
}


// ***************************************************************************
void	CActionsManager::forceDisplayForAction(const CAction::CName &name, bool state)
{
	if(state)
		_ActionForceDisplay.insert(name);
	else
		_ActionForceDisplay.erase(name);
}

// ***************************************************************************
bool	CActionsManager::isActionDisplayForced(const CAction::CName &name) const
{
	return _ActionForceDisplay.find(name)!=_ActionForceDisplay.end();
}

// ***************************************************************************
ucstring CActionsManager::getActionLocalizedText (const CAction::CName &name) const
{
	const CBaseAction *baseAction= getBaseAction(name);
	if(!baseAction)
		return ucstring();
	return baseAction->getActionLocalizedText(name);
}


// ***************************************************************************
// CActionsContext
// ***************************************************************************


CActionsContext::CActionsContext()
{
	_Context = "game";
}

bool CActionsContext::addActionsManager (CActionsManager *actionManager, const std::string &category)
{
	return _ActionsManagers.insert (TActionsManagerMap::value_type(category, actionManager)).second;
}

// ***************************************************************************
CActionsManager *CActionsContext::getActionsManager (const std::string &category) const
{
	TActionsManagerMap::const_iterator ite = _ActionsManagers.find (category);
	if (ite != _ActionsManagers.end())
		return ite->second;
	else
	{
		ite = _ActionsManagers.find ("");
		if (ite != _ActionsManagers.end())
			return ite->second;
		return NULL;
	}
}

// ***************************************************************************
bool CActionsContext::matchContext(const std::string &contexts) const
{
	std::vector<std::string> contextList;
	splitString(contexts, ",", contextList);
	for (uint k = 0; k < contextList.size(); ++k)
	{
		std::string currContext = contextList[k];
		while(strFindReplace(currContext, " ", ""));
		while(strFindReplace(currContext, "\t", ""));
		if (nlstricmp(currContext, _Context) == 0) return true;
	}
	return false;
}

// ***************************************************************************
void CActionsContext::removeAllCombos()
{
	for (TActionsManagerMap::iterator it = _ActionsManagers.begin(); it != _ActionsManagers.end(); ++it)
	{
		it->second->removeAllCombos();
	}
}

// ***************************************************************************