#ifndef RLV_HELPER_H
#define RLV_HELPER_H

#include "llagent.h"
#include "llboost.h"
#include "llgesturemgr.h"
#include "llinventorymodel.h"
#include "llviewerinventory.h"
#include "llvoavatar.h"
#include "llwlparamset.h"
#include "rlvdefines.h"
#include "rlvcommon.h"

#ifdef LL_WINDOWS
	#pragma warning (push)
	#pragma warning (disable : 4702) // warning C4702: unreachable code
#endif
#include <boost/variant.hpp>
#ifdef LL_WINDOWS
	#pragma warning (pop)
#endif

// ============================================================================
// RlvCommand
//

class RlvCommand
{
public:
	explicit RlvCommand(const std::string& strCommand);

	/*
	 * Member functions
	 */
public:
	std::string        asString() const;
	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; }
	bool               isStrict() const			{ return m_fStrict; }
	bool               isValid() const          { return m_fValid; }

	static ERlvBehaviour      getBehaviourFromString(const std::string& strBhvr, bool* pfStrict = NULL);
	static const std::string& getStringFromBehaviour(ERlvBehaviour eBhvr);
	static bool               hasStrictVariant(ERlvBehaviour eBhvr);

	static void initLookupTable();
protected:
	static bool parseCommand(const std::string& strCommand, std::string& strBehaviour, std::string& strOption,  std::string& strParam);

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

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

	typedef std::map<std::string, ERlvBehaviour> RlvBhvrTable;
	static RlvBhvrTable m_BhvrMap;

	friend class RlvHandler;
};
typedef std::list<RlvCommand> rlv_command_list_t;

// ============================================================================
// RlvObject
//

class RlvObject
{
public:
	RlvObject(const LLUUID& idObj);

	/*
	 * Member functions
	 */
public:
	bool addCommand(const RlvCommand& rlvCmd);
	bool removeCommand(const RlvCommand& rlvCmd);

	std::string getStatusString(const std::string& strMatch) const;
	bool        hasBehaviour(ERlvBehaviour eBehaviour, bool fStrictOnly) const;
	bool        hasBehaviour(ERlvBehaviour eBehaviour, const std::string& strOption, bool fStrictOnly) const;

	const rlv_command_list_t* getCommandList() const { return &m_Commands; }

	/*
	 * Member variables
	 */
protected:
	LLUUID             m_UUID;				// The object's UUID
	S32                m_idxAttachPt;		// The object's attachment point (or 0 if it's not an attachment)
	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;
};

// ============================================================================
// RlvAttachmentManager - Self contained helper class that automagically takes care of the nitty gritty of force detaching/reattaching
//

class RlvAttachmentManager
{
public:
	RlvAttachmentManager() : m_pTimer(NULL) {}
	~RlvAttachmentManager() { delete m_pTimer; }

	/*
	 * Member functions
	 */
public:
	// NOTE: the following two do *not* respect attachment locks so use with care
	static void attach(const LLUUID& idItem, S32 idxAttachPt);
	static void detach(LLViewerJointAttachment* pAttachPt);
protected:
	void startTimer() { if (!m_pTimer) m_pTimer = new RlvAttachmentManagerTimer(this); }

	/*
	 * Event handlers
	 */
public:
	void onAttach(LLViewerJointAttachment* pAttachPt);
	void onDetach(LLViewerJointAttachment* pAttachPt);
	void onSavedAssetIntoInventory(const LLUUID& idItem);
	BOOL onTimer();
	void onWearAttachment(const LLUUID& idItem);

	/*
	 * Member variables
	 */
protected:
	typedef std::map<S32, LLUUID> rlv_detach_map_t;
	rlv_detach_map_t m_PendingDetach;

	struct RlvReattachInfo
	{
		RlvReattachInfo(const LLUUID& itemid) : idItem(itemid), fAssetSaved(false), tsAttach(0) 
			{ tsDetach = LLFrameTimer::getElapsedSeconds(); }

		LLUUID idItem;
		bool   fAssetSaved;
		F64    tsDetach;
		F64    tsAttach;
	protected:
		RlvReattachInfo();
	};
	typedef std::map<S32, RlvReattachInfo> rlv_attach_map_t;
	rlv_attach_map_t m_PendingAttach;

	struct RlvWearInfo
	{
		RlvWearInfo(LLUUID itemid) : idItem(itemid) { tsWear = LLFrameTimer::getElapsedSeconds(); }

		LLUUID idItem;
		F64    tsWear;
		std::map<S32, LLUUID> attachPts;
	protected:
		RlvWearInfo();
	};
	typedef std::map<LLUUID, RlvWearInfo> rlv_wear_map_t;
	rlv_wear_map_t   m_PendingWear;

	class RlvAttachmentManagerTimer : public LLEventTimer
	{
	public:
		RlvAttachmentManagerTimer(RlvAttachmentManager* pMgr) : LLEventTimer(10), m_pMgr(pMgr) {}
		virtual ~RlvAttachmentManagerTimer() { m_pMgr->m_pTimer = NULL; }
		virtual BOOL tick() { return m_pMgr->onTimer(); }
		RlvAttachmentManager* m_pMgr;
	} *m_pTimer;
};

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

class RlvCriteriaCategoryCollector : public LLInventoryCollectFunctor
{
public:
	RlvCriteriaCategoryCollector(const std::string& strCriteria)
	{
		std::string::size_type idxIt, idxLast = 0;
		while (idxLast < strCriteria.length())
		{
			idxIt = strCriteria.find("&&", idxLast);
			if (std::string::npos == idxIt)
				idxIt = strCriteria.length();
			if (idxIt != idxLast)
				m_Criteria.push_back(strCriteria.substr(idxLast, idxIt - idxLast));
			idxLast = idxIt + 2;
		}
	}
	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);

		// NOTE: hidden or "give to #RLV" folders can never be a match
		if ( (strFolderName.empty()) ||
			 (RLV_FOLDER_PREFIX_HIDDEN == strFolderName[0]) || (RLV_FOLDER_PREFIX_PUTINV == strFolderName[0]) )
		{
			return false;
		}

		for (std::list<std::string>::const_iterator itCrit = m_Criteria.begin(); itCrit != m_Criteria.end(); ++itCrit)
			if (std::string::npos == 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
//

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;
};

// ============================================================================
// RlvForceWear
//

class RlvForceWear
{
public:
	// Folders
	enum eWearAction { ACTION_ATTACH, ACTION_DETACH };
	enum eWearFlags { FLAG_NONE = 0x00, FLAG_MATCHALL = 0x01, FLAG_DEFAULT = FLAG_NONE };
	void forceFolder(const LLViewerInventoryCategory* pFolder, eWearAction eAction, eWearFlags eFlags);

	// Attachments
	static bool isForceDetachable(LLViewerJointAttachment* pAttachPt, bool fCheckComposite = true, LLViewerObject* pExceptObj = NULL);
	void forceDetach(LLViewerJointAttachment* ptAttachPt);

	// Wearables
	static bool isForceRemovable(EWearableType wtType, bool fCheckComposite = true, const LLUUID& idExcept = LLUUID::null);
	void forceRemove(EWearableType wtType);

	// General purpose
	static bool isWearableItem(const LLInventoryItem* pItem);
	static bool isWearingItem(const LLInventoryItem* pItem);

public:
	void done()	{ processRem(); processAdd(); }
protected:
	void processAdd();
	void processRem();
	static void	onWearableArrived(LLWearable* pWearable, void* pParam);

protected:
	LLInventoryModel::item_array_t m_addAttachments, m_addWearables, m_addGestures, m_remGestures;
	std::list<LLViewerJointAttachment*> m_remAttachments;
	std::list<EWearableType> m_remWearables;
};

// ============================================================================
// RlvBehaviourNotifyObserver
//

class RlvBehaviourNotifyObserver : public RlvBehaviourObserver
{
public:
	virtual ~RlvBehaviourNotifyObserver() { }
	virtual void changed(const RlvCommand& rlvCmd, bool fInternal);

	void addNotify(const LLUUID& idObj, S32 nChannel, const std::string& strFilter)
	{
		m_Notifications.insert(std::pair<LLUUID, notifyData>(idObj, notifyData(nChannel, strFilter)));
	}
	void clearNotify(const LLUUID& idObj)
	{
		m_Notifications.erase(idObj);
	}
	bool hasNotify()
	{
		return (m_Notifications.size() != 0);
	}
	void removeNotify(const LLUUID& idObj, S32 nChannel, const std::string& strFilter)
	{
		for (std::multimap<LLUUID, notifyData>::iterator itNotify = m_Notifications.lower_bound(idObj),
				endNotify = m_Notifications.upper_bound(idObj); itNotify != endNotify; ++itNotify)
		{
			if ( (itNotify->second.nChannel == nChannel) && (itNotify->second.strFilter == strFilter) )
			{
				m_Notifications.erase(itNotify);
				break;
			}
		}
	}
protected:
	struct notifyData
	{
		S32         nChannel;
		std::string strFilter;
		notifyData(S32 channel, const std::string& filter) : nChannel(channel), strFilter(filter) {}
	};
	std::multimap<LLUUID, notifyData> m_Notifications;
};

// ============================================================================
// RlvRetainedCommand
//

struct RlvRetainedCommand
{
public:
	LLUUID      idObject;
	RlvCommand  rlvCmd;

	RlvRetainedCommand(const LLUUID& uuid, const RlvCommand& cmd) : idObject(uuid), rlvCmd(cmd) {}
private:
	RlvRetainedCommand();
};
typedef std::list<RlvRetainedCommand> rlv_retained_list_t;

// ============================================================================
// RlvException
//

typedef boost::variant<std::string, LLUUID, S32, ERlvBehaviour> RlvExceptionOption;

struct RlvException
{
public:
	LLUUID				idObject;    // UUID of the object that added the exception
	ERlvBehaviour		eBehaviour;  // Behaviour the exception applies to
	RlvExceptionOption	varOption;   // Exception data (type is dependent on eBehaviour)

	RlvException(const LLUUID& idObj, ERlvBehaviour eBhvr, const RlvExceptionOption& option) : idObject(idObj), eBehaviour(eBhvr), varOption(option) {}
private:
	RlvException();
};

// ============================================================================
// RlvWLSnapshot
//

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

	bool		 fIsRunning;
	bool		 fUseLindenTime;
	LLWLParamSet WLParams;
};

// ============================================================================
// 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);
};

class RlvGiveToRLVAgentOffer : public LLInventoryFetchDescendentsObserver
{
public:
	RlvGiveToRLVAgentOffer() {}
	virtual void done();
};

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

bool rlvCanDeleteOrReturn();
S32  rlvGetDirectDescendentsCount(const LLInventoryCategory* pFolder, LLAssetType::EType type);
bool rlvIsEmote(const std::string& strUTF8Text);
bool rlvIsValidReplyChannel(S32 nChannel);

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);

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);
void        rlvStringReplace(std::string& strText, std::string strFrom, const std::string& strTo);

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

// ============================================================================
// Debug helper functions
//

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

// Checked: 2009-09-19 (RLVa-1.0.3d)
inline std::string RlvCommand::asString() const
{
	// NOTE: @clear=<param> should be represented as clear:<param>
	return (m_eParamType != RLV_TYPE_CLEAR)
		? (!m_strOption.empty()) ? (std::string(m_strBehaviour)).append(":").append(m_strOption) : (std::string(m_strBehaviour))
	    : (!m_strParam.empty())  ? (std::string(m_strBehaviour)).append(":").append(m_strParam)  : (std::string(m_strBehaviour));
}

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 bool RlvCommand::hasStrictVariant(ERlvBehaviour eBhvr)
{
	switch (eBhvr)
	{
		case RLV_BHVR_RECVCHAT:
		case RLV_BHVR_RECVEMOTE:
		case RLV_BHVR_RECVIM:
		case RLV_BHVR_SENDIM:
		case RLV_BHVR_TPLURE:
		case RLV_BHVR_SENDCHANNEL:
			return true;
		default:
			return false;
	}
}

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

// Checked: 2009-12-18 (RLVa-1.1.0k) | Added: RLVa-1.1.0i
inline bool RlvForceWear::isWearableItem(const LLInventoryItem* pItem)
{
	return (LLAssetType::AT_OBJECT == pItem->getType()) || (LLAssetType::AT_GESTURE == pItem->getType()) ||
		(LLAssetType::AT_BODYPART == pItem->getType()) || (LLAssetType::AT_CLOTHING == pItem->getType());
}

// Checked: 2009-12-18 (RLVa-1.1.0k) | Modified: RLVa-1.1.0i
inline bool RlvForceWear::isWearingItem(const LLInventoryItem* pItem)
{
	return
		((LLAssetType::AT_OBJECT == pItem->getType()) && (gAgent.getAvatarObject()->isWearingAttachment(pItem->getUUID()))) ||
		((LLAssetType::AT_GESTURE == pItem->getType()) && (gGestureManager.isGestureActive(pItem->getUUID()))) ||
		(gAgent.isWearingItem(pItem->getUUID()));
}

// ============================================================================
// 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-09-05 (RLVa-1.0.2a) | Added: RLVa-1.0.2a
inline bool rlvIsValidReplyChannel(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