/** 
 * @file llcachename.cpp
 * @brief A hierarchical cache of first and last names queried based on UUID.
 *
 * $LicenseInfo:firstyear=2002&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * 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 "linden_common.h"

#include "llcachename.h"

// linden library includes
#include "lldbstrings.h"
#include "llframetimer.h"
#include "llhost.h"
#include "llrand.h"
#include "llsdserialize.h"
#include "lluuid.h"
#include "message.h"

#include <boost/regex.hpp>

#include "llavatarname.h"	// <FS:CR> FIRE-6659

// llsd serialization constants
static const std::string AGENTS("agents");
static const std::string GROUPS("groups");
static const std::string CTIME("ctime");
static const std::string FIRST("first");
static const std::string LAST("last");
static const std::string NAME("name");

// We track name requests in flight for up to this long.
// We won't re-request a name during this time
const U32 PENDING_TIMEOUT_SECS = 5 * 60;

// Globals
LLCacheName* gCacheName = NULL;
std::map<std::string, std::string> LLCacheName::sCacheName;

/// ---------------------------------------------------------------------------
/// class LLCacheNameEntry
/// ---------------------------------------------------------------------------

class LLCacheNameEntry
{
public:
	LLCacheNameEntry();

public:
	bool mIsGroup;
	U32 mCreateTime;	// unix time_t
	// IDEVO TODO collapse names to one field, which will eliminate
	// many string compares on "Resident"
	std::string mFirstName;
	std::string mLastName;
	std::string mGroupName;
};

LLCacheNameEntry::LLCacheNameEntry()
	: mIsGroup(false),
	  mCreateTime(0)
{
}


class PendingReply
{
public:
	LLUUID				mID;
	LLCacheNameSignal	mSignal;
	LLHost				mHost;
	// <FS:Ansariel> Fix stale legacy requests
	F64SecondsImplicit	mRequestStart;
	bool				mIsGroup;
	// </FS:Ansariel>
	
	PendingReply(const LLUUID& id, const LLHost& host)
		: mID(id), mHost(host)
	{
	}
	
	boost::signals2::connection setCallback(const LLCacheNameCallback& cb)
	{
		return mSignal.connect(cb);
	}
	
	void done()			{ mID.setNull(); }
	bool isDone() const	{ return mID.isNull() != FALSE; }
};

class ReplySender
{
public:
	ReplySender(LLMessageSystem* msg);
	~ReplySender();

	void send(const LLUUID& id,
		const LLCacheNameEntry& entry, const LLHost& host);

private:
	void flush();

	LLMessageSystem*	mMsg;
	bool				mPending;
	bool				mCurrIsGroup;
	LLHost				mCurrHost;
};

ReplySender::ReplySender(LLMessageSystem* msg)
	: mMsg(msg), mPending(false), mCurrIsGroup(false)
{ }

ReplySender::~ReplySender()
{
	flush();
}

void ReplySender::send(const LLUUID& id,
	const LLCacheNameEntry& entry, const LLHost& host)
{
	if (mPending)
	{
		if (mCurrIsGroup != entry.mIsGroup
		||  mCurrHost != host)
		{
			flush();
		}
	}

	if (!mPending)
	{
		mPending = true;
		mCurrIsGroup = entry.mIsGroup;
		mCurrHost = host;

		if(mCurrIsGroup)
			mMsg->newMessageFast(_PREHASH_UUIDGroupNameReply);
		else
			mMsg->newMessageFast(_PREHASH_UUIDNameReply);
	}

	mMsg->nextBlockFast(_PREHASH_UUIDNameBlock);
	mMsg->addUUIDFast(_PREHASH_ID, id);
	if(mCurrIsGroup)
	{
		mMsg->addStringFast(_PREHASH_GroupName, entry.mGroupName);
	}
	else
	{
		mMsg->addStringFast(_PREHASH_FirstName,	entry.mFirstName);
		mMsg->addStringFast(_PREHASH_LastName, entry.mLastName);
	}

	if(mMsg->isSendFullFast(_PREHASH_UUIDNameBlock))
	{
		flush();
	}
}

void ReplySender::flush()
{
	if (mPending)
	{
		mMsg->sendReliable(mCurrHost);
		mPending = false;
	}
}


typedef std::set<LLUUID>					AskQueue;
typedef std::list<PendingReply*>			ReplyQueue;
typedef std::map<LLUUID,U32>				PendingQueue;
typedef std::map<LLUUID, LLCacheNameEntry*> Cache;
typedef std::map<std::string, LLUUID> 		ReverseCache;

class LLCacheName::Impl
{
public:
	LLMessageSystem*	mMsg;
	LLHost				mUpstreamHost;

	Cache				mCache;
		// the map of UUIDs to names
	ReverseCache   	  	mReverseCache;
		// map of names to UUIDs
	
	AskQueue			mAskNameQueue;
	AskQueue			mAskGroupQueue;
		// UUIDs to ask our upstream host about
	
	PendingQueue		mPendingQueue;
		// UUIDs that have been requested but are not in cache yet.

	ReplyQueue			mReplyQueue;
		// requests awaiting replies from us

	LLCacheNameSignal	mSignal;

	LLFrameTimer		mProcessTimer;

	Impl(LLMessageSystem* msg);
	~Impl();

	BOOL getName(const LLUUID& id, std::string& first, std::string& last);

	// <FS:Ansariel> Fix stale legacy requests
	//boost::signals2::connection addPending(const LLUUID& id, const LLCacheNameCallback& callback);
	//void addPending(const LLUUID& id, const LLHost& host);
	boost::signals2::connection addPending(const LLUUID& id, bool is_group, const LLCacheNameCallback& callback);
	void addPending(const LLUUID& id, bool is_group, const LLHost& host);
	// </FS:Ansariel>

	void processPendingAsks();
	void processPendingReplies();
	void sendRequest(const char* msg_name, const AskQueue& queue);
	bool isRequestPending(const LLUUID& id);

	// Message system callbacks.
	void processUUIDRequest(LLMessageSystem* msg, bool isGroup);
	void processUUIDReply(LLMessageSystem* msg, bool isGroup);

	static void handleUUIDNameRequest(LLMessageSystem* msg, void** userdata);
	static void handleUUIDNameReply(LLMessageSystem* msg, void** userdata);
	static void handleUUIDGroupNameRequest(LLMessageSystem* msg, void** userdata);
	static void handleUUIDGroupNameReply(LLMessageSystem* msg, void** userdata);
};


/// --------------------------------------------------------------------------
/// class LLCacheName
/// ---------------------------------------------------------------------------

LLCacheName::LLCacheName(LLMessageSystem* msg)
	: impl(* new Impl(msg))
	{ }

LLCacheName::LLCacheName(LLMessageSystem* msg, const LLHost& upstream_host)
	: impl(* new Impl(msg))
{
	sCacheName["waiting"] = "(Loading...)";
	sCacheName["nobody"] = "(nobody)";
	sCacheName["none"] = "(none)";
	setUpstream(upstream_host);
}

LLCacheName::~LLCacheName()
{
	delete &impl;
}

LLCacheName::Impl::Impl(LLMessageSystem* msg)
	: mMsg(msg), mUpstreamHost(LLHost())
{
	mMsg->setHandlerFuncFast(
		_PREHASH_UUIDNameRequest, handleUUIDNameRequest, (void**)this);
	mMsg->setHandlerFuncFast(
		_PREHASH_UUIDNameReply, handleUUIDNameReply, (void**)this);
	mMsg->setHandlerFuncFast(
		_PREHASH_UUIDGroupNameRequest, handleUUIDGroupNameRequest, (void**)this);
	mMsg->setHandlerFuncFast(
		_PREHASH_UUIDGroupNameReply, handleUUIDGroupNameReply, (void**)this);
}


LLCacheName::Impl::~Impl()
{
	for_each(mCache.begin(), mCache.end(), DeletePairedPointer());
	mCache.clear();
	for_each(mReplyQueue.begin(), mReplyQueue.end(), DeletePointer());
	mReplyQueue.clear();
}

// <FS:Ansariel> Fix stale legacy requests
//boost::signals2::connection LLCacheName::Impl::addPending(const LLUUID& id, const LLCacheNameCallback& callback)
boost::signals2::connection LLCacheName::Impl::addPending(const LLUUID& id, bool is_group, const LLCacheNameCallback& callback)
// </FS:Ansariel>
{
	PendingReply* reply = new PendingReply(id, LLHost());
	// <FS:Ansariel> Fix stale legacy requests
	reply->mIsGroup = is_group;
	reply->mRequestStart = LLTimer::getElapsedSeconds();
	// </FS:Ansariel>
	boost::signals2::connection res = reply->setCallback(callback);
	mReplyQueue.push_back(reply);
	return res;
}

// <FS:Ansariel> Fix stale legacy requests
//void LLCacheName::Impl::addPending(const LLUUID& id, const LLHost& host)
void LLCacheName::Impl::addPending(const LLUUID& id, bool is_group, const LLHost& host)
// </FS:Ansariel>
{
	PendingReply* reply = new PendingReply(id, host);
	// <FS:Ansariel> Fix stale legacy requests
	reply->mIsGroup = is_group;
	reply->mRequestStart = LLTimer::getElapsedSeconds();
	// </FS:Ansariel>
	mReplyQueue.push_back(reply);
}

void LLCacheName::setUpstream(const LLHost& upstream_host)
{
	impl.mUpstreamHost = upstream_host;
}

boost::signals2::connection LLCacheName::addObserver(const LLCacheNameCallback& callback)
{
	return impl.mSignal.connect(callback);
}

bool LLCacheName::importFile(std::istream& istr)
{
	LLSD data;
	if(LLSDParser::PARSE_FAILURE == LLSDSerialize::fromXMLDocument(data, istr))
	{
		return false;
	}

	// We'll expire entries more than a week old
	U32 now = (U32)time(NULL);
	const U32 SECS_PER_DAY = 60 * 60 * 24;
	U32 delete_before_time = now - (7 * SECS_PER_DAY);

	// iterate over the agents
	S32 count = 0;
	LLSD agents = data[AGENTS];
	LLSD::map_iterator iter = agents.beginMap();
	LLSD::map_iterator end = agents.endMap();
	for( ; iter != end; ++iter)
	{
		LLUUID id((*iter).first);
		LLSD agent = (*iter).second;
		U32 ctime = (U32)agent[CTIME].asInteger();
		if(ctime < delete_before_time) continue;

		LLCacheNameEntry* entry = new LLCacheNameEntry();
		entry->mIsGroup = false;
		entry->mCreateTime = ctime;
		entry->mFirstName = agent[FIRST].asString();
		entry->mLastName = agent[LAST].asString();
		impl.mCache[id] = entry;
		std::string fullname = buildFullName(entry->mFirstName, entry->mLastName);
		impl.mReverseCache[fullname] = id;

		++count;
	}
	LL_INFOS() << "LLCacheName loaded " << count << " agent names" << LL_ENDL;

	count = 0;
	LLSD groups = data[GROUPS];
	iter = groups.beginMap();
	end = groups.endMap();
	for( ; iter != end; ++iter)
	{
		LLUUID id((*iter).first);
		LLSD group = (*iter).second;
		U32 ctime = (U32)group[CTIME].asInteger();
		if(ctime < delete_before_time) continue;

		LLCacheNameEntry* entry = new LLCacheNameEntry();
		entry->mIsGroup = true;
		entry->mCreateTime = ctime;
		entry->mGroupName = group[NAME].asString();
		impl.mCache[id] = entry;
		impl.mReverseCache[entry->mGroupName] = id;
		++count;
	}
	LL_INFOS() << "LLCacheName loaded " << count << " group names" << LL_ENDL;
	return true;
}

void LLCacheName::exportFile(std::ostream& ostr)
{
	LLSD data;
	Cache::iterator iter = impl.mCache.begin();
	Cache::iterator end = impl.mCache.end();
	for( ; iter != end; ++iter)
	{
		// Only write entries for which we have valid data.
		LLCacheNameEntry* entry = iter->second;
		if(!entry
		   || (std::string::npos != entry->mFirstName.find('?'))
		   || (std::string::npos != entry->mGroupName.find('?')))
		{
			continue;
		}

		// store it
		LLUUID id = iter->first;
		std::string id_str = id.asString();
		// IDEVO TODO: Should we store SLIDs with last name "Resident" or not?
		if(!entry->mFirstName.empty() && !entry->mLastName.empty())
		{
			data[AGENTS][id_str][FIRST] = entry->mFirstName;
			data[AGENTS][id_str][LAST] = entry->mLastName;
			data[AGENTS][id_str][CTIME] = (S32)entry->mCreateTime;
		}
		else if(entry->mIsGroup && !entry->mGroupName.empty())
		{
			data[GROUPS][id_str][NAME] = entry->mGroupName;
			data[GROUPS][id_str][CTIME] = (S32)entry->mCreateTime;
		}
	}

	LLSDSerialize::toPrettyXML(data, ostr);
}


BOOL LLCacheName::Impl::getName(const LLUUID& id, std::string& first, std::string& last)
{
	if(id.isNull())
	{
		first = sCacheName["nobody"];
		last.clear();
		return TRUE;
	}

	LLCacheNameEntry* entry = get_ptr_in_map(mCache, id );
	if (entry)
	{
		first = entry->mFirstName;
		last =  entry->mLastName;
		return TRUE;
	}
	else
	{
		first = sCacheName["waiting"];
		last.clear();
		if (!isRequestPending(id))
		{
			mAskNameQueue.insert(id);
		}	
		return FALSE;
	}

}

// static
void LLCacheName::localizeCacheName(std::string key, std::string value)
{
	if (key!="" && value!= "" )
		sCacheName[key]=value;
	else
		LL_WARNS()<< " Error localizing cache key " << key << " To "<< value<<LL_ENDL;
}

BOOL LLCacheName::getFullName(const LLUUID& id, std::string& fullname)
{
	std::string first_name, last_name;
	BOOL res = impl.getName(id, first_name, last_name);
	fullname = buildFullName(first_name, last_name);
	return res;
}

// <FS:CR> Returns first name, last name
BOOL LLCacheName::getFirstLastName(const LLUUID& id, std::string& first, std::string& last)
{
	return impl.getName(id, first, last);
}
// </FS:CR>

BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group)
{
	if(id.isNull())
	{
		group = sCacheName["none"];
		return TRUE;
	}

	LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache,id);
	if (entry && entry->mGroupName.empty())
	{
		// COUNTER-HACK to combat James' HACK in exportFile()...
		// this group name was loaded from a name cache that did not
		// bother to save the group name ==> we must ask for it
		LL_DEBUGS() << "LLCacheName queuing HACK group request: " << id << LL_ENDL;
		entry = NULL;
	}

	if (entry)
	{
		group = entry->mGroupName;
		return TRUE;
	}
	else 
	{
		group = sCacheName["waiting"];
		if (!impl.isRequestPending(id))
		{
			impl.mAskGroupQueue.insert(id);
		}
		return FALSE;
	}
}

BOOL LLCacheName::getUUID(const std::string& first, const std::string& last, LLUUID& id)
{
	std::string full_name = buildFullName(first, last);
	return getUUID(full_name, id);
}

BOOL LLCacheName::getUUID(const std::string& full_name, LLUUID& id)
{
	ReverseCache::iterator iter = impl.mReverseCache.find(full_name);
	if (iter != impl.mReverseCache.end())
	{
		id = iter->second;
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

//static
std::string LLCacheName::buildFullName(const std::string& first, const std::string& last)
{
// <FS:CR> FIRE-6659: Legacy "Resident" name toggle
	if (LLAvatarName::trimResidentSurname())
	{
// </FS:CR> FIRE-6659: Legacy "Resident" name toggle
		std::string fullname = first;
		if (!last.empty()
			&& last != "Resident")
		{
			fullname += ' ';
			fullname += last;
		}
		return fullname;
// <FS:CR> FIRE-6659: Legacy "Resident" name toggle
	}
	else
	{
		std::string fullname = first;
		if (!last.empty())
		{
			if (fullname.find(" ") == std::string::npos)
			{
				fullname += ' ';
				fullname += last;
			}
		}
		else if (fullname.find(" ") == std::string::npos)
		{
			fullname.append(" Resident");
		}
		return fullname;
	}
// </FS:CR> FIRE-6659: Legacy "Resident" name toggle
}

//static
std::string LLCacheName::cleanFullName(const std::string& full_name)
{
// <FS:CR> FIRE-6659: Legacy "Resident" name toggle
	return (!LLAvatarName::trimResidentSurname() ? full_name : full_name.substr(0, full_name.find(" Resident")));
// </FS:CR> FIRE-6659: Legacy "Resident" name toggle
}

//static 
// Transform hard-coded name provided by server to a more legible username
std::string LLCacheName::buildUsername(const std::string& full_name)
{
	// rare, but handle hard-coded error names returned from server
	if (full_name == "(\?\?\?) (\?\?\?)")
	{
		return "(\?\?\?)";
	}

	std::string::size_type index = full_name.find(' ');
	if (index != std::string::npos)
	{
		std::string username;
		username = full_name.substr(0, index);
		std::string lastname = full_name.substr(index+1);

		if (lastname != "Resident")
		{
			username = username + "." + lastname;
		}
		
		LLStringUtil::toLower(username);
		return username;
	}

	// if the input wasn't a correctly formatted legacy name, just return it  
	// cleaned up from a potential terminal "Resident"
	return cleanFullName(full_name);
}

//static 
std::string LLCacheName::buildLegacyName(const std::string& complete_name)
{
	//boost::regexp was showing up in the crashreporter, so doing  
	//painfully manual parsing using substr. LF
	S32 open_paren = complete_name.rfind(" (");
	S32 close_paren = complete_name.rfind(')');

	if (open_paren != std::string::npos &&
		close_paren == complete_name.length()-1)
	{
		S32 length = close_paren - open_paren - 2;
		std::string legacy_name = complete_name.substr(open_paren+2, length);
		
		if (legacy_name.length() > 0)
		{			
			std::string cap_letter = legacy_name.substr(0, 1);
			LLStringUtil::toUpper(cap_letter);
			legacy_name = cap_letter + legacy_name.substr(1);
	
			S32 separator = legacy_name.find('.');

			if (separator != std::string::npos)
			{
				std::string last_name = legacy_name.substr(separator+1);
				legacy_name = legacy_name.substr(0, separator);

				if (last_name.length() > 0)
				{
					cap_letter = last_name.substr(0, 1);
					LLStringUtil::toUpper(cap_letter);
					legacy_name = legacy_name + " " + cap_letter + last_name.substr(1);
				}
// <FS:CR> FIRE-6659: Legacy "Resident" name toggle
				else if (!LLAvatarName::trimResidentSurname())
				{
					legacy_name.append(" Resident");
				}
			}
			else if (!LLAvatarName::trimResidentSurname())
			{
				legacy_name.append(" Resident");
// </FS:CR> FIRE-6659: Legacy "Resident" name toggle
			}

			return legacy_name;
		}
	}

	return complete_name;
}

// This is a little bit kludgy. LLCacheNameCallback is a slot instead of a function pointer.
//  The reason it is a slot is so that the legacy get() function below can bind an old callback
//  and pass it as a slot. The reason it isn't a boost::function is so that trackable behavior
//  doesn't get lost. As a result, we have to bind the slot to a signal to call it, even when
//  we call it immediately. -Steve
// NOTE: Even though passing first and last name is a bit of extra overhead, it eliminates the
//  potential need for any parsing should any code need to handle first and last name independently.
boost::signals2::connection LLCacheName::get(const LLUUID& id, bool is_group, const LLCacheNameCallback& callback)
{
	boost::signals2::connection res;
	
	if(id.isNull())
	{
		LLCacheNameSignal signal;
		signal.connect(callback);
		signal(id, sCacheName["nobody"], is_group);
		return res;
	}

	LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache, id );
	if (entry)
	{
		LLCacheNameSignal signal;
		signal.connect(callback);
		// id found in map therefore we can call the callback immediately.
		if (entry->mIsGroup)
		{
			signal(id, entry->mGroupName, entry->mIsGroup);
		}
		else
		{
			std::string fullname =
				buildFullName(entry->mFirstName, entry->mLastName);
			signal(id, fullname, entry->mIsGroup);
		}
	}
	else
	{
		// id not found in map so we must queue the callback call until available.
		if (!impl.isRequestPending(id))
		{
			if (is_group)
			{
				impl.mAskGroupQueue.insert(id);
			}
			else
			{
				impl.mAskNameQueue.insert(id);
			}
		}
		// <FS:Ansariel> Fix stale legacy requests
		//res = impl.addPending(id, callback);
		res = impl.addPending(id, is_group, callback);
	}
	return res;
}

// NaCl - Sound explorer
BOOL LLCacheName::getIfThere(const LLUUID& id, std::string& fullname, BOOL& is_group)
{
	if(id.isNull())
	{
		fullname = "";
		return FALSE;
	}

	LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache, id );
	if (entry)
	{
		if (entry->mIsGroup)
		{
			fullname = entry->mGroupName;
		}
		else
		{
			fullname = buildFullName(entry->mFirstName, entry->mLastName);
		}
		is_group = entry->mIsGroup;
		return TRUE;
	}
	fullname = "";
	return FALSE;
}
// NaCl end

boost::signals2::connection LLCacheName::getGroup(const LLUUID& group_id,
												  const LLCacheNameCallback& callback)
{
	return get(group_id, true, callback);
}

boost::signals2::connection LLCacheName::get(const LLUUID& id, bool is_group, old_callback_t callback, void* user_data)
{
	return get(id, is_group, boost::bind(callback, _1, _2, _3, user_data));
}

void LLCacheName::processPending()
{
	const F32 SECS_BETWEEN_PROCESS = 0.1f;
	if(!impl.mProcessTimer.checkExpirationAndReset(SECS_BETWEEN_PROCESS))
	{
		return;
	}

	if(!impl.mUpstreamHost.isOk())
	{
		LL_DEBUGS() << "LLCacheName::processPending() - bad upstream host."
				 << LL_ENDL;
		return;
	}

	impl.processPendingAsks();
	impl.processPendingReplies();
}

void LLCacheName::deleteEntriesOlderThan(S32 secs)
{
	U32 now = (U32)time(NULL);
	U32 expire_time = now - secs;
	for(Cache::iterator iter = impl.mCache.begin(); iter != impl.mCache.end(); )
	{
		Cache::iterator curiter = iter++;
		LLCacheNameEntry* entry = curiter->second;
		if (entry->mCreateTime < expire_time)
		{
			delete entry;
			impl.mCache.erase(curiter);
		}
	}

	// These are pending requests that we never heard back from.
	U32 pending_expire_time = now - PENDING_TIMEOUT_SECS;
	for(PendingQueue::iterator p_iter = impl.mPendingQueue.begin();
		p_iter != impl.mPendingQueue.end(); )
	{
		PendingQueue::iterator p_curitor = p_iter++;
 
		if (p_curitor->second < pending_expire_time)
		{
			impl.mPendingQueue.erase(p_curitor);
		}
	}
}


void LLCacheName::dump()
{
	for (Cache::iterator iter = impl.mCache.begin(),
			 end = impl.mCache.end();
		 iter != end; iter++)
	{
		LLCacheNameEntry* entry = iter->second;
		if (entry->mIsGroup)
		{
			LL_INFOS()
				<< iter->first << " = (group) "
				<< entry->mGroupName
				<< " @ " << entry->mCreateTime
				<< LL_ENDL;
		}
		else
		{
			LL_INFOS()
				<< iter->first << " = "
				<< buildFullName(entry->mFirstName, entry->mLastName)
				<< " @ " << entry->mCreateTime
				<< LL_ENDL;
		}
	}
}

void LLCacheName::dumpStats()
{
	LL_INFOS() << "Queue sizes: "
			<< " Cache=" << impl.mCache.size()
			<< " AskName=" << impl.mAskNameQueue.size()
			<< " AskGroup=" << impl.mAskGroupQueue.size()
			<< " Pending=" << impl.mPendingQueue.size()
			<< " Reply=" << impl.mReplyQueue.size()
// 			<< " Observers=" << impl.mSignal.size()
			<< LL_ENDL;
}

void LLCacheName::clear()
{
	for_each(impl.mCache.begin(), impl.mCache.end(), DeletePairedPointer());
	impl.mCache.clear();
}

//static 
std::string LLCacheName::getDefaultName()
{
	return sCacheName["waiting"];
}

//static 
std::string LLCacheName::getDefaultLastName()
{
	return "Resident";
}

void LLCacheName::Impl::processPendingAsks()
{
	sendRequest(_PREHASH_UUIDNameRequest, mAskNameQueue);
	sendRequest(_PREHASH_UUIDGroupNameRequest, mAskGroupQueue);
	mAskNameQueue.clear();
	mAskGroupQueue.clear();
}

void LLCacheName::Impl::processPendingReplies()
{
	// First call all the callbacks, because they might send messages.
	// <FS:Ansariel> Fix stale legacy requests
	//for(ReplyQueue::iterator it = mReplyQueue.begin(); it != mReplyQueue.end(); ++it)
	for (ReplyQueue::iterator it = mReplyQueue.begin(); it != mReplyQueue.end(); )
	// </FS:Ansariel>
	{
		PendingReply* reply = *it;

		LLCacheNameEntry* entry = get_ptr_in_map(mCache, reply->mID);
		// <FS:Ansariel> Fix stale legacy requests
		//if(!entry) continue;
		if (!entry)
		{
			static const F64SecondsImplicit TIMEOUT(10.0);
			if (LLTimer::getElapsedSeconds() - reply->mRequestStart > TIMEOUT)
			{
				if (reply->mIsGroup)
				{
					(reply->mSignal)(reply->mID, "(\?\?\?)", true);
				}
				else
				{
					(reply->mSignal)(reply->mID, "(\?\?\?).(\?\?\?)", false);
				}

				// Assuming we don't need to send a reply to the simulator
				// since we never received anything for this request either.

				delete reply;
				it = mReplyQueue.erase(it);
				continue;
			}
			else
			{
				it++;
				continue;
			}
		}
		// </FS:Ansariel>

		if (!entry->mIsGroup)
		{
			std::string fullname =
				LLCacheName::buildFullName(entry->mFirstName, entry->mLastName);
			(reply->mSignal)(reply->mID, fullname, false);
		}
		else
		{
			(reply->mSignal)(reply->mID, entry->mGroupName, true);
		}

		// <FS:Ansariel> Fix stale legacy requests
		it++;
	}

	// Forward on all replies, if needed.
	ReplySender sender(mMsg);
	for(ReplyQueue::iterator it = mReplyQueue.begin(); it != mReplyQueue.end(); ++it)
	{
		PendingReply* reply = *it;
		LLCacheNameEntry* entry = get_ptr_in_map(mCache, reply->mID);
		if(!entry) continue;

		if (reply->mHost.isOk())
		{
			sender.send(reply->mID, *entry, reply->mHost);
		}

		reply->done();
	}
	
	for(ReplyQueue::iterator it = mReplyQueue.begin(); it != mReplyQueue.end(); )
	{
		ReplyQueue::iterator curit = it++;
		PendingReply* reply = *curit;
		if (reply->isDone())
		{
			delete reply;
			mReplyQueue.erase(curit);
		}
	}
}


void LLCacheName::Impl::sendRequest(
	const char* msg_name,
	const AskQueue& queue)
{
	if(queue.empty())
	{
		return;		
	}

	bool start_new_message = true;
	AskQueue::const_iterator it = queue.begin();
	AskQueue::const_iterator end = queue.end();
	for(; it != end; ++it)
	{
		if(start_new_message)
		{
			start_new_message = false;
			mMsg->newMessageFast(msg_name);
		}
		mMsg->nextBlockFast(_PREHASH_UUIDNameBlock);
		mMsg->addUUIDFast(_PREHASH_ID, (*it));

		if(mMsg->isSendFullFast(_PREHASH_UUIDNameBlock))
		{
			start_new_message = true;
			mMsg->sendReliable(mUpstreamHost);
		}
	}
	if(!start_new_message)
	{
		mMsg->sendReliable(mUpstreamHost);
	}
}

bool LLCacheName::Impl::isRequestPending(const LLUUID& id)
{
	U32 now = (U32)time(NULL);
	U32 expire_time = now - PENDING_TIMEOUT_SECS;

	PendingQueue::iterator iter = mPendingQueue.find(id);

	if (iter == mPendingQueue.end()
		|| (iter->second < expire_time) )
	{
		mPendingQueue[id] = now;
		return false;
	}

	return true;
}
	
void LLCacheName::Impl::processUUIDRequest(LLMessageSystem* msg, bool isGroup)
{
	// You should only get this message if the cache is at the simulator
	// level, hence having an upstream provider.
	if (!mUpstreamHost.isOk())
	{
		LL_WARNS() << "LLCacheName - got UUID name/group request, but no upstream provider!" << LL_ENDL;
		return;
	}

	LLHost fromHost = msg->getSender();
	ReplySender sender(msg);

	S32 count = msg->getNumberOfBlocksFast(_PREHASH_UUIDNameBlock);
	for(S32 i = 0; i < count; ++i)
	{
		LLUUID id;
		msg->getUUIDFast(_PREHASH_UUIDNameBlock, _PREHASH_ID, id, i);
		LLCacheNameEntry* entry = get_ptr_in_map(mCache, id);
		if(entry)
		{
			if (isGroup != entry->mIsGroup)
			{
				LL_WARNS() << "LLCacheName - Asked for "
						<< (isGroup ? "group" : "user") << " name, "
						<< "but found "
						<< (entry->mIsGroup ? "group" : "user")
						<< ": " << id << LL_ENDL;
			}
			else
			{
				// ...it's in the cache, so send it as the reply
				sender.send(id, *entry, fromHost);
			}
		}
		else
		{
			if (!isRequestPending(id))
			{
				if (isGroup)
				{
					mAskGroupQueue.insert(id);
				}
				else
				{
					mAskNameQueue.insert(id);
				}
			}
			
			// <FS:Ansariel> Fix stale legacy requests
			//addPending(id, fromHost);
			addPending(id, isGroup, fromHost);
		}
	}
}



void LLCacheName::Impl::processUUIDReply(LLMessageSystem* msg, bool isGroup)
{
	S32 count = msg->getNumberOfBlocksFast(_PREHASH_UUIDNameBlock);
	for(S32 i = 0; i < count; ++i)
	{
		LLUUID id;
		msg->getUUIDFast(_PREHASH_UUIDNameBlock, _PREHASH_ID, id, i);
		LLCacheNameEntry* entry = get_ptr_in_map(mCache, id);
		if (!entry)
		{
			entry = new LLCacheNameEntry;
			mCache[id] = entry;
		}

		mPendingQueue.erase(id);

		entry->mIsGroup = isGroup;
		entry->mCreateTime = (U32)time(NULL);
		if (!isGroup)
		{
			msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_FirstName, entry->mFirstName, i);
			msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_LastName,  entry->mLastName, i);
		}
		else
		{	// is group
			msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_GroupName, entry->mGroupName, i);
			LLStringFn::replace_ascii_controlchars(entry->mGroupName, LL_UNKNOWN_CHAR);
		}

		if (!isGroup)
		{
			// NOTE: Very occasionally the server sends down a full name
			// in the first name field with an empty last name, for example,
			// first = "Ladanie1 Resident", last = "".
			// I cannot reproduce this, nor can I find a bug in the server code.
			// Ensure "Resident" does not appear via cleanFullName, because
			// buildFullName only checks last name. JC
			std::string full_name;
			if (entry->mLastName.empty())
			{
// <FS:CR> FIRE-6659: Legacy "Resident" name toggle
				if (!LLAvatarName::trimResidentSurname())
				{
// </FS:CR> FIRE-6659: Legacy "Resident" name toggle
					full_name = cleanFullName(entry->mFirstName);

					//fix what we are putting in the cache
					entry->mFirstName = full_name;
					entry->mLastName = "Resident";
				}
// <FS:CR> FIRE-6659: Legacy "Resident" name toggle
				else
				{
					if (entry->mFirstName.find(" ")==std::string::npos)
					{
						entry->mLastName = "Resident";
					}
					full_name = LLCacheName::buildFullName(entry->mFirstName, entry->mLastName);
				}
// </FS:CR> FIRE-6659: Legacy "Resident" name toggle
			}
			else
			{
				full_name = LLCacheName::buildFullName(entry->mFirstName, entry->mLastName);
			}
			mSignal(id, full_name, false);
			mReverseCache[full_name] = id;
		}
		else
		{
			mSignal(id, entry->mGroupName, true);
			mReverseCache[entry->mGroupName] = id;
		}
	}
}



// static call back functions

void LLCacheName::Impl::handleUUIDNameReply(LLMessageSystem* msg, void** userData)
{
	((LLCacheName::Impl*)userData)->processUUIDReply(msg, false);
}

void LLCacheName::Impl::handleUUIDNameRequest(LLMessageSystem* msg, void** userData)
{
	((LLCacheName::Impl*)userData)->processUUIDRequest(msg, false);
}

void LLCacheName::Impl::handleUUIDGroupNameRequest(LLMessageSystem* msg, void** userData)
{
	((LLCacheName::Impl*)userData)->processUUIDRequest(msg, true);
}

void LLCacheName::Impl::handleUUIDGroupNameReply(LLMessageSystem* msg, void** userData)
{
	((LLCacheName::Impl*)userData)->processUUIDReply(msg, true);
}
