/**
 *
 * Copyright (c) 2009-2020, Kitty Barnett
 *
 * The source code in this file is provided to you under the terms of the
 * GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt
 * in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
 *
 * By copying, modifying or distributing this software, you acknowledge that
 * you have read and understood your obligations described above, and agree to
 * abide by those obligations.
 *
 */

#include "llviewerprecompiledheaders.h"
#include "llagent.h"
#include "llagentui.h"
#include "llavatarnamecache.h"
#include "llcallingcard.h"
#include "llimview.h"
#include "llinstantmessage.h"
#include "llnotificationsutil.h"
#include "llregex.h"
#include "llregionhandle.h"
#include "llsdserialize.h"
#include "lltrans.h"
#include "llversioninfo.h"
#include "llviewerparcelmgr.h"
#include "llviewermenu.h"
#include "llviewermessage.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
#include "llworld.h"

#include "rlvactions.h"
#include "rlvcommon.h"
#include "rlvhelper.h"
#include "rlvhandler.h"
#include "rlvlocks.h"

#include "llscriptruntimeperms.h"
#include <boost/algorithm/string/predicate.hpp> // icontains
#include <boost/algorithm/string/regex.hpp> // regex_replace_all
#include <boost/algorithm/string.hpp>


// ============================================================================
// Forward declarations
//

// llviewermenu.cpp
LLVOAvatar* find_avatar_from_object(LLViewerObject* object);

// ============================================================================
// RlvNotifications
//

// Checked: 2009-11-13 (RLVa-1.1.0b) | Modified: RLVa-1.1.0b
/*
void RlvNotifications::warnGiveToRLV()
{
	if ( (gSavedSettings.getWarning(RLV_SETTING_FIRSTUSE_GIVETORLV)) && (RlvSettings::getForbidGiveToRLV()) )
		LLNotifications::instance().add(RLV_SETTING_FIRSTUSE_GIVETORLV, LLSD(), LLSD(), &RlvNotifications::onGiveToRLVConfirmation);
}
*/

// Checked: 2009-11-13 (RLVa-1.1.0b) | Modified: RLVa-1.1.0b
/*
void RlvNotifications::onGiveToRLVConfirmation(const LLSD& notification, const LLSD& response)
{
	gSavedSettings.setWarning(RLV_SETTING_FIRSTUSE_GIVETORLV, FALSE);

	S32 idxOption = LLNotification::getSelectedOption(notification, response);
	if ( (0 == idxOption) || (1 == idxOption) )
		gSavedSettings.setBOOL(RLV_SETTING_FORBIDGIVETORLV, (idxOption == 1));
}
*/

// =========================================================================
// RlvSettings
//

#ifdef RLV_EXPERIMENTAL_COMPOSITEFOLDERS
bool RlvSettings::s_fCompositeFolders = false;
#endif // RLV_EXPERIMENTAL_COMPOSITEFOLDERS
bool RlvSettings::s_fCanOOC = true;
U8 RlvSettings::s_nExperienceMinMaturity = 0;
bool RlvSettings::s_fLegacyNaming = true;
bool RlvSettings::s_fNoSetEnv = false;
bool RlvSettings::s_fTempAttach = true;
std::list<std::string> RlvSettings::s_BlockedExperiences;
std::list<LLUUID> RlvSettings::s_CompatItemCreators;
std::list<std::string> RlvSettings::s_CompatItemNames;

// Checked: 2010-02-27 (RLVa-1.2.0a) | Modified: RLVa-1.1.0i
void RlvSettings::initClass()
{
	static bool fInitialized = false;
	if (!fInitialized)
	{
		initCompatibilityMode(LLStringUtil::null);

		s_fTempAttach = rlvGetSetting<bool>(RlvSettingNames::EnableTempAttach, true);
		if (gSavedSettings.controlExists(RlvSettingNames::EnableTempAttach))
			gSavedSettings.getControl(RlvSettingNames::EnableTempAttach)->getSignal()->connect(boost::bind(&onChangedSettingBOOL, _2, &s_fTempAttach));

		#ifdef RLV_EXPERIMENTAL_COMPOSITEFOLDERS
		s_fCompositeFolders = rlvGetSetting<bool>(RLV_SETTING_ENABLECOMPOSITES, false);
		if (gSavedSettings.controlExists(RLV_SETTING_ENABLECOMPOSITES))
			gSavedSettings.getControl(RLV_SETTING_ENABLECOMPOSITES)->getSignal()->connect(boost::bind(&onChangedSettingBOOL, _2, &s_fCompositeFolders));
		#endif // RLV_EXPERIMENTAL_COMPOSITEFOLDERS

		s_fLegacyNaming = rlvGetSetting<bool>(RlvSettingNames::EnableLegacyNaming, true);
		if (gSavedSettings.controlExists(RlvSettingNames::EnableLegacyNaming))
			gSavedSettings.getControl(RlvSettingNames::EnableLegacyNaming)->getSignal()->connect(boost::bind(&onChangedSettingBOOL, _2, &s_fLegacyNaming));

		s_fCanOOC = rlvGetSetting<bool>(RlvSettingNames::CanOoc, true);
		s_fNoSetEnv = rlvGetSetting<bool>(RlvSettingNames::NoSetEnv, false);

		// Don't allow toggling RLVaLoginLastLocation from the debug settings floater
		if (gSavedPerAccountSettings.controlExists(RlvSettingNames::LoginLastLocation))
			gSavedPerAccountSettings.getControl(RlvSettingNames::LoginLastLocation)->setHiddenFromSettingsEditor(true);

		if (gSavedSettings.controlExists(RlvSettingNames::TopLevelMenu))
			gSavedSettings.getControl(RlvSettingNames::TopLevelMenu)->getSignal()->connect(boost::bind(&onChangedMenuLevel));

		int nMinMaturity = gSavedSettings.getS32("RLVaExperienceMaturityThreshold");
		s_nExperienceMinMaturity = (nMinMaturity == 0) ? 0 : ((nMinMaturity == 1) ? SIM_ACCESS_PG : ((nMinMaturity == 2) ? SIM_ACCESS_MATURE : SIM_ACCESS_ADULT));
		const std::string& strBlockedExperiences = gSavedSettings.getString("RLVaBlockedExperiences");
		boost::split(s_BlockedExperiences, strBlockedExperiences, boost::is_any_of(";"));

		fInitialized = true;
	}
}

// Checked: 2010-04-01 (RLVa-1.2.0c) | Modified: RLVa-0.2.1d
void RlvSettings::updateLoginLastLocation()
{
	if ( (!LLApp::isExiting()) && (gSavedPerAccountSettings.controlExists(RlvSettingNames::LoginLastLocation)) )
	{
		bool fValue = (gRlvHandler.hasBehaviour(RLV_BHVR_TPLOC)) || (!RlvActions::canStand());
		if (gSavedPerAccountSettings.get<bool>(RlvSettingNames::LoginLastLocation) != fValue)
		{
			gSavedPerAccountSettings.set<bool>(RlvSettingNames::LoginLastLocation, fValue);
			gSavedPerAccountSettings.saveToFile(gSavedSettings.getString("PerAccountSettingsFile"), TRUE);
		}
	}
}

// Checked: 2011-08-16 (RLVa-1.4.0b) | Added: RLVa-1.4.0b
bool RlvSettings::onChangedMenuLevel()
{
	rlvMenuToggleVisible();
	return true;
}

// Checked: 2010-02-27 (RLVa-1.2.0a) | Added: RLVa-1.1.0i
bool RlvSettings::onChangedSettingBOOL(const LLSD& sdValue, bool* pfSetting)
{
	if (pfSetting)
		*pfSetting = sdValue.asBoolean();
	return true;
}

void RlvSettings::onChangedSettingMain(const LLSD& sdValue)
{
	LLStringUtil::format_map_t args;
	args["[STATE]"] = LLTrans::getString( (sdValue.asBoolean()) ? "RLVaToggleEnabled" : "RLVaToggleDisabled");

	// As long as RLVa hasn't been enabled but >can< be enabled all toggles are instant (everything else will require a restart)
	bool fQuickToggle = (!RlvHandler::isEnabled()) && (RlvHandler::canEnable());
	LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", LLTrans::getString((fQuickToggle) ? "RLVaToggleMessageLogin" : "RLVaToggleMessageRestart", args)));
}

void RlvSettings::initCompatibilityMode(std::string strCompatList)
{
	// NOTE: this function can be called more than once
	s_CompatItemCreators.clear();
	s_CompatItemNames.clear();

	strCompatList.append(";").append(rlvGetSetting<std::string>("RLVaCompatibilityModeList", ""));

	boost_tokenizer tokCompatList(strCompatList, boost::char_separator<char>(";", "", boost::drop_empty_tokens));
	for (const std::string& strCompatEntry : tokCompatList)
	{
		if (boost::starts_with(strCompatEntry, "creator:"))
		{
			LLUUID idCreator;
			if ( (44 == strCompatEntry.size()) && (LLUUID::parseUUID(strCompatEntry.substr(8), &idCreator)) &&
			     (s_CompatItemCreators.end() == std::find(s_CompatItemCreators.begin(), s_CompatItemCreators.end(), idCreator)) )
			{
				s_CompatItemCreators.push_back(idCreator);
			}
		}
		else if (boost::starts_with(strCompatEntry, "name:"))
		{
			if (strCompatEntry.size() > 5)
				s_CompatItemNames.push_back(strCompatEntry.substr(5));
		}
	}
}

bool RlvSettings::isCompatibilityModeObject(const LLUUID& idRlvObject)
{
	bool fCompatMode = false;
	if (idRlvObject.notNull())
	{
		const LLViewerObject* pObj = gObjectList.findObject(idRlvObject);
		if ( (pObj) && (pObj->isAttachment()) )
		{
			const LLViewerInventoryItem* pItem = gInventory.getItem(pObj->getAttachmentItemID());
			if (pItem)
			{
				fCompatMode = s_CompatItemCreators.end() != std::find(s_CompatItemCreators.begin(), s_CompatItemCreators.end(), pItem->getCreatorUUID());
				if (!fCompatMode)
				{
					const std::string& strAttachName = pItem->getName();
					for (const std::string& strCompatName : s_CompatItemNames)
					{
					    boost::regex regexp(strCompatName, boost::regex::perl | boost::regex::icase);
						if (ll_regex_match(strAttachName, regexp))
						{
							fCompatMode = true;
							break;
						}
					}
				}
			}
		}
	}
	return fCompatMode;
}

bool RlvSettings::isAllowedExperience(const LLUUID& idExperience, U8 nMaturity)
{
	// An experience is allowed to interact with RLVa if:
	//   - temporary attachments can interact with RLVa
	//   - the user set a minimum maturity and the specified maturity is equal or higher
	//   - the experience isn't explicitly blocked (NOTE: case-sensitive string comparison)
	return
		(getEnableTemporaryAttachments()) &&
		(s_nExperienceMinMaturity) && (s_nExperienceMinMaturity <= nMaturity) &&
		(s_BlockedExperiences.end() == std::find(s_BlockedExperiences.begin(), s_BlockedExperiences.end(), idExperience.asString()));
}

// ============================================================================
// RlvStrings
//

std::vector<std::string> RlvStrings::m_Anonyms;
RlvStrings::string_map_t RlvStrings::m_StringMap;
std::string RlvStrings::m_StringMapPath;

// Checked: 2011-11-08 (RLVa-1.5.0)
void RlvStrings::initClass()
{
	static bool fInitialized = false;
	if (!fInitialized)
	{
		// Load the default string values
		std::vector<std::string> files = gDirUtilp->findSkinnedFilenames(LLDir::XUI, RLV_STRINGS_FILE, LLDir::ALL_SKINS);
		m_StringMapPath = (!files.empty()) ? files.front() : LLStringUtil::null;
		for (std::vector<std::string>::const_iterator itFile = files.begin(); itFile != files.end(); ++itFile)
		{
			loadFromFile(*itFile, false);
		}

		// Load the custom string overrides
		loadFromFile(gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, RLV_STRINGS_FILE), true);

		// Sanity check
		if ( (m_StringMap.empty()) || (m_Anonyms.empty()) )
		{
			RLV_ERRS << "Problem parsing RLVa string XML file" << RLV_ENDL;
			return;
		}

		fInitialized = true;
	}
}

// Checked: 2011-11-08 (RLVa-1.5.0)
void RlvStrings::loadFromFile(const std::string& strFilePath, bool fUserOverride)
{
	llifstream fileStream(strFilePath.c_str(), std::ios::binary); LLSD sdFileData;
	if ( (!fileStream.is_open()) || (!LLSDSerialize::fromXMLDocument(sdFileData, fileStream)) )
		return;
	fileStream.close();

	if (sdFileData.has("strings"))
	{
		const LLSD& sdStrings = sdFileData["strings"];
		for (LLSD::map_const_iterator itString = sdStrings.beginMap(); itString != sdStrings.endMap(); ++itString)
		{
			if ( (!itString->second.has("value")) || ((fUserOverride) && (!hasString(itString->first))) )
				continue;

			std::list<std::string>& listValues = m_StringMap[itString->first];
			if (!fUserOverride)
			{
				if (listValues.size() > 0)
					listValues.pop_front();
				listValues.push_front(itString->second["value"].asString());
			}
			else
			{
				while (listValues.size() > 1)
					listValues.pop_back();
				listValues.push_back(itString->second["value"].asString());
			}
		}
	}
	if (sdFileData.has("anonyms"))
	{
		const LLSD& sdAnonyms = sdFileData["anonyms"];
		for (LLSD::array_const_iterator itAnonym = sdAnonyms.beginArray(); itAnonym != sdAnonyms.endArray(); ++itAnonym)
		{
			m_Anonyms.push_back((*itAnonym).asString());
		}
	}
}

// Checked: 2011-11-08 (RLVa-1.5.0)
void RlvStrings::saveToFile(const std::string& strFilePath)
{
	LLSD sdFileData;

	LLSD& sdStrings = sdFileData["strings"];
	for (string_map_t::const_iterator itString = m_StringMap.begin(); itString != m_StringMap.end(); ++itString)
	{
		const std::list<std::string>& listValues = itString->second;
		if (listValues.size() > 1)
			sdStrings[itString->first]["value"] = listValues.back();
	}

	llofstream fileStream(strFilePath.c_str());
	if (!fileStream.good())
		return;

	LLSDSerialize::toPrettyXML(sdFileData, fileStream);
	fileStream.close();
}

// Checked: 2009-11-11 (RLVa-1.1.0a) | Modified: RLVa-1.1.0a
std::string RlvStrings::getAnonym(const std::string& strName)
{
	static const std::string strUnknown = LLTrans::getString("Unknown");
	if ( (!RlvActions::isRlvEnabled()) || (m_Anonyms.empty()) )
	{
		return strUnknown;
	}

	const char* pszName = strName.c_str(); U32 nHash = 0;
	
	// Test with 11,264 SL names showed a 3.33% - 3.82% occurance for each so we *should* get a very even spread
	for (int idx = 0, cnt = strName.length(); idx < cnt; idx++)
		nHash += pszName[idx];

	return m_Anonyms[nHash % m_Anonyms.size()];
}

// Checked: 2011-11-08 (RLVa-1.5.0)
#ifdef CATZNIP_STRINGVIEW
const std::string& RlvStrings::getString(const boost::string_view& strStringName)
#else
const std::string& RlvStrings::getString(const std::string& strStringName)
#endif // CATZNIP_STRINGVIEW
{
	static const std::string strMissing = "(Missing RLVa string)";
	string_map_t::const_iterator itString = m_StringMap.find(strStringName);
	return (itString != m_StringMap.end()) ? itString->second.back() : strMissing;
}

// Checked: 2009-11-25 (RLVa-1.1.0f) | Added: RLVa-1.1.0f
const char* RlvStrings::getStringFromReturnCode(ERlvCmdRet eRet)
{
	// TODO-RLVa: [2009-11-25] clean this up along with the calling code in process_chat_from_simulator() once we're happy with the output
	switch (eRet)
	{
		case RLV_RET_SUCCESS_UNSET:
			return "unset";
		case RLV_RET_SUCCESS_DUPLICATE:
			return "duplicate";
		case RLV_RET_SUCCESS_DELAYED:
			return "delayed";
		case RLV_RET_SUCCESS_DEPRECATED:
			return "deprecated";
		case RLV_RET_FAILED_SYNTAX:
			return "thingy error";
		case RLV_RET_FAILED_OPTION:
			return "invalid option";
		case RLV_RET_FAILED_PARAM:
			return "invalid param";
		case RLV_RET_FAILED_LOCK:
			return "locked command";
		case RLV_RET_FAILED_DISABLED:
			return "disabled command";
		case RLV_RET_FAILED_UNKNOWN:
			return "unknown command";
		case RLV_RET_FAILED_NOSHAREDROOT:
			return "missing #RLV";
		case RLV_RET_FAILED_DEPRECATED:
			return "deprecated and disabled";
		case RLV_RET_FAILED_NOBEHAVIOUR:
			return "no active behaviours";
		case RLV_RET_FAILED_UNHELDBEHAVIOUR:
			return "base behaviour not held";
		case RLV_RET_FAILED_BLOCKED:
			return "blocked object";
		case RLV_RET_FAILED_THROTTLED:
			return "throttled";
		// The following are identified by the chat verb
		case RLV_RET_RETAINED:
		case RLV_RET_SUCCESS:
		case RLV_RET_FAILED:
			break;
		// The following shouldn't occur
		case RLV_RET_UNKNOWN:
		default:
			RLV_ASSERT(false);
			break;
	};
	return NULL;
}

std::string RlvStrings::getVersion(const LLUUID& idRlvObject, bool fLegacy)
{
	bool fCompatMode = RlvSettings::isCompatibilityModeObject(idRlvObject);
	return llformat("%s viewer v%d.%d.%d (RLVa %d.%d.%d)",
		( (!fLegacy) ? "RestrainedLove" : "RestrainedLife" ),
		(!fCompatMode) ? RLV_VERSION_MAJOR : RLV_VERSION_MAJOR_COMPAT, (!fCompatMode) ? RLV_VERSION_MINOR : RLV_VERSION_MINOR_COMPAT, (!fCompatMode) ? RLV_VERSION_PATCH : RLV_VERSION_PATCH_COMPAT,
		RLVa_VERSION_MAJOR, RLVa_VERSION_MINOR, RLVa_VERSION_PATCH);
}

std::string RlvStrings::getVersionAbout()
{
	return llformat("RLV v%d.%d.%d / RLVa v%d.%d.%d.%d",
	                RLV_VERSION_MAJOR, RLV_VERSION_MINOR, RLV_VERSION_PATCH, RLVa_VERSION_MAJOR, RLVa_VERSION_MINOR, RLVa_VERSION_PATCH, LLVersionInfo::instance().getBuild());
}

std::string RlvStrings::getVersionNum(const LLUUID& idRlvObject)
{
	bool fCompatMode = RlvSettings::isCompatibilityModeObject(idRlvObject);
	return llformat("%d%02d%02d%02d",
		(!fCompatMode) ? RLV_VERSION_MAJOR : RLV_VERSION_MAJOR_COMPAT, (!fCompatMode) ? RLV_VERSION_MINOR : RLV_VERSION_MINOR_COMPAT,
		(!fCompatMode) ? RLV_VERSION_PATCH : RLV_VERSION_PATCH_COMPAT, (!fCompatMode) ? RLV_VERSION_BUILD : RLV_VERSION_BUILD_COMPAT);
}

std::string RlvStrings::getVersionImplNum()
{
	return llformat("%d%02d%02d%02d", RLVa_VERSION_MAJOR, RLVa_VERSION_MINOR, RLVa_VERSION_PATCH, RLVa_IMPL_ID);
}

// Checked: 2011-11-08 (RLVa-1.5.0)
bool RlvStrings::hasString(const std::string& strStringName, bool fCheckCustom)
{
	string_map_t::const_iterator itString = m_StringMap.find(strStringName);
	return (itString != m_StringMap.end()) && ((!fCheckCustom) || (itString->second.size() > 0));
}

// Checked: 2011-11-08 (RLVa-1.5.0)
void RlvStrings::setCustomString(const std::string& strStringName, const std::string& strStringValue)
{
	if (!hasString(strStringName))
		return;

	std::list<std::string>& listValues = m_StringMap[strStringName];
	while (listValues.size() > 1)
		listValues.pop_back();
	if (!strStringValue.empty())
		listValues.push_back(strStringValue);
}

// ============================================================================
// RlvUtil
//

bool RlvUtil::m_fForceTp = false;

std::string escape_for_regex(const std::string& str)
{
	using namespace boost;
	return regex_replace(str, regex("[.^$|()\\[\\]{}*+?\\\\]"), "\\\\&", match_default|format_sed);
}

// Checked: 2009-07-04 (RLVa-1.0.0a) | Modified: RLVa-1.0.0a
void RlvUtil::filterLocation(std::string& strUTF8Text)
{
	// Filter any mention of the surrounding region names
	LLWorld::region_list_t regions = LLWorld::getInstance()->getRegionList();
	const std::string& strHiddenRegion = RlvStrings::getString(RlvStringKeys::Hidden::Region);
	for (LLWorld::region_list_t::const_iterator itRegion = regions.begin(); itRegion != regions.end(); ++itRegion)
		boost::replace_all_regex(strUTF8Text, boost::regex("\\b" + escape_for_regex((*itRegion)->getName()) + "\\b", boost::regex::icase), strHiddenRegion);

	// Filter any mention of the parcel name
	LLViewerParcelMgr* pParcelMgr = LLViewerParcelMgr::getInstance();
	if (pParcelMgr)
		boost::replace_all_regex(strUTF8Text, boost::regex("\\b" + escape_for_regex(pParcelMgr->getAgentParcelName()) + "\\b", boost::regex::icase), RlvStrings::getString(RlvStringKeys::Hidden::Parcel));
}

// Checked: 2010-12-08 (RLVa-1.2.2c) | Modified: RLVa-1.2.2c
void RlvUtil::filterNames(std::string& strUTF8Text, bool fFilterLegacy, bool fClearMatches)
{
	uuid_vec_t idAgents;
	LLWorld::getInstance()->getAvatars(&idAgents, NULL);
	for (int idxAgent = 0, cntAgent = idAgents.size(); idxAgent < cntAgent; idxAgent++)
	{
		LLAvatarName avName;
		// NOTE: if we're agressively culling nearby names then ignore exceptions
		if ( (LLAvatarNameCache::get(idAgents[idxAgent], &avName)) && ((fClearMatches) || (!RlvActions::canShowName(RlvActions::SNC_DEFAULT, idAgents[idxAgent]))) )
		{
			const std::string& strDisplayName = escape_for_regex(avName.getDisplayName());
			bool fFilterDisplay = (strDisplayName.length() > 2);
			const std::string& strLegacyName = avName.getLegacyName();
			fFilterLegacy &= (strLegacyName.length() > 2);
			const std::string& strAnonym = (!fClearMatches) ? RlvStrings::getAnonym(avName) : LLStringUtil::null;

			// If the display name is a subset of the legacy name we need to filter that first, otherwise it's the other way around
			if (boost::icontains(strLegacyName, strDisplayName))
			{
				if (fFilterLegacy)
					boost::replace_all_regex(strUTF8Text, boost::regex("\\b" + strLegacyName + "\\b", boost::regex::icase), strAnonym);
				if (fFilterDisplay)
					boost::replace_all_regex(strUTF8Text, boost::regex("\\b" + strDisplayName + "\\b", boost::regex::icase), strAnonym);
			}
			else
			{
				if (fFilterDisplay)
					boost::replace_all_regex(strUTF8Text, boost::regex("\\b" + strDisplayName + "\\b", boost::regex::icase), strAnonym);
				if (fFilterLegacy)
					boost::replace_all_regex(strUTF8Text, boost::regex("\\b" + strLegacyName + "\\b", boost::regex::icase), strAnonym);
			}
		}
	}
}

// Checked: 2012-08-19 (RLVa-1.4.7)
void RlvUtil::filterScriptQuestions(S32& nQuestions, LLSD& sdPayload)
{
	// Check SCRIPT_PERMISSION_ATTACH
	if ( (!gRlvAttachmentLocks.canAttach()) && (SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_ATTACH].permbit & nQuestions) )
	{
		// Notify the user that we blocked it since they're not allowed to wear any new attachments
		sdPayload["rlv_blocked"] = RlvStringKeys::Blocked::PermissionAttach;
		nQuestions &= ~SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_ATTACH].permbit;
	}

	// Check SCRIPT_PERMISSION_TELEPORT
	if ( (gRlvHandler.hasBehaviour(RLV_BHVR_TPLOC)) && (SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_TELEPORT].permbit & nQuestions) )
	{
		// Notify the user that we blocked it since they're not allowed to teleport
		sdPayload["rlv_blocked"] = RlvStringKeys::Blocked::PermissionTeleport;
		nQuestions &= ~SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_TELEPORT].permbit;
	}

	sdPayload["questions"] = nQuestions;
}

// Checked: 2010-08-29 (RLVa-1.2.1c) | Added: RLVa-1.2.1c
void RlvUtil::forceTp(const LLVector3d& posDest)
{
	m_fForceTp = true;
	gAgent.teleportViaLocationLookAt(posDest);
	m_fForceTp = false;
}

// Checked: 2010-04-22 (RLVa-1.2.0f) | Modified: RLVa-1.2.0f
bool RlvUtil::isNearbyAgent(const LLUUID& idAgent)
{
	// Sanity check since we call this with notification payloads as well and those strings tend to change from one release to another
	RLV_ASSERT(idAgent.notNull());
	if ( (idAgent.notNull()) && (gAgent.getID() != idAgent) )
	{
		std::vector<LLUUID> idAgents;
		LLWorld::getInstance()->getAvatars(&idAgents, NULL);

		for (int idxAgent = 0, cntAgent = idAgents.size(); idxAgent < cntAgent; idxAgent++)
			if (idAgents[idxAgent] == idAgent)
				return true;
	}
	return false;
}

// Checked: 2010-04-05 (RLVa-1.2.0d) | Modified: RLVa-1.2.0d
bool RlvUtil::isNearbyRegion(const std::string& strRegion)
{
	LLWorld::region_list_t regions = LLWorld::getInstance()->getRegionList();
	for (LLWorld::region_list_t::const_iterator itRegion = regions.begin(); itRegion != regions.end(); ++itRegion)
		if ((*itRegion)->getName() == strRegion)
			return true;
	return false;
}

// Checked: 2011-04-11 (RLVa-1.3.0h) | Modified: RLVa-1.3.0h
#ifdef CATZNIP_STRINGVIEW
void RlvUtil::notifyBlocked(const boost::string_view& strNotifcation, const LLSD& sdArgs, bool fLogToChat)
#else
void RlvUtil::notifyBlocked(const std::string& strNotifcation, const LLSD& sdArgs, bool fLogToChat)
#endif // CATZNIP_STRINGVIEW
{
	std::string strMsg = RlvStrings::getString(strNotifcation);
	LLStringUtil::format(strMsg, sdArgs);

	LLSD sdNotify;
	sdNotify["MESSAGE"] = strMsg;
	if (!fLogToChat)
		LLNotificationsUtil::add("SystemMessageTip", sdNotify);
	else
		LLNotificationsUtil::add("ChatSystemMessageTip", sdNotify);
}

// Checked: 2010-11-11 (RLVa-1.2.1g) | Added: RLVa-1.2.1g
void RlvUtil::notifyFailedAssertion(const std::string& strAssert, const std::string& strFile, int nLine)
{
	// Don't show the same assertion over and over, or if the user opted out
	static std::string strAssertPrev, strFilePrev; static int nLinePrev;
	if ( ((strAssertPrev == strAssert) && (strFile == strFilePrev) && (nLine == nLinePrev)) ||
		 (!rlvGetSetting<bool>(RlvSettingNames::ShowAssertionFail, true)) )
	{
		return;
	}

	strAssertPrev = strAssert;
	strFilePrev = strFile;
	nLinePrev = nLine;

	LLSD argsNotify;
	argsNotify["MESSAGE"] = llformat("RLVa assertion failure: %s (%s - %d)", strAssert.c_str(), strFile.c_str(), nLine);
	LLNotificationsUtil::add("SystemMessageTip", argsNotify);
}

// Checked: 2010-03-27 (RLVa-1.2.0b) | Modified: RLVa-1.2.0b
void RlvUtil::sendBusyMessage(const LLUUID& idTo, const std::string& strMsg, const LLUUID& idSession)
{
	// [See process_improved_im()]
	std::string strFullName;
	LLAgentUI::buildFullname(strFullName);

	pack_instant_message(gMessageSystem, gAgent.getID(), FALSE, gAgent.getSessionID(), idTo, strFullName,
		strMsg, IM_ONLINE, IM_DO_NOT_DISTURB_AUTO_RESPONSE, idSession);
	gAgent.sendReliableMessage();
}

// Checked: 2010-03-09 (RLVa-1.2.0a) | Modified: RLVa-1.0.1e
bool RlvUtil::sendChatReply(S32 nChannel, const std::string& strUTF8Text)
{
	if (!isValidReplyChannel(nChannel))
		return false;

	// Copy/paste from send_chat_from_viewer()
	gMessageSystem->newMessageFast(_PREHASH_ChatFromViewer);
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	gMessageSystem->nextBlockFast(_PREHASH_ChatData);
	gMessageSystem->addStringFast(_PREHASH_Message, utf8str_truncate(strUTF8Text, MAX_MSG_STR_LEN));
	gMessageSystem->addU8Fast(_PREHASH_Type, CHAT_TYPE_SHOUT);
	gMessageSystem->addS32("Channel", nChannel);
	gAgent.sendReliableMessage();
	add(LLStatViewer::CHAT_COUNT, 1);

	return true;
}

bool RlvUtil::sendChatReplySplit(S32 nChannel, const std::string& strMsg, char chSplit)
{
	std::list<std::string> msgList;
	utf8str_split(msgList, strMsg, MAX_MSG_STR_LEN, chSplit);
	for (const std::string& strMsg : msgList)
	{
		if (!sendChatReply(nChannel, strMsg))
		{
			return false;
		}
	}
	return true;
}

void RlvUtil::sendIMMessage(const LLUUID& idRecipient, const std::string& strMsg, char chSplit)
{
	const LLUUID idSession = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL, idRecipient);
	const LLRelationship* pBuddyInfo = LLAvatarTracker::instance().getBuddyInfo(idRecipient);
	std::string strAgentName;
	LLAgentUI::buildFullname(strAgentName);

	std::list<std::string> msgList;
	utf8str_split(msgList, strMsg, MAX_MSG_STR_LEN, chSplit);
	for (const std::string& strMsg : msgList)
	{
		pack_instant_message(
			gMessageSystem,
			gAgent.getID(),
			false,
			gAgent.getSessionID(),
			idRecipient,
			strAgentName.c_str(),
			strMsg.c_str(),
			((!pBuddyInfo) || (pBuddyInfo->isOnline())) ? IM_ONLINE : IM_OFFLINE,
			IM_NOTHING_SPECIAL,
			idSession);
		gAgent.sendReliableMessage();
	}
}

// ============================================================================
// Generic menu enablers
//

// Checked: 2015-05-25 (RLVa-1.5.0)
bool rlvMenuMainToggleVisible(LLUICtrl* pMenuCtrl)
{
	LLMenuItemCheckGL* pMenuItem = dynamic_cast<LLMenuItemCheckGL*>(pMenuCtrl);
	if (pMenuItem)
	{
		static std::string strLabel = pMenuItem->getLabel();
		if ((bool)gSavedSettings.get<bool>(RlvSettingNames::Main) == rlv_handler_t::isEnabled())
			pMenuItem->setLabel(strLabel);
		else
			pMenuItem->setLabel(strLabel + " " + LLTrans::getString("RLVaPendingRestart"));
	}
	return true;
}

// Checked: 2011-08-16 (RLVa-1.4.0b) | Added: RLVa-1.4.0b
void rlvMenuToggleVisible()
{
	bool fTopLevel = rlvGetSetting(RlvSettingNames::TopLevelMenu, true);
	bool fRlvEnabled = rlv_handler_t::isEnabled();

	LLMenuGL* pRLVaMenuMain = gMenuBarView->findChildMenuByName("RLVa Main", FALSE);
	LLMenuGL* pAdvancedMenu = gMenuBarView->findChildMenuByName("Advanced", FALSE);
	LLMenuGL* pRLVaMenuEmbed = pAdvancedMenu->findChildMenuByName("RLVa Embedded", FALSE);

	gMenuBarView->setItemVisible("RLVa Main", (fRlvEnabled) && (fTopLevel));
	pAdvancedMenu->setItemVisible("RLVa Embedded", (fRlvEnabled) && (!fTopLevel));

	if ( (rlv_handler_t::isEnabled()) && (pRLVaMenuMain) && (pRLVaMenuEmbed) && 
		 ( ((fTopLevel) && (1 == pRLVaMenuMain->getItemCount())) || ((!fTopLevel) && (1 == pRLVaMenuEmbed->getItemCount())) ) )
	{
		LLMenuGL* pMenuFrom = (fTopLevel) ? pRLVaMenuEmbed : pRLVaMenuMain;
		LLMenuGL* pMenuTo = (fTopLevel) ? pRLVaMenuMain : pRLVaMenuEmbed;
		while (LLMenuItemGL* pItem = pMenuFrom->getItem(1))
		{
			pMenuFrom->removeChild(pItem);
			pMenuTo->addChild(pItem);
			pItem->updateBranchParent(pMenuTo);
		}
	}
}

bool rlvMenuCanShowName()
{
	bool fEnable = true;
	if (rlv_handler_t::isEnabled())
	{
		const LLVOAvatar* pAvatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject());
		fEnable = (pAvatar) && (RlvActions::canShowName(RlvActions::SNC_DEFAULT, pAvatar->getID()));
	}
	return fEnable;
}

// Checked: 2010-04-23 (RLVa-1.2.0g) | Modified: RLVa-1.2.0g
bool rlvMenuEnableIfNot(const LLSD& sdParam)
{
	bool fEnable = true;
	if (rlv_handler_t::isEnabled())
	{
		ERlvBehaviour eBhvr = RlvBehaviourDictionary::instance().getBehaviourFromString(sdParam.asString(), RLV_TYPE_ADDREM);
		fEnable = (eBhvr != RLV_BHVR_UNKNOWN) ? !gRlvHandler.hasBehaviour(eBhvr) : true;
	}
	return fEnable;
}

// ============================================================================
// Selection functors
//

// Checked: 2011-05-28 (RLVa-1.4.6) | Modified: RLVa-1.4.0
bool rlvCanDeleteOrReturn(const LLViewerObject* pObj)
{
	// Block if: @rez=n restricted and owned by us or a group *or* @unsit=n restricted and being sat on by us
	return
		( (!gRlvHandler.hasBehaviour(RLV_BHVR_REZ)) || ((!pObj->permYouOwner()) && (!pObj->permGroupOwner())) ) &&
		( (!gRlvHandler.hasBehaviour(RLV_BHVR_UNSIT)) || (!isAgentAvatarValid()) || (!pObj->getRootEdit()->isChild(gAgentAvatarp)) );
}

// Checked: 2011-05-28 (RLVa-1.4.6) | Modified: RLVa-1.4.0
bool rlvCanDeleteOrReturn()
{
	if ( (gRlvHandler.hasBehaviour(RLV_BHVR_REZ)) || (gRlvHandler.hasBehaviour(RLV_BHVR_UNSIT)) )
	{
		struct RlvCanDeleteOrReturn : public LLSelectedObjectFunctor
		{
			/*virtual*/ bool apply(LLViewerObject* pObj) { return rlvCanDeleteOrReturn(pObj); }
		} f;
		LLObjectSelectionHandle hSel = LLSelectMgr::getInstance()->getSelection();
		return (hSel.notNull()) && (0 != hSel->getRootObjectCount()) && (hSel->applyToRootObjects(&f, false));
	}
	return true;
}

// Checked: 2010-04-20 (RLVa-1.2.0f) | Modified: RLVa-0.2.0f
bool RlvSelectHasLockedAttach::apply(LLSelectNode* pNode)
{
	return (pNode->getObject()) ? gRlvAttachmentLocks.isLockedAttachment(pNode->getObject()->getRootEdit()) : false;
}

// Checked: 2010-11-29 (RLVa-1.3.0c) | Added: RLVa-1.3.0c
bool RlvSelectIsEditable::apply(LLSelectNode* pNode)
{
	const LLViewerObject* pObj = pNode->getObject();
	return (pObj) && (!RlvActions::canEdit(pObj));
}

// Checked: 2011-05-28 (RLVa-1.4.0a) | Modified: RLVa-1.4.0a
bool RlvSelectIsSittingOn::apply(LLSelectNode* pNode)
{
	return (pNode->getObject()) && (pNode->getObject()->getRootEdit()->isChild(m_pAvatar));
}

// ============================================================================
// Predicates
//

// Checked: 2010-11-11 (RLVa-1.2.1g) | Modified: RLVa-1.2.1g
bool rlvPredCanWearItem(const LLViewerInventoryItem* pItem, ERlvWearMask eWearMask)
{
	if ( (pItem) && (RlvForceWear::isWearableItem(pItem)) )
	{
		if (RlvForceWear::isWearingItem(pItem))
			return true; // Special exception for currently worn items
		switch (pItem->getType())
		{
			case LLAssetType::AT_BODYPART:
				// NOTE: only one body part of each type is allowed so the only way to wear one is if we can replace the current one
				return (RLV_WEAR_LOCKED != (gRlvWearableLocks.canWear(pItem) & RLV_WEAR_REPLACE & eWearMask));
			case LLAssetType::AT_CLOTHING:
				return (RLV_WEAR_LOCKED != (gRlvWearableLocks.canWear(pItem) & eWearMask));
			case LLAssetType::AT_OBJECT:
				return (RLV_WEAR_LOCKED != (gRlvAttachmentLocks.canAttach(pItem) & eWearMask));
			case LLAssetType::AT_GESTURE:
				return true;
			default:
				RLV_ASSERT(false);
		}
	}
	return false;
}

// Checked: 2010-03-22 (RLVa-1.2.0c) | Added: RLVa-1.2.0a
bool rlvPredCanNotWearItem(const LLViewerInventoryItem* pItem, ERlvWearMask eWearMask)
{
	return !rlvPredCanWearItem(pItem, eWearMask);
}

// Checked: 2014-11-02 (RLVa-1.4.11)
bool rlvPredCanRemoveItem(const LLUUID& idItem)
{
	// Check the inventory item if it's available
	const LLViewerInventoryItem* pItem = gInventory.getItem(idItem);
	if (pItem)
	{
		return rlvPredCanRemoveItem(pItem);
	}

	// Temporary attachments don't have inventory items associated with them so check the attachment itself
	if (isAgentAvatarValid())
	{
		const LLViewerObject* pAttachObj = gAgentAvatarp->getWornAttachment(idItem);
		return (pAttachObj) && (!gRlvAttachmentLocks.isLockedAttachment(pAttachObj));
	}

	return false;
}

// Checked: 2010-03-22 (RLVa-1.2.0)
bool rlvPredCanRemoveItem(const LLViewerInventoryItem* pItem)
{
	if (pItem)
	{
		switch (pItem->getType())
		{
			case LLAssetType::AT_BODYPART:
			case LLAssetType::AT_CLOTHING:
				return gRlvWearableLocks.canRemove(pItem);
			case LLAssetType::AT_OBJECT:
				return gRlvAttachmentLocks.canDetach(pItem);
			case LLAssetType::AT_GESTURE:
				return true;
			case LLAssetType::AT_LINK:
			case LLAssetType::AT_LINK_FOLDER:
				// Broken links can always be removed since they don't represent a worn item
				return true;
			default:
				RLV_ASSERT(!RlvForceWear::isWearableItem(pItem));
		}
	}
	return false;
}

// Checked: 2010-03-22 (RLVa-1.2.0c) | Added: RLVa-1.2.0a
bool rlvPredCanNotRemoveItem(const LLViewerInventoryItem* pItem)
{
	return !rlvPredCanRemoveItem(pItem);
}

// Checked: 2010-04-24 (RLVa-1.2.0f) | Added: RLVa-1.2.0f
RlvPredIsEqualOrLinkedItem::RlvPredIsEqualOrLinkedItem(const LLUUID& idItem)
{
	m_pItem = gInventory.getItem(idItem);
}

// Checked: 2010-04-24 (RLVa-1.2.0f) | Added: RLVa-1.2.0f
bool RlvPredIsEqualOrLinkedItem::operator()(const LLViewerInventoryItem* pItem) const
{
	return (m_pItem) && (pItem) && (m_pItem->getLinkedUUID() == pItem->getLinkedUUID());
}

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

// Checked: 2009-11-15 (RLVa-1.1.0c) | Added: RLVa-1.1.0c
/*
BOOL rlvEnableSharedWearEnabler(void* pParam)
{
	return false;
	// Visually disable the "Enable Shared Wear" option when at least one attachment is non-detachable
	return (!gRlvHandler.hasLockedAttachment(RLV_LOCK_REMOVE));
}
*/

// Checked: 2010-11-01 (RLVa-1.2.2a) | Added: RLVa-1.2.2a
std::string rlvGetAnonym(const LLAvatarName& avName)
{
	return RlvStrings::getAnonym(avName);
}

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