#ifndef RLV_HELPER_H
#define RLV_HELPER_H

#include "llboost.h"
#include "llinventorymodel.h"
#include "llselectmgr.h"
#include "llviewercontrol.h"
#include "llviewerobjectlist.h"
#include "llwlparamset.h"

#include "rlvmultistringsearch.h"

// ============================================================================
// Extensions
//

// Comment out if you don't want the Advanced / RLVa menu (may prevent enabling some extensions or experimental features - see below)
#define RLV_ADVANCED_MENU
// Comment out if you provide your own way to enable/disable RLVa
#define RLV_ADVANCED_TOGGLE_RLVA

// Provides access to "advanced" feature through the RLVa debug menu
#define RLV_EXTENSION_ENABLE_WEAR			// "Enable Wear"
#define RLV_EXTENSION_HIDELOCKED			// "Hide locked layers", "Hide locked attachments" and "Hide locked inventory"
#define RLV_EXTENSION_FLOATER_RESTRICTIONS	// Enables the Advanced / RLVa / Restrictions... floater

// Extensions
#define RLV_EXTENSION_CMD_GETSETDEBUG_EX	// Extends the debug variables accessible through @getdebug_xxx/@setdebug_xxx
#define RLV_EXTENSION_CMD_FINDFOLDERS		// @findfolders:<option>=<channel> - @findfolder with multiple results
#define RLV_EXTENSION_FLAG_NOSTRIP			// Layers and attachments marked as "nostrip" are exempt from @detach/@remoutfit
#define RLV_EXTENSION_STARTLOCATION			// Reenables "Start Location" at login if not @tploc=n or @unsit=n restricted at last logoff
#define RLV_EXPERIMENTAL					// Enables/disables experimental features en masse

// Experimental features
#ifdef RLV_EXPERIMENTAL
	// Stable (will mature to RLV_EXTENSION_XXX in next release if no bugs are found)
	#define RLV_EXPERIMENTAL_FARTOUCH_FEEDBACK		// Enables "cleaner" UI responses when fartouch blocks something

	// Under testing (stable, but requires further testing - safe for public release but may be quirky)

	// Under development (don't include in public release)
	#if LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG
	#endif // LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG
#endif // RLV_EXPERIMENTAL

// ============================================================================
// Defines
//

// Version of the specifcation we support
const S32 RLV_VERSION_MAJOR = 1;
const S32 RLV_VERSION_MINOR = 20;
const S32 RLV_VERSION_PATCH = 0;

// Implementation version
const S32 RLVa_VERSION_MAJOR = 1;
const S32 RLVa_VERSION_MINOR = 0;
const S32 RLVa_VERSION_PATCH = 1;
const S32 RLVa_VERSION_BUILD = 7;

// The official viewer version we're patching against
#define RLV_MAKE_TARGET(x, y, z)	((x << 16) | (y << 8) | z)
#define RLV_TARGET					RLV_MAKE_TARGET(1, 22, 11)

// Defining these makes it easier if we ever need to change our tag
#define RLV_WARNS	LL_WARNS("RLV")
#define RLV_INFOS	LL_INFOS("RLV")
#define RLV_DEBUGS	LL_DEBUGS("RLV")

#if LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG
	// Turn on extended debugging information
	#define RLV_DEBUG
	// Make sure we halt execution on errors
	#define RLV_ERRS  LL_ERRS("RLV")
	// Uncomment to enable the Advanced / RLVa / Unit Tests menu (non-public)
	//#define RLV_DEBUG_TESTS
#else
	// Uncomment if you want extended debugging information on release builds
	//#define RLV_DEBUG
	// Don't halt execution on errors in release
	#define RLV_ERRS  LL_WARNS("RLV")
#endif // LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG

#define RLV_ROOT_FOLDER					"#RLV"
#define RLV_CMD_PREFIX					'@'
#define RLV_PUTINV_PREFIX				"#RLV/~"
#define RLV_SETROT_OFFSET				F_PI_BY_TWO		// @setrot is off by 90� with the rest of SL

#define RLV_FOLDER_FLAG_NOSTRIP			"nostrip"
#define RLV_FOLDER_PREFIX_HIDDEN		'.'
#define RLV_FOLDER_PREFIX_PUTINV    	'~'

// ============================================================================
// Enumeration declarations
//

// NOTE: * any changes to this enumeration should be reflected in initLookupTable()
//       * only uncomment the ones we actually use in a switch() to keep the size of the lookup table down
enum ERlvBehaviour {
	RLV_BHVR_VERSION = 0,			// "version"
	RLV_BHVR_DETACH,				// "detach"
//	RLV_BHVR_SENDCHAT,				// "sendchat"
//	RLV_BHVR_EMOTE,					// "emote"
//	RLV_BHVR_CHATSHOUT,				// "chatshout"
//	RLV_BHVR_CHATNORMAL,			// "chatnormal"
//	RLV_BHVR_CHATWHISPER,			// "chatwhisper"
	RLV_BHVR_REDIRCHAT,				// "redirchat"
	RLV_BHVR_REDIREMOTE,			// "rediremote"
	RLV_BHVR_SENDIM,				// "sendim"
	RLV_BHVR_RECVCHAT,				// "recvchat"
	RLV_BHVR_RECVEMOTE,				// "recvemote"
	RLV_BHVR_RECVIM,				// "recvim"
//	RLV_BHVR_TPLM,					// "tplm"
	RLV_BHVR_TPLOC,					// "tploc"
	RLV_BHVR_TPLURE,				// "tplure"
	RLV_BHVR_SITTP,					// "sittp"
//	RLV_BHVR_CLEAR,					// "clear"
	RLV_BHVR_EDIT,					// "edit"
	RLV_BHVR_REZ,					// "rez"
	RLV_BHVR_ADDOUTFIT,				// "addoutfit"
	RLV_BHVR_REMOUTFIT,				// "remoutfit"
	RLV_BHVR_GETOUTFIT,				// "getoutfit"
	RLV_BHVR_GETATTACH,				// "getattach"
	RLV_BHVR_SHOWINV,				// "showinv"
//	RLV_BHVR_VIEWNOTE,				// "viewnote"
	RLV_BHVR_UNSIT,					// "unsit"
	RLV_BHVR_SIT,					// "sit"
//	RLV_BHVR_SENDCHANNEL,			// "sendchannel"
	RLV_BHVR_GETSTATUS,				// "getstatus"
	RLV_BHVR_GETSTATUSALL,			// "getstatusall"
	RLV_BHVR_GETINV,				// "getinv"
	RLV_BHVR_GETINVWORN,			// "getinvworn"
	RLV_BHVR_FINDFOLDER,			// "findfolder"
	RLV_BHVR_FINDFOLDERS,			// "findfolders"
	RLV_BHVR_ATTACH,				// "attach"
	RLV_BHVR_ATTACHALL,				// "attachall"
	RLV_BHVR_DETACHALL,				// "detachall"
	RLV_BHVR_GETPATH,				// "getpath"
	RLV_BHVR_ATTACHTHIS,			// "attachthis"
	RLV_BHVR_ATTACHALLTHIS,			// "attachallthis"
	RLV_BHVR_DETACHTHIS,			// "detachthis"
	RLV_BHVR_DETACHALLTHIS,			// "detachallthis"
	RLV_BHVR_FARTOUCH,				// "fartouch"
	RLV_BHVR_SHOWWORLDMAP,			// "showworldmap"
	RLV_BHVR_SHOWMINIMAP,			// "showminimap"
	RLV_BHVR_SHOWLOC,				// "showloc"
	RLV_BHVR_TPTO,					// "tpto"
	RLV_BHVR_ACCEPTTP,				// "accepttp"
	RLV_BHVR_SHOWNAMES,				// "shownames"
	RLV_BHVR_FLY,					// "fly"
	RLV_BHVR_GETSITID,				// "getsitid"
	RLV_BHVR_SETDEBUG,				// "setdebug"
	RLV_BHVR_SETENV,				// "setenv"
	RLV_BHVR_DETACHME,				// "detachme"
	RLV_BHVR_SHOWHOVERTEXTALL,		// "showhovertextall"
	RLV_BHVR_SHOWHOVERTEXTWORLD,	// "showhovertextworld"
	RLV_BHVR_SHOWHOVERTEXTHUD,		// "showhovertexthud"
	RLV_BHVR_SHOWHOVERTEXT,			// "showhovertext"
	RLV_BHVR_NOTIFY,				// "notify"

	RLV_BHVR_COUNT,
	RLV_BHVR_UNKNOWN
};

enum ERlvParamType {
	RLV_TYPE_UNKNOWN,
	RLV_TYPE_ADD,				// <param> == "n"|"add"
	RLV_TYPE_REMOVE,			// <param> == "y"|"rem"
	RLV_TYPE_FORCE,				// <param> == "force"
	RLV_TYPE_REPLY				// <param> == <number>
};

enum ERlvCmdRet {
	RLV_RET_NOERROR,			// Command executed succesfully
	RLV_RET_RETAINED,			// Command was retained
	RLV_RET_FAILED,				// Command failed (general failure)
	RLV_RET_FAILED_SYNTAX,		// Command failed (syntax error)
	RLV_RET_FAILED_UNSET,		// Command failed (unset restriction)
	RLV_RET_FAILED_DUPLICATE,	// Command failed (duplicate)
	RLV_RET_FAILED_OPTION,		// Command failed (invalid option)
	RLV_RET_UNKNOWN				// Command unkown
};

// ============================================================================
/*
 * RlvCommand
 * ==========
 * Encapsulates an "RLV command" (duh :p)
 *
 */

class RlvCommand
{
public:
	/*
	 * Constructors
	 */
	explicit RlvCommand(const std::string& strCommand);
	RlvCommand(const RlvCommand& rlvCmd);

	/*
	 * Accessors
	 */
	BOOL isValid() const { return m_fValid; }

	const std::string& getBehaviour() const		{ return m_strBehaviour; }
	ERlvBehaviour      getBehaviourType() const	{ return m_eBehaviour; }
	const std::string& getOption() const		{ return m_strOption; }
	const std::string& getParam() const			{ return m_strParam; }
	ERlvParamType      getParamType() const		{ return m_eParamType; }

	std::string        asString() const;

	/*
	 * Operators
	 */
	bool operator ==(const RlvCommand&) const;

	// Parses an RLV command into its "tokens"
	static BOOL parseCommand(/*[in]*/ const std::string& strCommand,  
		/*[out]*/ std::string& strBehaviour, /*[out]*/ std::string& strOption, /*[out]*/ std::string& strParam);
	static void initLookupTable();

	/*
	 * Member variables
	 */
protected:
	BOOL			m_fValid;
	std::string		m_strBehaviour;
	ERlvBehaviour	m_eBehaviour;
	std::string		m_strOption;
	std::string		m_strParam;
	ERlvParamType	m_eParamType;

	static RlvMultiStringSearch m_BhvrLookup;
	friend class RlvHandler;
};

// ============================================================================
/*
 * RlvObject
 * =========
 * Encapsulates an "RLV Object" (= an object that has issued an RLV command)
 *
 */

typedef std::list<RlvCommand> rlv_command_list_t;

class RlvObject
{
public:
	RlvObject(const LLUUID& uuid) : m_UUID(uuid), m_nLookupMisses(0) { m_fLookup = (NULL != gObjectList.findObject(uuid)); }

	BOOL addCommand(const RlvCommand& rlvCmd);
	BOOL removeCommand(const RlvCommand& rlvCmd);

	BOOL hasBehaviour(ERlvBehaviour eBehaviour) const;
	BOOL hasBehaviour(const std::string& strBehaviour) const;
	BOOL hasBehaviour(ERlvBehaviour eBehaviour, const std::string& strOption) const;
	BOOL hasBehaviour(const std::string& strBehaviour, const std::string& strOption) const;

	std::string getStatusString(const std::string& strMatch) const;

	const rlv_command_list_t* getCommandList() const { return &m_Commands; }
protected:
	LLUUID             m_UUID;				// The object's UUID
	bool               m_fLookup;			// TRUE if the object existed in gObjectList at one point in time
	S16                m_nLookupMisses;		// Count of unsuccessful lookups in gObjectList by the GC
	rlv_command_list_t m_Commands;			// List of behaviours held by this object (in the order they were received)

	friend class RlvHandler;
};

// ============================================================================
/*
 * RlvCriteriaCategoryCollector
 * ============================
 * Criteria based folder matching filter used by @findfolder and @findfolders
 *
 */

class RlvCriteriaCategoryCollector : public LLInventoryCollectFunctor
{
public:
	RlvCriteriaCategoryCollector(const std::string& strCriteria)
	{
		typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
		boost::char_separator<char> sep("&&", "", boost::drop_empty_tokens);
		tokenizer tokens(strCriteria, sep);
		for (tokenizer::iterator itToken = tokens.begin(); itToken != tokens.end(); ++itToken)
			m_Criteria.push_back(*itToken);
	}
	virtual ~RlvCriteriaCategoryCollector() {}

	virtual bool operator()(LLInventoryCategory* pFolder, LLInventoryItem* pItem)
	{
		if ( (!pFolder) || (m_Criteria.empty()) )	// We're only interested in matching folders, we don't care about items
			return false;							// (if there are no criteria then we don't want to return a match)

		std::string strFolderName = pFolder->getName();
		LLStringUtil::toLower(strFolderName);

		if ( (strFolderName.empty()) ||	(RLV_FOLDER_PREFIX_HIDDEN == strFolderName[0]) )
			return false;

		for (std::list<std::string>::const_iterator itCrit = m_Criteria.begin(); itCrit != m_Criteria.end(); ++itCrit)
			if (-1 == strFolderName.find(*itCrit))	// Return false on the first mismatch
				return false;
		return true;
	}

protected:
	std::list<std::string> m_Criteria;
};

// ============================================================================
/*
 * RlvWearableItemCollector
 * ========================
 * Inventory item filter used by attach/detach/attachall/detachall/getinvworn (also used by "Add/Replace Outfit" and "Take Off Items")
 *
 */

class RlvWearableItemCollector : public LLInventoryCollectFunctor
{
public:
	RlvWearableItemCollector(const LLUUID& idFolder, bool fAttach, bool fMatchAll)
		: m_idFolder(idFolder), m_fAttach(fAttach), m_fMatchAll(fMatchAll)
	{
		m_Wearable.push_back(idFolder);
	}
	virtual ~RlvWearableItemCollector() {}

	virtual bool operator()(LLInventoryCategory* pFolder, LLInventoryItem* pItem);

	const LLUUID& getFoldedParent(const LLUUID& idFolder) const;
protected:
	bool         m_fAttach;
	bool         m_fMatchAll;
	const LLUUID m_idFolder;

	bool onCollectFolder(const LLInventoryCategory* pFolder);
	bool onCollectItem(const LLInventoryItem* pItem);

	std::list<LLUUID> m_Tentative;
	std::list<LLUUID> m_Wearable;

	std::map<LLUUID, LLUUID> m_Folding;
};

// ============================================================================
/*
 * RlvRetainedCommand
 * ==================
 *
 */

struct RlvRetainedCommand
{
public:
	std::string strObject;
	LLUUID      idObject;
	std::string strCmd;

	RlvRetainedCommand(const std::string obj, const LLUUID& uuid, const std::string& cmd) : strObject(obj), idObject(uuid), strCmd(cmd) {}
private:
	RlvRetainedCommand();
};
typedef std::list<RlvRetainedCommand> rlv_retained_list_t;

// ============================================================================
/*
 * RlvWLSnapshot
 * =============
 *
 */

struct RlvWLSnapshot
{
public:
	static void           restoreSnapshot(const RlvWLSnapshot* pWLSnapshot);
	static RlvWLSnapshot* takeSnapshot();
private:
	RlvWLSnapshot() {}

	bool		 fIsRunning;
	bool		 fUseLindenTime;
	LLWLParamSet WLParams;
};

// ============================================================================
/*
 * RlvSettings
 * ===========
 *
 */

#define RLV_SETTING_MAIN				"RestrainedLife"
#define RLV_SETTING_DEBUG				"RestrainedLifeDebug"
#define RLV_SETTING_NOSETENV			"RestrainedLifeNoSetEnv"
#define RLV_SETTING_FORBIDGIVETORLV		"RestrainedLifeForbidGiveToRLV"

#define RLV_SETTING_ENABLEWEAR			"RLVaEnableWear"
#define RLV_SETTING_ENABLELEGACYNAMING  "RLVaEnableLegacyNaming"
#define RLV_SETTING_HIDELOCKEDLAYER		"RLVaHideLockedLayers"
#define RLV_SETTING_HIDELOCKEDATTACH	"RLVaHideLockedAttachments"
#define RLV_SETTING_HIDELOCKEDINVENTORY	"RLVaHideLockedInventory"
#define RLV_SETTING_LOGINLASTLOCATION	"RLVaLoginLastLocation"
#define RLV_SETTING_SHOWNAMETAGS		"RLVaShowNameTags"

inline BOOL rlvGetSettingBOOL(const std::string& strSetting, BOOL fDefault)
{
	return (gSavedSettings.controlExists(strSetting)) ? gSavedSettings.getBOOL(strSetting) : fDefault;
}
inline BOOL rlvGetPerUserSettingsBOOL(const std::string& strSetting, BOOL fDefault)
{
	return (gSavedPerAccountSettings.controlExists(strSetting)) ? gSavedPerAccountSettings.getBOOL(strSetting) : fDefault;
}

class RlvSettings
{
public:
	static BOOL getDebug()					{ return rlvGetSettingBOOL(RLV_SETTING_DEBUG, FALSE); }
	static BOOL getForbidGiveToRLV()		{ return rlvGetSettingBOOL(RLV_SETTING_FORBIDGIVETORLV, TRUE); }

	static BOOL getEnableWear()				{ return rlvGetSettingBOOL(RLV_SETTING_ENABLEWEAR, FALSE); }
	static BOOL getHideLockedLayers()		{ return rlvGetSettingBOOL(RLV_SETTING_HIDELOCKEDLAYER, FALSE); }		
	static BOOL getHideLockedAttach()		{ return rlvGetSettingBOOL(RLV_SETTING_HIDELOCKEDATTACH, FALSE); }
	static BOOL getHideLockedInventory()	{ return rlvGetSettingBOOL(RLV_SETTING_HIDELOCKEDINVENTORY, FALSE); }

	#ifdef RLV_EXTENSION_STARTLOCATION
		static BOOL getLoginLastLocation()	{ return rlvGetPerUserSettingsBOOL(RLV_SETTING_LOGINLASTLOCATION, TRUE); }
		static void updateLoginLastLocation();
	#endif // RLV_EXTENSION_STARTLOCATION

	static BOOL fShowNameTags;
};

// ============================================================================
/*
 * State keeping classes/structure
 *
 */

struct RlvRedirInfo
{
	S16 nRedirChat;
	S16 nRedirEmote;

	RlvRedirInfo() : nRedirChat(0), nRedirEmote(0) {}
	bool isActive() { return (nRedirChat + nRedirEmote) != 0; }
};

struct RlvReattachInfo
{
	LLUUID idItem;
	bool   fInInventory;
	bool   fAssetSaved;

	RlvReattachInfo() : idItem(), fInInventory(false), fAssetSaved(false) {}
};

// ============================================================================
/*
 * Various helper classes/timers/functors
 *
 */

class RlvGCTimer : public LLEventTimer
{
public:
	RlvGCTimer() : LLEventTimer(30.0) {}
	virtual BOOL tick();
};

class RlvCurrentlyWorn : public LLInventoryFetchObserver
{
public:
	RlvCurrentlyWorn() {}
	~RlvCurrentlyWorn() {}
	virtual void done() {}

	static void fetchWorn();
	void fetchItem(const LLUUID& idItem);
};

struct RlvSelectHasLockedAttach : public LLSelectedNodeFunctor
{
	virtual bool apply(LLSelectNode* pNode);
};

struct RlvSelectIsOwnedByOrGroupOwned : public LLSelectedNodeFunctor
{
	RlvSelectIsOwnedByOrGroupOwned(const LLUUID& uuid) : m_idAgent(uuid) {}
	virtual bool apply(LLSelectNode* pNode);
	LLUUID m_idAgent;
};

struct RlvSelectIsSittingOn : public LLSelectedNodeFunctor
{
	RlvSelectIsSittingOn(LLXform* pObject) : m_pObject(pObject) {}
	virtual bool apply(LLSelectNode* pNode);
	LLXform* m_pObject;
};

// ============================================================================
/*
 * Various helper functions
 *
 */

BOOL rlvAttachToEnabler(void* pParam);
bool rlvCanDeleteOrReturn();
S32  rlvGetDirectDescendentsCount(const LLInventoryCategory* pFolder, LLAssetType::EType type);
bool rlvIsEmote(const std::string& strUTF8Text);
bool rlvIsValidChannel(S32 nChannel);
bool rlvIsWearingItem(const LLInventoryItem* pItem);

void rlvForceDetach(LLViewerJointAttachment* pAttachPt);
void rlvSendBusyMessage(const LLUUID& idTo, const std::string& strMsg, const LLUUID& idSession = LLUUID::null);
bool rlvSendChatReply(const std::string& strChannel, const std::string& strReply);
bool rlvSendChatReply(S32 nChannel, const std::string& strReply);

void rlvStringReplace(std::string& strText, std::string strFrom, const std::string& strTo);
std::string rlvGetFirstParenthesisedText(const std::string& strText, std::string::size_type* pidxMatch = NULL);
std::string rlvGetLastParenthesisedText(const std::string& strText, std::string::size_type* pidxStart = NULL);

#ifdef RLV_ADVANCED_TOGGLE_RLVA
	// "Advanced / RLVa / Enable RLV" menu option
	void rlvDbgToggleEnabled(void*);
	BOOL rlvDbgGetEnabled(void*);
#endif // RLV_ADVANCED_TOGGLE_RLVA

// ============================================================================
// Inlined class member functions
//

inline bool RlvCommand::operator ==(const RlvCommand& rhs) const
{
	// The specification notes that "@detach=n" is semantically identical to "@detach=add" (same for "y" and "rem"
	return (m_strBehaviour == rhs.m_strBehaviour) && (m_strOption == rhs.m_strOption) &&
		( (RLV_TYPE_UNKNOWN != m_eParamType) ? (m_eParamType == rhs.m_eParamType) : (m_strParam == rhs.m_strParam) );
}

inline void RlvCurrentlyWorn::fetchItem(const LLUUID& idItem)
{ 
	if (idItem.notNull()) 
	{
		LLInventoryFetchObserver::item_ref_t idItems; 
		idItems.push_back(idItem);
		fetchItems(idItems);
	}
}

// ============================================================================
// Inlined helper functions
//

inline bool rlvIsEmote(const std::string& strUTF8Text)
{
	return (strUTF8Text.length() > 4) && ( (strUTF8Text.compare(0, 4, "/me ") == 0) || (strUTF8Text.compare(0, 4, "/me'") == 0) );
}

// Checked: 2009-08-05 (RLVa-1.0.1e) | Added: RLVa-1.0.0e
inline bool rlvIsValidChannel(S32 nChannel)
{
	return (nChannel >= 0) && (CHAT_CHANNEL_DEBUG != nChannel);
}

// Checked: 2009-08-05 (RLVa-1.0.1e) | Added: RLVa-1.0.0e
inline bool rlvSendChatReply(const std::string& strChannel, const std::string& strReply)
{
	S32 nChannel;
	return (LLStringUtil::convertToS32(strChannel, nChannel)) ? rlvSendChatReply(nChannel, strReply) : false;
}

// ============================================================================

#endif // RLV_HELPER_H