/** 
 * @file permissionstracker.cpp
 * @brief Permissions Tracker implementation - Initially it's only tracking
 * camera control permissions, to warn users about attachments or seats that
 * took camera control permissions and might interfere with resetting the
 * camera view.
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * Copyright (C) 2022, Zi Ree @ Second Life
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "llagentdata.h"			// for gAgentID anf gAgentSessionID
#include "llnotificationsutil.h"
#include "llslurl.h"
#include "lltrans.h"
#include "llviewermenu.h"			// for handle_object_edit()
#include "llviewerregion.h"
#include "llviewerobject.h"
#include "llviewerobjectlist.h"		// for gObjectList
#include "llvoavatarself.h"			// for gAgentAvatarp
#include "llworld.h"

#include "permissionstracker.h"

#define PERMISSION_ENTRY_EXPIRY_TIME	3600.0

PermissionsTracker::PermissionsTracker()
:	LLSingleton<PermissionsTracker>()
{
}

PermissionsTracker::~PermissionsTracker()
{
}

void PermissionsTracker::addPermissionsEntry(const LLUUID& source_id, PermissionsTracker::PERM_TYPE permission_type)
{
	// find out if this is a new entry in the list
	if (mPermissionsList.find(source_id) == mPermissionsList.end())
	{
		LL_DEBUGS("PermissionsTracker") << "Creating list entry for source " << source_id << LL_ENDL;
		mPermissionsList[source_id].objectName = LLTrans::getString("LoadingData");

		// find out if the object is still in reach
		LLViewerObject* vo = gObjectList.findObject(source_id);
		if (!vo)
		{
			mPermissionsList[source_id].objectName = LLTrans::getString("ObjectOutOfRange");
		}
		else
		{
			mPermissionsList[source_id].attachmentID = vo->getAttachmentItemID();
			LL_DEBUGS("PermissionsTracker") << "Requesting ObjectProperties for source " << source_id << LL_ENDL;

			// remember which object names we already requested
			mRequestedIDs.push_back(source_id);

			// send a request out to get this object's details
			LLMessageSystem* msg = gMessageSystem;

			msg->newMessageFast(_PREHASH_ObjectSelect);
			msg->nextBlockFast(_PREHASH_AgentData);
			msg->addUUIDFast(_PREHASH_AgentID, gAgentID);
			msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID);
			msg->nextBlockFast(_PREHASH_ObjectData);
			msg->addU32Fast(_PREHASH_ObjectLocalID, vo->getLocalID());
			msg->sendReliable(gAgentAvatarp->getRegion()->getHost());

			msg->newMessageFast(_PREHASH_ObjectDeselect);
			msg->nextBlockFast(_PREHASH_AgentData);
			msg->addUUIDFast(_PREHASH_AgentID, gAgentID);
			msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID);
			msg->nextBlockFast(_PREHASH_ObjectData);
			msg->addU32Fast(_PREHASH_ObjectLocalID, vo->getLocalID());
			msg->sendReliable(gAgentAvatarp->getRegion()->getHost());
		}
	}

	LL_DEBUGS("PermissionsTracker") << "Adding permission type " << permission_type << " to source " << source_id << LL_ENDL;
	mPermissionsList[source_id].type |= permission_type;
	mPermissionsList[source_id].time = LLDate(LLTimer::getTotalSeconds());

	purgePermissionsEntries();
}

void PermissionsTracker::removePermissionsEntry(const LLUUID& source_id, PermissionsTracker::PERM_TYPE permission_type)
{
	if (mPermissionsList.find(source_id) == mPermissionsList.end())
	{
		LL_DEBUGS("PermissionsTracker") << "Could not find list entry for source " << source_id << LL_ENDL;
		return;
	}

	LL_DEBUGS("PermissionsTracker") << "Removing permissions type " << permission_type << " from source " << source_id << LL_ENDL;
	mPermissionsList[source_id].type &= ~permission_type;
	mPermissionsList[source_id].time = LLDate(LLTimer::getTotalSeconds());

	purgePermissionsEntries();
}

void PermissionsTracker::purgePermissionsEntries()
{
	F64 expiry_time = LLDate(LLTimer::getTotalSeconds()).secondsSinceEpoch() - PERMISSION_ENTRY_EXPIRY_TIME;

	auto it = mPermissionsList.begin();
	while (it != mPermissionsList.end())
	{
		if (it->second.type == PERM_TYPE::PERM_NONE && it->second.time.secondsSinceEpoch() < expiry_time)
		{
			LL_DEBUGS("PermissionsTracker") << "Erasing list entry for source " << it->first << LL_ENDL;
			it = mPermissionsList.erase(it);
		}
		else
		{
			++it;
		}
	}
}

void PermissionsTracker::warnFollowcam()
{
	std::string followcamList;
	for (auto entry : mPermissionsList)
	{
		if (entry.second.type & PermissionsTracker::PERM_FOLLOWCAM)
		{
			if (entry.second.attachmentID.notNull())
			{
				std::string attachment_point = "???";
				gAgentAvatarp->getAttachedPointName(entry.second.attachmentID, attachment_point);

				LLSD args;
				args["ATTACHMENT_POINT"] = attachment_point;

				std::string verb = "select?name=" + LLURI::escape(entry.second.objectName);
				followcamList += LLSLURL("inventory", entry.second.attachmentID, verb.c_str()).getSLURLString() + " " +
					LLTrans::getString("WornOnAttachmentPoint", args) + "\n";
			}
			else
			{
				followcamList += LLSLURL("objectim", entry.first, "").getSLURLString() +
					"?name=" + LLURI::escape(entry.second.objectName) +
					"&owner=" + entry.second.ownerID.asString();

				LLSD args;
				std::string slurl = args["slurl"].asString();
				if (slurl.empty())
				{
					LLViewerRegion* region = LLWorld::instance().getRegionFromPosAgent(gAgentAvatarp->getPositionAgent());
					if(region)
					{
						LLSLURL region_slurl(region->getName(), gAgentAvatarp->getPositionAgent());
						slurl = region_slurl.getLocationString();
					}
				}

				followcamList += "&slurl=" + LLURI::escape(slurl) + "\n";
			}
		}
	}

	if (followcamList.empty())
	{
		return;
	}

	LLSD args;
	args["SOURCES"] = followcamList;
	LLNotificationsUtil::add("WarnScriptedCamera", args);
}

void PermissionsTracker::objectPropertiesCallback(LLMessageSystem* msg)
{
	LL_DEBUGS("PermissionsTracker") << "Received ObjectProperties message." << LL_ENDL;

	// if we weren't looking for any IDs, ignore this callback
	if (mRequestedIDs.empty())
	{
		LL_DEBUGS("PermissionsTracker") << "No objects in request list." << LL_ENDL;
		return;
	}

	// we might have received more than one answer in one block
	S32 num = msg->getNumberOfBlocksFast(_PREHASH_ObjectData);
	for (S32 index = 0; index < num; ++index)
	{
		LLUUID source_id;
		msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, source_id, index);

		auto iter = std::find(mRequestedIDs.begin(), mRequestedIDs.end(), source_id);
		// if this is one of the objects we were looking for, process the data
		if (iter != mRequestedIDs.end())
		{
			// get the name of the object
			std::string object_name;
			msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Name, object_name, index);

			LLUUID object_owner;
			msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_OwnerID, object_owner, index);

			LL_DEBUGS("PermissionsTracker") << "Received object name " << object_name
				<< " and owner " << object_owner.asString() << " for source " << source_id << LL_ENDL;

			// remove the object from the lookup list and add it to the known names list
			mRequestedIDs.erase(iter);

			mPermissionsList[source_id].objectName = object_name;
			mPermissionsList[source_id].ownerID = object_owner;

			LLAvatarName avatar_name;
			if (LLAvatarNameCache::get(object_owner, &avatar_name))
			{
				LL_DEBUGS("PermissionsTracker") << "Found cached entry for owner " << object_owner.asString()
					<< ": " << avatar_name.getCompleteName() << LL_ENDL;
				mPermissionsList[source_id].ownerName = avatar_name.getCompleteName();
			}
			else
			{
				if (mAvatarNameCacheConnections.find(object_owner) != mAvatarNameCacheConnections.end())
				{
					boost::signals2::connection cb_connection = LLAvatarNameCache::get(object_owner, boost::bind(&PermissionsTracker::avatarNameCallback, this, _1, _2));
					mAvatarNameCacheConnections.insert(std::make_pair(object_owner, cb_connection));

					LL_DEBUGS("PermissionsTracker") << "Requesting avatar name for owner " << object_owner.asString() << LL_ENDL;
				}
			}
		}
	}
}

void PermissionsTracker::avatarNameCallback(const LLUUID& avatar_id, const LLAvatarName& avatar_name)
{
	LL_DEBUGS("PermissionsTracker") << "Received avatar name " << avatar_name.getCompleteName() << LL_ENDL;

	auto iter = mAvatarNameCacheConnections.find(avatar_id);
	if (iter != mAvatarNameCacheConnections.end())
	{
		if (iter->second.connected())
		{
			iter->second.disconnect();
		}
		mAvatarNameCacheConnections.erase(iter);
	}

	for (auto entry : mPermissionsList)
	{
		if (entry.second.ownerID == avatar_id)
		{
			LL_DEBUGS("PermissionsTracker") << "Saved avatar name " << avatar_name.getCompleteName()<< " for source " << entry.first.asString() << LL_ENDL;
			entry.second.ownerName = avatar_name.getCompleteName();
		}
	}
}
