/** 
 * @file llavatarnamecache.cpp
 * @brief Provides lookup of avatar SLIDs ("bobsmith123") and display names
 * ("James Cook") from avatar UUIDs.
 *
 * $LicenseInfo:firstyear=2010&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 "llavatarnamecache.h"

#include "llcachename.h"		// we wrap this system
#include "llframetimer.h"
#include "llsd.h"
#include "llsdserialize.h"
#include "httpresponse.h"
#include "llhttpsdhandler.h"
#include <boost/tokenizer.hpp>

#include "httpcommon.h"
#include "httprequest.h"
#include "httpheaders.h"
#include "httpoptions.h"
#include "llcoros.h"
#include "lleventcoro.h"
#include "llcorehttputil.h"
#include "llexception.h"
#include "stringize.h"

#include <map>
#include <set>

#include "llcontrol.h" // <FS:Ansariel> Optional legacy name cache expiration

// Time-to-live for a temp cache entry.
const F64 TEMP_CACHE_ENTRY_LIFETIME = 60.0;
// Maximum time an unrefreshed cache entry is allowed.
const F64 MAX_UNREFRESHED_TIME = 20.0 * 60.0;

// Send bulk lookup requests a few times a second at most.
// Only need per-frame timing resolution.
static LLFrameTimer sRequestTimer;

// static to avoid unnessesary dependencies
LLCore::HttpRequest::ptr_t		sHttpRequest;
LLCore::HttpHeaders::ptr_t		sHttpHeaders;
LLCore::HttpOptions::ptr_t		sHttpOptions;
LLCore::HttpRequest::policy_t	sHttpPolicy;
LLCore::HttpRequest::priority_t	sHttpPriority;

/* Sample response:
<?xml version="1.0"?>
<llsd>
  <map>
    <key>agents</key>
    <array>
      <map>
        <key>display_name_next_update</key>
        <date>2010-04-16T21:34:02+00:00Z</date>
        <key>display_name_expires</key>
        <date>2010-04-16T21:32:26.142178+00:00Z</date>
        <key>display_name</key>
        <string>MickBot390 LLQABot</string>
        <key>sl_id</key>
        <string>mickbot390.llqabot</string>
        <key>id</key>
        <string>0012809d-7d2d-4c24-9609-af1230a37715</string>
        <key>is_display_name_default</key>
        <boolean>false</boolean>
      </map>
      <map>
        <key>display_name_next_update</key>
        <date>2010-04-16T21:34:02+00:00Z</date>
        <key>display_name_expires</key>
        <date>2010-04-16T21:32:26.142178+00:00Z</date>
        <key>display_name</key>
        <string>Bjork Gudmundsdottir</string>
        <key>sl_id</key>
        <string>sardonyx.linden</string>
        <key>id</key>
        <string>3941037e-78ab-45f0-b421-bd6e77c1804d</string>
        <key>is_display_name_default</key>
        <boolean>true</boolean>
      </map>
    </array>
  </map>
</llsd>
*/

// Coroutine for sending and processing avatar name cache requests.  
// Do not call directly.  See documentation in lleventcoro.h and llcoro.h for
// further explanation.

LLAvatarNameCache::LLAvatarNameCache()
{
    // Will be set to running later
    // For now fail immediate lookups and query async ones.
    mRunning = false;

    mUsePeopleAPI = true;

    sHttpRequest = LLCore::HttpRequest::ptr_t(new LLCore::HttpRequest());
    sHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders());
    sHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions());
    sHttpPolicy = LLCore::HttpRequest::DEFAULT_POLICY_ID;
    sHttpPriority = 0;
}

LLAvatarNameCache::~LLAvatarNameCache()
{
    sHttpRequest.reset();
    sHttpHeaders.reset();
    sHttpOptions.reset();
    mCache.clear();
}

void LLAvatarNameCache::requestAvatarNameCache_(std::string url, std::vector<LLUUID> agentIds)
{
    LL_DEBUGS("AvNameCache") << "Entering coroutine " << LLCoros::getName()
        << " with url '" << url << "', requesting " << agentIds.size() << " Agent Ids" << LL_ENDL;

    // Check pointer that can be cleaned up by cleanupClass()
    if (!sHttpRequest || !sHttpOptions || !sHttpHeaders)
    {
        LL_WARNS("AvNameCache") << " Trying to request name cache when http pointers are not initialized." << LL_ENDL;
        return;
    }

    LLSD httpResults;
    bool success = true;

    try
    {

        LLCoreHttpUtil::HttpCoroutineAdapter httpAdapter("NameCache", sHttpPolicy);
        LLSD results = httpAdapter.getAndSuspend(sHttpRequest, url);

        LL_DEBUGS() << results << LL_ENDL;

        if (!results.isMap())
        {
            LL_WARNS("AvNameCache") << " Invalid result returned from LLCoreHttpUtil::HttpCoroHandler." << LL_ENDL;
            success = false;
        }
        else
        {
            httpResults = results["http_result"];
            if (!httpResults.isMap())
            {
                success = false;
                LL_WARNS("AvNameCache") << " Invalid http_result returned from LLCoreHttpUtil::HttpCoroHandler." << LL_ENDL;
            }
            else
            {
                success = httpResults["success"].asBoolean();
                if (!success)
                {
                    LL_WARNS("AvNameCache") << "Error result from LLCoreHttpUtil::HttpCoroHandler. Code "
                        << httpResults["status"] << ": '" << httpResults["message"] << "'" << LL_ENDL;
                }
            }
        }

        if (LLAvatarNameCache::instanceExists())
        {
            if (!success)
            {   // on any sort of failure add dummy records for any agent IDs 
                // in this request that we do not have cached already
                std::vector<LLUUID>::const_iterator it = agentIds.begin();
                for (; it != agentIds.end(); ++it)
                {
                    const LLUUID& agent_id = *it;
                    LLAvatarNameCache::getInstance()->handleAgentError(agent_id);
                }
                return;
            }

            LLAvatarNameCache::getInstance()->handleAvNameCacheSuccess(results, httpResults);
        }
    }
    catch (const LLCoros::Stop&)
    {
        LL_DEBUGS("AvNameCache") << "Received a shutdown exception" << LL_ENDL;
    }
    catch (...)
    {
        LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << LLCoros::getName()
                                          << "('" << url << "', "
                                          << agentIds.size() << "Agent Ids,"
                                          << " http result: " << S32(success)
                                          << " has response: " << S32(httpResults.size())
                                          << ")"));
        throw;
    }
}

void LLAvatarNameCache::handleAvNameCacheSuccess(const LLSD &data, const LLSD &httpResult)
{

    LLSD headers = httpResult["headers"];
    // Pull expiration out of headers if available
    F64 expires = LLAvatarNameCache::nameExpirationFromHeaders(headers);
    F64 now = LLFrameTimer::getTotalSeconds();

    const LLSD& agents = data["agents"];
    LLSD::array_const_iterator it = agents.beginArray();
    for (; it != agents.endArray(); ++it)
    {
        const LLSD& row = *it;
        LLUUID agent_id = row["id"].asUUID();

        LLAvatarName av_name;
        // <FS> Contact sets alias
        bool dn_removed;
        std::string pseudonym;
        if (mCustomNameCheckCallback && mCustomNameCheckCallback(agent_id, dn_removed, pseudonym))
        {
            LLSD info(row);
            info["is_display_name_default"] = dn_removed;
            info["display_name"] = dn_removed
                ? (info["legacy_first_name"].asString() + " " + info["legacy_last_name"].asString())
                : pseudonym;
            av_name.fromLLSD(info);
        }
        else
        // </FS> Contact sets alias
        av_name.fromLLSD(row);

        // Use expiration time from header
        av_name.mExpires = expires;

        LL_DEBUGS("AvNameCache") << "LLAvatarNameResponder::result for " << agent_id << LL_ENDL;
        av_name.dump();

        // cache it and fire signals
        LLAvatarNameCache::processName(agent_id, av_name);
    }

    // Same logic as error response case
    const LLSD& unresolved_agents = data["bad_ids"];
    S32  num_unresolved = unresolved_agents.size();
    if (num_unresolved > 0)
    {
        LL_WARNS("AvNameCache") << "LLAvatarNameResponder::result " << num_unresolved << " unresolved ids; "
            << "expires in " << expires - now << " seconds"
            << LL_ENDL;
        it = unresolved_agents.beginArray();
        for (; it != unresolved_agents.endArray(); ++it)
        {
            const LLUUID& agent_id = *it;

            LL_WARNS("AvNameCache") << "LLAvatarNameResponder::result "
                << "failed id " << agent_id
                << LL_ENDL;

            LLAvatarNameCache::handleAgentError(agent_id);
        }
    }
    LL_DEBUGS("AvNameCache") << "LLAvatarNameResponder::result "
        << LLAvatarNameCache::mCache.size() << " cached names"
        << LL_ENDL;
}

// Provide some fallback for agents that return errors
void LLAvatarNameCache::handleAgentError(const LLUUID& agent_id)
{
	std::map<LLUUID,LLAvatarName>::iterator existing = mCache.find(agent_id);
	if (existing == mCache.end())
    {
		// <FS:Ansariel> Don't re-request names for agents with null uuid.
		if (agent_id.isNull())
		{
			LL_WARNS("AvNameCache") << "LLAvatarNameCache handling error for agent with null uuid" << LL_ENDL;
			mPendingQueue.erase(agent_id);
			return;
		}
		// </FS:Ansariel>

        // there is no existing cache entry, so make a temporary name from legacy
        LL_WARNS("AvNameCache") << "LLAvatarNameCache get legacy for agent "
								<< agent_id << LL_ENDL;
        gCacheName->get(agent_id, false,  // legacy compatibility
                        boost::bind(&LLAvatarNameCache::legacyNameFetch, _1, _2, _3));
    }
	else
    {
        // we have a cached (but probably expired) entry - since that would have
        // been returned by the get method, there is no need to signal anyone

        // Clear this agent from the pending list
        LLAvatarNameCache::mPendingQueue.erase(agent_id);

        LLAvatarName& av_name = existing->second;
        LL_DEBUGS("AvNameCache") << "LLAvatarNameCache use cache for agent " << agent_id << LL_ENDL;
		av_name.dump();

		 // Reset expiry time so we don't constantly rerequest.
		av_name.setExpires(TEMP_CACHE_ENTRY_LIFETIME);
    }
}

void LLAvatarNameCache::processName(const LLUUID& agent_id, const LLAvatarName& av_name)
{
	if (agent_id.isNull())
	{
		return;
	}

    bool updated_account = true; // assume obsolete value for new arrivals by default

    std::map<LLUUID, LLAvatarName>::iterator it = mCache.find(agent_id);
    if (it != mCache.end()
        && (*it).second.getAccountName() == av_name.getAccountName())
    {
        updated_account = false;
    }

	// Add to the cache
	mCache[agent_id] = av_name;

	// Suppress request from the queue
	mPendingQueue.erase(agent_id);

	// notify mute list about changes
    if (updated_account && mAccountNameChangedCallback)
    {
        mAccountNameChangedCallback(agent_id, av_name);
    }

	// Signal everyone waiting on this name
	signal_map_t::iterator sig_it =	mSignalMap.find(agent_id);
	if (sig_it != mSignalMap.end())
	{
		callback_signal_t* signal = sig_it->second;
		(*signal)(agent_id, av_name);

		mSignalMap.erase(agent_id);

		delete signal;
		signal = NULL;
	}


}

void LLAvatarNameCache::requestNamesViaCapability()
{
	F64 now = LLFrameTimer::getTotalSeconds();

	// URL format is like:
	// http://pdp60.lindenlab.com:8000/agents/?ids=3941037e-78ab-45f0-b421-bd6e77c1804d&ids=0012809d-7d2d-4c24-9609-af1230a37715&ids=0019aaba-24af-4f0a-aa72-6457953cf7f0
	//
	// Apache can handle URLs of 4096 chars, but let's be conservative
	static const U32 NAME_URL_MAX = 4096;
	static const U32 NAME_URL_SEND_THRESHOLD = 3500;

	std::string url;
	url.reserve(NAME_URL_MAX);

	std::vector<LLUUID> agent_ids;
	agent_ids.reserve(128);
	
	U32 ids = 0;
	ask_queue_t::const_iterator it;
	while(!mAskQueue.empty())
	{
		it = mAskQueue.begin();
		LLUUID agent_id = *it;
		mAskQueue.erase(it);

		if (url.empty())
		{
			// ...starting new request
			url += mNameLookupURL;
			url += "?ids=";
			ids = 1;
		}
		else
		{
			// ...continuing existing request
			url += "&ids=";
			ids++;
		}
		url += agent_id.asString();
		agent_ids.push_back(agent_id);

		// mark request as pending
		mPendingQueue[agent_id] = now;

		if (url.size() > NAME_URL_SEND_THRESHOLD)
		{
			break;
		}
	}

    if (!url.empty())
    {
        LL_DEBUGS("AvNameCache") << "requested " << ids << " ids" << LL_ENDL;

        std::string coroname = 
            LLCoros::instance().launch("LLAvatarNameCache::requestAvatarNameCache_",
            boost::bind(&LLAvatarNameCache::requestAvatarNameCache_, url, agent_ids));
        LL_DEBUGS("AvNameCache") << coroname << " with  url '" << url << "', agent_ids.size()=" << agent_ids.size() << LL_ENDL;

	}
}

void LLAvatarNameCache::legacyNameCallback(const LLUUID& agent_id,
										   const std::string& full_name,
										   bool is_group)
{
	// Put the received data in the cache
	legacyNameFetch(agent_id, full_name, is_group);
	
	// Retrieve the name and set it to never (or almost never...) expire: when we are using the legacy
	// protocol, we do not get an expiration date for each name and there's no reason to ask the 
	// data again and again so we set the expiration time to the largest value admissible.
	std::map<LLUUID,LLAvatarName>::iterator av_record = LLAvatarNameCache::getInstance()->mCache.find(agent_id);
	LLAvatarName& av_name = av_record->second;
	av_name.setExpires(MAX_UNREFRESHED_TIME);
}

void LLAvatarNameCache::legacyNameFetch(const LLUUID& agent_id,
										const std::string& full_name,
										bool is_group)
{
	LL_DEBUGS("AvNameCache") << "LLAvatarNameCache agent " << agent_id << " "
							 << "full name '" << full_name << "'"
	                         << ( is_group ? " [group]" : "" )
	                         << LL_ENDL;
	
	// Construct an av_name record from this name.
	LLAvatarName av_name;
	av_name.fromString(full_name);
	
	// Add to cache: we're still using the new cache even if we're using the old (legacy) protocol.
	LLAvatarNameCache::getInstance()->processName(agent_id, av_name);
}

void LLAvatarNameCache::requestNamesViaLegacy()
{
	static const S32 MAX_REQUESTS = 100;
	F64 now = LLFrameTimer::getTotalSeconds();
	std::string full_name;
	ask_queue_t::const_iterator it;
	for (S32 requests = 0; !mAskQueue.empty() && requests < MAX_REQUESTS; ++requests)
	{
		it = mAskQueue.begin();
		LLUUID agent_id = *it;
		mAskQueue.erase(it);

		// Mark as pending first, just in case the callback is immediately
		// invoked below.  This should never happen in practice.
		mPendingQueue[agent_id] = now;

		LL_DEBUGS("AvNameCache") << "agent " << agent_id << LL_ENDL;

		gCacheName->get(agent_id, false,  // legacy compatibility
			boost::bind(&LLAvatarNameCache::legacyNameCallback, _1, _2, _3));
	}
}

// <FS:Ansariel> FIRE-6659: Legacy "Resident" name toggle
void LLAvatarNameCache::clearCache()
{
	mCache.clear();
}
// </FS:Ansariel>

bool LLAvatarNameCache::importFile(std::istream& istr)
{
	LLSD data;
	if (LLSDParser::PARSE_FAILURE == LLSDSerialize::fromXMLDocument(data, istr))
	{
        LL_WARNS("AvNameCache") << "avatar name cache data xml parse failed" << LL_ENDL;
		return false;
	}

	// by convention LLSD storage is a map
	// we only store one entry in the map
	LLSD agents = data["agents"];

	LLUUID agent_id;
	LLAvatarName av_name;
	LLSD::map_const_iterator it = agents.beginMap();
	for ( ; it != agents.endMap(); ++it)
	{
		agent_id.set(it->first);
		av_name.fromLLSD( it->second );
		mCache[agent_id] = av_name;
	}
    LL_INFOS("AvNameCache") << "LLAvatarNameCache loaded " << mCache.size() << LL_ENDL;
	// Some entries may have expired since the cache was stored,
    // but they will be flushed in the first call to eraseUnrefreshed
    // from LLAvatarNameResponder::idle

    return true;
}

void LLAvatarNameCache::exportFile(std::ostream& ostr)
{
	LLSD agents;
	F64 max_unrefreshed = LLFrameTimer::getTotalSeconds() - MAX_UNREFRESHED_TIME;
    LL_INFOS("AvNameCache") << "LLAvatarNameCache at exit cache has " << mCache.size() << LL_ENDL;
	cache_t::const_iterator it = mCache.begin();
	for ( ; it != mCache.end(); ++it)
	{
		const LLUUID& agent_id = it->first;
		const LLAvatarName& av_name = it->second;
		// Do not write temporary or expired entries to the stored cache
		if (av_name.isValidName(max_unrefreshed))
		{
			// key must be a string
			agents[agent_id.asString()] = av_name.asLLSD();
		}
	}
    LL_INFOS("AvNameCache") << "LLAvatarNameCache returning " << agents.size() << LL_ENDL;
	LLSD data;
	data["agents"] = agents;
	LLSDSerialize::toPrettyXML(data, ostr);
}

void LLAvatarNameCache::setNameLookupURL(const std::string& name_lookup_url)
{
	mNameLookupURL = name_lookup_url;
}

bool LLAvatarNameCache::hasNameLookupURL()
{
	return !mNameLookupURL.empty();
}

void LLAvatarNameCache::setUsePeopleAPI(bool use_api)
{
    mUsePeopleAPI = use_api;
}

bool LLAvatarNameCache::usePeopleAPI()
{
	return hasNameLookupURL() && mUsePeopleAPI;
}

void LLAvatarNameCache::idle()
{
	// By convention, start running at first idle() call
	mRunning = true;

	// *TODO: Possibly re-enabled this based on People API load measurements
	// 100 ms is the threshold for "user speed" operations, so we can
	// stall for about that long to batch up requests.
	const F32 SECS_BETWEEN_REQUESTS = 0.1f;
	if (!sRequestTimer.hasExpired())
	{
		return;
	}

	if (!mAskQueue.empty())
	{
        if (usePeopleAPI())
        {
            requestNamesViaCapability();
        }
        else
        {
            LL_WARNS_ONCE("AvNameCache") << "LLAvatarNameCache still using legacy api" << LL_ENDL;
            requestNamesViaLegacy();
        }
	}

	if (mAskQueue.empty())
	{
		// cleared the list, reset the request timer.
		sRequestTimer.resetWithExpiry(SECS_BETWEEN_REQUESTS);
	}

    // erase anything that has not been refreshed for more than MAX_UNREFRESHED_TIME
    eraseUnrefreshed();
}

bool LLAvatarNameCache::isRequestPending(const LLUUID& agent_id)
{
	bool isPending = false;
	const F64 PENDING_TIMEOUT_SECS = 5.0 * 60.0;

	pending_queue_t::const_iterator it = mPendingQueue.find(agent_id);
	if (it != mPendingQueue.end())
	{
		// in the list of requests in flight, retry if too old
		F64 expire_time = LLFrameTimer::getTotalSeconds() - PENDING_TIMEOUT_SECS;
		isPending = (it->second > expire_time);
	}
	return isPending;
}

void LLAvatarNameCache::eraseUnrefreshed()
{
	F64 now = LLFrameTimer::getTotalSeconds();
	F64 max_unrefreshed = now - MAX_UNREFRESHED_TIME;

    if (!mLastExpireCheck || mLastExpireCheck < max_unrefreshed)
    {
        mLastExpireCheck = now;
        S32 expired = 0;
        for (cache_t::iterator it = mCache.begin(); it != mCache.end();)
        {
            const LLAvatarName& av_name = it->second;
            if (av_name.mExpires < max_unrefreshed)
            {
                LL_DEBUGS("AvNameCacheExpired") << "LLAvatarNameCache " << it->first 
                                         << " user '" << av_name.getAccountName() << "' "
                                         << "expired " << now - av_name.mExpires << " secs ago"
                                         << LL_ENDL;
                mCache.erase(it++);
                expired++;
            }
			else
			{
				++it;
			}
        }
        LL_INFOS("AvNameCache") << "LLAvatarNameCache expired " << expired << " cached avatar names, "
                                << mCache.size() << " remaining" << LL_ENDL;
	}
}

//static, wrapper
bool LLAvatarNameCache::get(const LLUUID& agent_id, LLAvatarName *av_name)
{
    return LLAvatarNameCache::getInstance()->getName(agent_id, av_name);
}
// fills in av_name if it has it in the cache, even if expired (can check expiry time)
// returns bool specifying  if av_name was filled, false otherwise
bool LLAvatarNameCache::getName(const LLUUID& agent_id, LLAvatarName *av_name)
{
	// <FS:Beq> Avoid null entries entering NameCache
	// This is a catch-all, better to avoid at call site
	if( agent_id.isNull() )
	{
		return false;
	}
	// </FS:Beq>
	if (mRunning)
	{
		// ...only do immediate lookups when cache is running
		std::map<LLUUID,LLAvatarName>::iterator it = mCache.find(agent_id);
		if (it != mCache.end())
		{
			*av_name = it->second;
			// <FS> Contact sets alias
			bool dn_removed;
			std::string pseudonym;
			if (mCustomNameCheckCallback && mCustomNameCheckCallback(agent_id, dn_removed, pseudonym))
			{
				LLSD info = av_name->asLLSD();
				info["is_display_name_default"] = dn_removed;
				info["display_name"] = dn_removed
					? (info["legacy_first_name"].asString() + " " + info["legacy_last_name"].asString())
					: pseudonym;
				av_name->fromLLSD(info);
			}
			// <FS/> Contact sets alias

			// re-request name if entry is expired
			if (av_name->mExpires < LLFrameTimer::getTotalSeconds())
			{
				if (!isRequestPending(agent_id))
				{
					LL_DEBUGS("AvNameCache") << "LLAvatarNameCache refresh agent " << agent_id
											 << LL_ENDL;
					mAskQueue.insert(agent_id);
				}
			}
				
			return true;
		}
	}

	if (!isRequestPending(agent_id))
	{
		LL_DEBUGS("AvNameCache") << "LLAvatarNameCache queue request for agent " << agent_id << LL_ENDL;
		mAskQueue.insert(agent_id);
	}

	return false;
}

void LLAvatarNameCache::fireSignal(const LLUUID& agent_id,
								   const callback_slot_t& slot,
								   const LLAvatarName& av_name)
{
	callback_signal_t signal;
	signal.connect(slot);
	signal(agent_id, av_name);
}

// static, wrapper
LLAvatarNameCache::callback_connection_t LLAvatarNameCache::get(const LLUUID& agent_id, callback_slot_t slot)
{
    return LLAvatarNameCache::getInstance()->getNameCallback(agent_id, slot);
}

LLAvatarNameCache::callback_connection_t LLAvatarNameCache::getNameCallback(const LLUUID& agent_id, callback_slot_t slot)
{
	callback_connection_t connection;

	if (mRunning)
	{
		// ...only do immediate lookups when cache is running
		std::map<LLUUID,LLAvatarName>::iterator it = mCache.find(agent_id);
		if (it != mCache.end())
		{
			LLAvatarName& av_name = it->second;
			LLSD test = av_name.asLLSD();

			// <FS> Contact sets alias
			bool dn_removed;
			std::string pseudonym;
			if (mCustomNameCheckCallback && mCustomNameCheckCallback(agent_id, dn_removed, pseudonym))
			{
				LLSD info = av_name.asLLSD();
				info["is_display_name_default"] = dn_removed;
				info["display_name"] = dn_removed
					? (info["legacy_first_name"].asString() + " " + info["legacy_last_name"].asString())
					: pseudonym;
				av_name.fromLLSD(info);
			}
			// </FS> Contact sets alias

			if (av_name.mExpires > LLFrameTimer::getTotalSeconds())
			{
				// ...name already exists in cache, fire callback now
				fireSignal(agent_id, slot, av_name);
				return connection;
			}
		}
	}

	// schedule a request
	if (!isRequestPending(agent_id))
	{
		mAskQueue.insert(agent_id);
	}

	// always store additional callback, even if request is pending
	signal_map_t::iterator sig_it = mSignalMap.find(agent_id);
	if (sig_it == mSignalMap.end())
	{
		// ...new callback for this id
		callback_signal_t* signal = new callback_signal_t();
		connection = signal->connect(slot);
		mSignalMap[agent_id] = signal;
	}
	else
	{
		// ...existing callback, bind additional slot
		callback_signal_t* signal = sig_it->second;
		connection = signal->connect(slot);
	}

	return connection;
}

// [RLVa:KB] - Checked: 2010-12-08 (RLVa-1.4.0a) | Added: RLVa-1.2.2c
bool LLAvatarNameCache::getForceDisplayNames()
{
	return mRlvForceDisplayNames;
}

void LLAvatarNameCache::setForceDisplayNames(bool force)
{
	mRlvForceDisplayNames = force;
	if ( (!LLAvatarName::useDisplayNames()) && (force) )
	{
		LLAvatarName::setUseDisplayNames(true);
	}
}
// [/RLVa:KB]

void LLAvatarNameCache::setUseDisplayNames(bool use)
{
// [RLVa:KB] - Checked: 2010-12-08 (RLVa-1.4.0a) | Added: RLVa-1.2.2c
	// We need to force the use of the "display names" cache when @shownames=n restricted (and disallow toggling it)
	use |= getForceDisplayNames();
// [/RLVa:KB]
	if (use != LLAvatarName::useDisplayNames())

	{
		LLAvatarName::setUseDisplayNames(use);
		mUseDisplayNamesSignal();
	}
}

void LLAvatarNameCache::setUseUsernames(bool use)
{
	if (use != LLAvatarName::useUsernames())
	{
		LLAvatarName::setUseUsernames(use);
		mUseDisplayNamesSignal();
	}
}

void LLAvatarNameCache::erase(const LLUUID& agent_id)
{
	mCache.erase(agent_id);
}

void LLAvatarNameCache::fetch(const LLUUID& agent_id) // FS:TM used in LGGContactSets
{
	// re-request, even if request is already pending
	mAskQueue.insert(agent_id);
}

void LLAvatarNameCache::insert(const LLUUID& agent_id, const LLAvatarName& av_name)
{
	// *TODO: update timestamp if zero?
	mCache[agent_id] = av_name;
}

LLUUID LLAvatarNameCache::findIdByName(const std::string& name)
{
    std::map<LLUUID, LLAvatarName>::iterator it;
    std::map<LLUUID, LLAvatarName>::iterator end = mCache.end();
    for (it = mCache.begin(); it != end; ++it)
    {
        if (it->second.getUserName() == name)
        {
            return it->first;
        }
    }

    // Legacy method
    LLUUID id;
    if (gCacheName && gCacheName->getUUID(name, id))
    {
        return id;
    }

    return LLUUID::null;
}

#if 0
F64 LLAvatarNameCache::nameExpirationFromHeaders(LLCore::HttpHeaders *headers)
{
    F64 expires = 0.0;
    if (expirationFromCacheControl(headers, &expires))
    {
        return expires;
    }
    else
    {
        // With no expiration info, default to an hour
        const F64 DEFAULT_EXPIRES = 60.0 * 60.0;
        F64 now = LLFrameTimer::getTotalSeconds();
        return now + DEFAULT_EXPIRES;
    }
}

bool LLAvatarNameCache::expirationFromCacheControl(LLCore::HttpHeaders *headers, F64 *expires)
{
    bool fromCacheControl = false;
    F64 now = LLFrameTimer::getTotalSeconds();

    // Allow the header to override the default
    const std::string *cache_control;
    
    cache_control = headers->find(HTTP_IN_HEADER_CACHE_CONTROL);

    if (cache_control && !cache_control->empty())
    {
        S32 max_age = 0;
        if (max_age_from_cache_control(*cache_control, &max_age))
        {
            *expires = now + (F64)max_age;
            fromCacheControl = true;
        }
    }
    LL_DEBUGS("AvNameCache")
        << ( fromCacheControl ? "expires based on cache control " : "default expiration " )
        << "in " << *expires - now << " seconds"
        << LL_ENDL;

    return fromCacheControl;
}
#else
F64 LLAvatarNameCache::nameExpirationFromHeaders(const LLSD& headers)
{
	// <FS:Ansariel> Optional legacy name cache expiration in case SL can't handle us... ;)
	//F64 expires = 0.0;
	//if (expirationFromCacheControl(headers, &expires))
	//{
	//	return expires;
	//}
	//else
	//{
	//	// With no expiration info, default to an hour
	//	const F64 DEFAULT_EXPIRES = 60.0 * 60.0;
	//	F64 now = LLFrameTimer::getTotalSeconds();
	//	return now + DEFAULT_EXPIRES;
	//}

	static LLCachedControl<bool> use_legacy_namecache_expiration(*LLControlGroup::getInstance("Global"), "FSLegacyNameCacheExpiration");
	if (!use_legacy_namecache_expiration)
	{
		F64 expires = 0.0;
		if (expirationFromCacheControl(headers, &expires))
		{
			return expires;
		}
		else
		{
			// With no expiration info, default to an hour
			const F64 DEFAULT_EXPIRES = 60.0 * 60.0;
			F64 now = LLFrameTimer::getTotalSeconds();
			return now + DEFAULT_EXPIRES;
		}
	}
	else
	{
		const F64 DEFAULT_EXPIRES = 60.0 * 60.0 + LLFrameTimer::getTotalSeconds();
	
		F64 expires = 0.0;
		if (expirationFromCacheControl(headers, &expires))
		{
			//AO make sure cache expiration is at least 1HR
			if (expires < DEFAULT_EXPIRES) {expires = DEFAULT_EXPIRES;}
			return expires;
		}
		else
		{
			return DEFAULT_EXPIRES;
		}
	}
	// </FS:Ansariel>
}

bool LLAvatarNameCache::expirationFromCacheControl(const LLSD& headers, F64 *expires)
{
	bool fromCacheControl = false;
	F64 now = LLFrameTimer::getTotalSeconds();

	// Allow the header to override the default
	std::string cache_control;
	if (headers.has(HTTP_IN_HEADER_CACHE_CONTROL))
	{
		cache_control = headers[HTTP_IN_HEADER_CACHE_CONTROL].asString();
	}

	if (!cache_control.empty())
	{
		S32 max_age = 0;
		if (max_age_from_cache_control(cache_control, &max_age))
		{
			*expires = now + (F64)max_age;
			fromCacheControl = true;
		}
	}
	LL_DEBUGS("AvNameCache") << "LLAvatarNameCache "
		<< ( fromCacheControl ? "expires based on cache control " : "default expiration " )
		<< "in " << *expires - now << " seconds"
		<< LL_ENDL;
	
	return fromCacheControl;
}
#endif

void LLAvatarNameCache::addUseDisplayNamesCallback(const use_display_name_signal_t::slot_type& cb) 
{ 
	mUseDisplayNamesSignal.connect(cb); 
}


static const std::string MAX_AGE("max-age");
static const boost::char_separator<char> EQUALS_SEPARATOR("=");
static const boost::char_separator<char> COMMA_SEPARATOR(",");

bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age)
{
	// Split the string on "," to get a list of directives
	typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
	tokenizer directives(cache_control, COMMA_SEPARATOR);

	tokenizer::iterator token_it = directives.begin();
	for ( ; token_it != directives.end(); ++token_it)
	{
		// Tokens may have leading or trailing whitespace
		std::string token = *token_it;
		LLStringUtil::trim(token);

		if (token.compare(0, MAX_AGE.size(), MAX_AGE) == 0)
		{
			// ...this token starts with max-age, so let's chop it up by "="
			tokenizer subtokens(token, EQUALS_SEPARATOR);
			tokenizer::iterator subtoken_it = subtokens.begin();

			// Must have a token
			if (subtoken_it == subtokens.end()) return false;
			std::string subtoken = *subtoken_it;

			// Must exactly equal "max-age"
			LLStringUtil::trim(subtoken);
			if (subtoken != MAX_AGE) return false;

			// Must have another token
			++subtoken_it;
			if (subtoken_it == subtokens.end()) return false;
			subtoken = *subtoken_it;

			// Must be a valid integer
			// *NOTE: atoi() returns 0 for invalid values, so we have to
			// check the string first.
			// *TODO: Do servers ever send "0000" for zero?  We don't handle it
			LLStringUtil::trim(subtoken);
			if (subtoken == "0")
			{
				*max_age = 0;
				return true;
			}
			S32 val = atoi( subtoken.c_str() );
			if (val > 0 && val < S32_MAX)
			{
				*max_age = val;
				return true;
			}
			return false;
		}
	}
	return false;
}

