/**
 * @file llspeakers.cpp
 * @brief Management interface for muting and controlling volume of residents currently speaking
 *
 * $LicenseInfo:firstyear=2005&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 "llviewerprecompiledheaders.h"

#include "llspeakers.h"

#include "llagent.h"
#include "llavatarnamecache.h"
#include "llappviewer.h"
#include "llimview.h"
#include "llgroupmgr.h"
#include "llsdutil.h"
#include "lluicolortable.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
#include "llvoavatar.h"
#include "llworld.h"
#include "llcorehttputil.h"

extern LLControlGroup gSavedSettings;

const LLColor4 INACTIVE_COLOR(0.3f, 0.3f, 0.3f, 0.5f);
const LLColor4 ACTIVE_COLOR(0.5f, 0.5f, 0.5f, 1.f);

LLSpeaker::LLSpeaker(const LLUUID& id, const std::string& name, const ESpeakerType type) :
    mStatus(LLSpeaker::STATUS_TEXT_ONLY),
    mLastSpokeTime(0.f),
    mSpeechVolume(0.f),
    mHasSpoken(FALSE),
    mHasLeftCurrentCall(FALSE),
    mDotColor(LLColor4::white),
    mID(id),
    mTyping(FALSE),
    mSortIndex(0),
    mType(type),
    mIsModerator(FALSE),
    mModeratorMutedVoice(FALSE),
    mModeratorMutedText(FALSE)
{
    if (name.empty() && type == SPEAKER_AGENT)
    {
        lookupName();
    }
    else
    {
        mDisplayName = name;
    }
}


void LLSpeaker::lookupName()
{
    if (mDisplayName.empty())
    {
        LLAvatarNameCache::get(mID, boost::bind(&LLSpeaker::onNameCache, this, _1, _2)); // todo: can be group???
    }
}

void LLSpeaker::onNameCache(const LLUUID& id, const LLAvatarName& av_name)
{
    mDisplayName = av_name.getUserName();
}

bool LLSpeaker::isInVoiceChannel()
{
    return mStatus <= LLSpeaker::STATUS_VOICE_ACTIVE || mStatus == LLSpeaker::STATUS_MUTED;
}

LLSpeakerUpdateSpeakerEvent::LLSpeakerUpdateSpeakerEvent(LLSpeaker* source)
: LLEvent(source, "Speaker update speaker event"),
  mSpeakerID (source->mID)
{
}

LLSD LLSpeakerUpdateSpeakerEvent::getValue()
{
    LLSD ret;
    ret["id"] = mSpeakerID;
    return ret;
}

LLSpeakerUpdateModeratorEvent::LLSpeakerUpdateModeratorEvent(LLSpeaker* source)
: LLEvent(source, "Speaker add moderator event"),
  mSpeakerID (source->mID),
  mIsModerator (source->mIsModerator)
{
}

LLSD LLSpeakerUpdateModeratorEvent::getValue()
{
    LLSD ret;
    ret["id"] = mSpeakerID;
    ret["is_moderator"] = mIsModerator;
    return ret;
}

LLSpeakerTextModerationEvent::LLSpeakerTextModerationEvent(LLSpeaker* source)
: LLEvent(source, "Speaker text moderation event")
{
}

LLSD LLSpeakerTextModerationEvent::getValue()
{
    return std::string("text");
}


LLSpeakerVoiceModerationEvent::LLSpeakerVoiceModerationEvent(LLSpeaker* source)
: LLEvent(source, "Speaker voice moderation event")
{
}

LLSD LLSpeakerVoiceModerationEvent::getValue()
{
    return std::string("voice");
}

LLSpeakerListChangeEvent::LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id)
: LLEvent(source, "Speaker added/removed from speaker mgr"),
  mSpeakerID(speaker_id)
{
}

LLSD LLSpeakerListChangeEvent::getValue()
{
    return mSpeakerID;
}

// helper sort class
struct LLSortRecentSpeakers
{
    bool operator()(const LLPointer<LLSpeaker> lhs, const LLPointer<LLSpeaker> rhs) const;
};

bool LLSortRecentSpeakers::operator()(const LLPointer<LLSpeaker> lhs, const LLPointer<LLSpeaker> rhs) const
{
    // Sort first on status
    if (lhs->mStatus != rhs->mStatus)
    {
        return (lhs->mStatus < rhs->mStatus);
    }

    // and then on last speaking time
    if(lhs->mLastSpokeTime != rhs->mLastSpokeTime)
    {
        return (lhs->mLastSpokeTime > rhs->mLastSpokeTime);
    }

    // and finally (only if those are both equal), on name.
    return( lhs->mDisplayName.compare(rhs->mDisplayName) < 0 );
}

LLSpeakerActionTimer::LLSpeakerActionTimer(action_callback_t action_cb, F32 action_period, const LLUUID& speaker_id)
: LLEventTimer(action_period)
, mActionCallback(action_cb)
, mSpeakerId(speaker_id)
{
}

BOOL LLSpeakerActionTimer::tick()
{
    if (mActionCallback)
    {
        return (BOOL)mActionCallback(mSpeakerId);
    }
    return TRUE;
}

void LLSpeakerActionTimer::unset()
{
    mActionCallback = 0;
}

LLSpeakersDelayActionsStorage::LLSpeakersDelayActionsStorage(LLSpeakerActionTimer::action_callback_t action_cb, F32 action_delay)
: mActionCallback(action_cb)
, mActionDelay(action_delay)
{
}

LLSpeakersDelayActionsStorage::~LLSpeakersDelayActionsStorage()
{
    removeAllTimers();
}

void LLSpeakersDelayActionsStorage::setActionTimer(const LLUUID& speaker_id)
{
    bool not_found = true;
    if (mActionTimersMap.size() > 0)
    {
        not_found = mActionTimersMap.find(speaker_id) == mActionTimersMap.end();
    }

    // If there is already a started timer for the passed UUID don't do anything.
    if (not_found)
    {
        // Starting a timer to remove an participant after delay is completed
        mActionTimersMap.insert(LLSpeakerActionTimer::action_value_t(speaker_id,
            new LLSpeakerActionTimer(
                boost::bind(&LLSpeakersDelayActionsStorage::onTimerActionCallback, this, _1),
                mActionDelay, speaker_id)));
    }
}

void LLSpeakersDelayActionsStorage::unsetActionTimer(const LLUUID& speaker_id)
{
    if (mActionTimersMap.size() == 0) return;

    LLSpeakerActionTimer::action_timer_iter_t it_speaker = mActionTimersMap.find(speaker_id);

    if (it_speaker != mActionTimersMap.end())
    {
        it_speaker->second->unset();
        mActionTimersMap.erase(it_speaker);
    }
}

void LLSpeakersDelayActionsStorage::removeAllTimers()
{
    LLSpeakerActionTimer::action_timer_iter_t iter = mActionTimersMap.begin();
    for (; iter != mActionTimersMap.end(); ++iter)
    {
        delete iter->second;
    }
    mActionTimersMap.clear();
}

bool LLSpeakersDelayActionsStorage::onTimerActionCallback(const LLUUID& speaker_id)
{
    unsetActionTimer(speaker_id);

    if (mActionCallback)
    {
        mActionCallback(speaker_id);
    }

    return true;
}

bool LLSpeakersDelayActionsStorage::isTimerStarted(const LLUUID& speaker_id)
{
    return (mActionTimersMap.size() > 0) && (mActionTimersMap.find(speaker_id) != mActionTimersMap.end());
}

//
// LLSpeakerMgr
//

LLSpeakerMgr::LLSpeakerMgr(LLVoiceChannel* channelp) :
    mVoiceChannel(channelp),
    mVoiceModerated(false),
    mModerateModeHandledFirstTime(false),
    mSpeakerListUpdated(false)
{
    mGetListTime.reset();
    static LLUICachedControl<F32> remove_delay ("SpeakerParticipantRemoveDelay", 10.0);

    mSpeakerDelayRemover = new LLSpeakersDelayActionsStorage(boost::bind(&LLSpeakerMgr::removeSpeaker, this, _1), remove_delay);
}

LLSpeakerMgr::~LLSpeakerMgr()
{
    delete mSpeakerDelayRemover;
}

LLPointer<LLSpeaker> LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::string& name, LLSpeaker::ESpeakerStatus status, LLSpeaker::ESpeakerType type)
{
    if (!mVoiceChannel)
    {
        return NULL;
    }
    LLUUID session_id = getSessionID();
    if (id.isNull() || (id == session_id))
    {
        return NULL;
    }

    LLPointer<LLSpeaker> speakerp;
    if (mSpeakers.find(id) == mSpeakers.end())
    {
        speakerp = new LLSpeaker(id, name, type);
        speakerp->mStatus = status;
        mSpeakers.insert(std::make_pair(speakerp->mID, speakerp));
        mSpeakersSorted.push_back(speakerp);
        LL_DEBUGS("Speakers") << "Added speaker " << id << LL_ENDL;
        fireEvent(new LLSpeakerListChangeEvent(this, speakerp->mID), "add");
    }
    else
    {
        speakerp = findSpeaker(id);
        if (speakerp.notNull())
        {
            // keep highest priority status (lowest value) instead of overriding current value
            speakerp->mStatus = llmin(speakerp->mStatus, status);
            // RN: due to a weird behavior where IMs from attached objects come from the wearer's agent_id
            // we need to override speakers that we think are objects when we find out they are really
            // residents
            if (type == LLSpeaker::SPEAKER_AGENT)
            {
                speakerp->mType = LLSpeaker::SPEAKER_AGENT;
                speakerp->lookupName();
            }
        }
        else
        {
            LL_WARNS("Speakers") << "Speaker " << id << " not found" << LL_ENDL;
        }
    }

    mSpeakerDelayRemover->unsetActionTimer(speakerp->mID);
    return speakerp;
}

// *TODO: Once way to request the current voice channel moderation mode is implemented
// this method with related code should be removed.
/*
 Initializes "moderate_mode" of voice session on first join.

 This is WORKAROUND because a way to request the current voice channel moderation mode exists
 but is not implemented in viewer yet. See EXT-6937.
*/
void LLSpeakerMgr::initVoiceModerateMode()
{
    if (!mModerateModeHandledFirstTime && (mVoiceChannel && mVoiceChannel->isActive()))
    {
        LLPointer<LLSpeaker> speakerp;

        if (mSpeakers.find(gAgentID) != mSpeakers.end())
        {
            speakerp = mSpeakers[gAgentID];
        }

        if (speakerp.notNull())
        {
            mVoiceModerated = speakerp->mModeratorMutedVoice;
            mModerateModeHandledFirstTime = true;
        }
    }
}

void LLSpeakerMgr::update(BOOL resort_ok)
{
    if (!LLVoiceClient::getInstance())
    {
        return;
    }

    LLColor4 speaking_color = LLUIColorTable::instance().getColor("SpeakingColor");
    LLColor4 overdriven_color = LLUIColorTable::instance().getColor("OverdrivenColor");

    if(resort_ok) // only allow list changes when user is not interacting with it
    {
        updateSpeakerList();
    }

    // update status of all current speakers
    BOOL voice_channel_active = (!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive());
    for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); speaker_it++)
    {
        LLUUID speaker_id = speaker_it->first;
        LLSpeaker* speakerp = speaker_it->second;

        if (voice_channel_active && LLVoiceClient::getInstance()->getVoiceEnabled(speaker_id))
        {
            speakerp->mSpeechVolume = LLVoiceClient::getInstance()->getCurrentPower(speaker_id);
            BOOL moderator_muted_voice = LLVoiceClient::getInstance()->getIsModeratorMuted(speaker_id);
            if (moderator_muted_voice != speakerp->mModeratorMutedVoice)
            {
                speakerp->mModeratorMutedVoice = moderator_muted_voice;
                LL_DEBUGS("Speakers") << (speakerp->mModeratorMutedVoice? "Muted" : "Umuted") << " speaker " << speaker_id<< LL_ENDL;
                speakerp->fireEvent(new LLSpeakerVoiceModerationEvent(speakerp));
            }

            if (LLVoiceClient::getInstance()->getOnMuteList(speaker_id) || speakerp->mModeratorMutedVoice)
            {
                speakerp->mStatus = LLSpeaker::STATUS_MUTED;
            }
            else if (LLVoiceClient::getInstance()->getIsSpeaking(speaker_id))
            {
                // reset inactivity expiration
                if (speakerp->mStatus != LLSpeaker::STATUS_SPEAKING)
                {
                    speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32();
                    speakerp->mHasSpoken = TRUE;
                    fireEvent(new LLSpeakerUpdateSpeakerEvent(speakerp), "update_speaker");
                }
                speakerp->mStatus = LLSpeaker::STATUS_SPEAKING;
                // interpolate between active color and full speaking color based on power of speech output
                speakerp->mDotColor = speaking_color;
                if (speakerp->mSpeechVolume > LLVoiceClient::OVERDRIVEN_POWER_LEVEL)
                {
                    speakerp->mDotColor = overdriven_color;
                }
            }
            else
            {
                speakerp->mSpeechVolume = 0.f;
                speakerp->mDotColor = ACTIVE_COLOR;

                if (speakerp->mHasSpoken)
                {
                    // have spoken once, not currently speaking
                    speakerp->mStatus = LLSpeaker::STATUS_HAS_SPOKEN;
                }
                else
                {
                    // default state for being in voice channel
                    speakerp->mStatus = LLSpeaker::STATUS_VOICE_ACTIVE;
                }
            }
        }
        // speaker no longer registered in voice channel, demote to text only
        else if (speakerp->mStatus != LLSpeaker::STATUS_NOT_IN_CHANNEL)
        {
            if(speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL)
            {
                // external speakers should be timed out when they leave the voice channel (since they only exist via SLVoice)
                speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL;
            }
            else
            {
                speakerp->mStatus = LLSpeaker::STATUS_TEXT_ONLY;
                speakerp->mSpeechVolume = 0.f;
                speakerp->mDotColor = ACTIVE_COLOR;
            }
        }
    }

    if(resort_ok)  // only allow list changes when user is not interacting with it
    {
        // sort by status then time last spoken
        std::sort(mSpeakersSorted.begin(), mSpeakersSorted.end(), LLSortRecentSpeakers());
    }

    // for recent speakers who are not currently speaking, show "recent" color dot for most recent
    // fading to "active" color

    S32 recent_speaker_count = 0;
    S32 sort_index = 0;
    speaker_list_t::iterator sorted_speaker_it;
    for(sorted_speaker_it = mSpeakersSorted.begin();
        sorted_speaker_it != mSpeakersSorted.end(); ++sorted_speaker_it)
    {
        LLPointer<LLSpeaker> speakerp = *sorted_speaker_it;

        // color code recent speakers who are not currently speaking
        if (speakerp->mStatus == LLSpeaker::STATUS_HAS_SPOKEN)
        {
            speakerp->mDotColor = lerp(speaking_color, ACTIVE_COLOR, clamp_rescale((F32)recent_speaker_count, -2.f, 3.f, 0.f, 1.f));
            recent_speaker_count++;
        }

        // stuff sort ordinal into speaker so the ui can sort by this value
        speakerp->mSortIndex = sort_index++;
    }
}

void LLSpeakerMgr::updateSpeakerList()
{
    // Are we bound to the currently active voice channel?
    if ((!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive()))
    {
        std::set<LLUUID> participants;
        LLVoiceClient::getInstance()->getParticipantList(participants);
        // If we are, add all voice client participants to our list of known speakers
        for (std::set<LLUUID>::iterator participant_it = participants.begin(); participant_it != participants.end(); ++participant_it)
        {
                setSpeaker(*participant_it,
                           LLVoiceClient::getInstance()->getDisplayName(*participant_it),
                           LLSpeaker::STATUS_VOICE_ACTIVE,
                           (LLVoiceClient::getInstance()->isParticipantAvatar(*participant_it)?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL));
        }
    }
    else if (mVoiceChannel)
    {
        // If not, check if the list is empty, except if it's Nearby Chat (session_id NULL).
        LLUUID session_id = getSessionID();
        if (!session_id.isNull() && !mSpeakerListUpdated)
        {
            // If the list is empty, we update it with whatever we have locally so that it doesn't stay empty too long.
            // *TODO: Fix the server side code that sometimes forgets to send back the list of participants after a chat started.
            // (IOW, fix why we get no ChatterBoxSessionAgentListUpdates message after the initial ChatterBoxSessionStartReply)
            LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(session_id);
            if (session->isGroupSessionType() && (mSpeakers.size() <= 1))
            {
                // For groups, we need to hit the group manager.
                // Note: The session uuid and the group uuid are actually one and the same. If that was to change, this will fail.
                LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(session_id);

                if (gdatap && gdatap->isMemberDataComplete() && !gdatap->mMembers.empty())
                {
                    // Add group members when we get the complete list (note: can take a while before we get that list)
                    LLGroupMgrGroupData::member_list_t::iterator member_it = gdatap->mMembers.begin();
                    const S32 load_group_max_members = gSavedSettings.getS32("ChatLoadGroupMaxMembers");
                    S32 updated = 0;
                    while (member_it != gdatap->mMembers.end())
                    {
                        LLGroupMemberData* member = member_it->second;
                        LLUUID id = member_it->first;
                        // Add only members who are online and not already in the list
                        if ((member->getOnlineStatus() == "Online") && (mSpeakers.find(id) == mSpeakers.end()))
                        {
                            LLPointer<LLSpeaker> speakerp = setSpeaker(id, "", LLSpeaker::STATUS_VOICE_ACTIVE, LLSpeaker::SPEAKER_AGENT);
                            speakerp->mIsModerator = ((member->getAgentPowers() & GP_SESSION_MODERATOR) == GP_SESSION_MODERATOR);
                            updated++;
                        }
                        ++member_it;
                        // Limit the number of "manually updated" participants to a reasonable number to avoid severe fps drop
                        // *TODO : solve the perf issue of having several hundreds of widgets in the conversation list
                        if (updated >= load_group_max_members)
                            break;
                    }
                    mSpeakerListUpdated = true;
                }
            }
            else if (mSpeakers.size() == 0)
            {
                // For all other session type (ad-hoc, P2P), we use the initial participants targets list
                for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin();it!=session->mInitialTargetIDs.end();++it)
                {
                    // Add buddies if they are on line, add any other avatar.
                    if (!LLAvatarTracker::instance().isBuddy(*it) || LLAvatarTracker::instance().isBuddyOnline(*it))
                    {
                        setSpeaker(*it, "", LLSpeaker::STATUS_VOICE_ACTIVE, LLSpeaker::SPEAKER_AGENT);
                    }
                }
                mSpeakerListUpdated = true;
            }
            else
            {
                // The list has been updated the normal way (i.e. by a ChatterBoxSessionAgentListUpdates received from the server)
                mSpeakerListUpdated = true;
            }
        }
    }
    // Always add the current agent (it has to be there...). Will do nothing if already there.
    setSpeaker(gAgentID, "", LLSpeaker::STATUS_VOICE_ACTIVE, LLSpeaker::SPEAKER_AGENT);
}

void LLSpeakerMgr::setSpeakerNotInChannel(LLPointer<LLSpeaker> speakerp)
{
    if  (speakerp.notNull())
    {
        speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL;
        speakerp->mDotColor = INACTIVE_COLOR;
        mSpeakerDelayRemover->setActionTimer(speakerp->mID);
    }
}

bool LLSpeakerMgr::removeSpeaker(const LLUUID& speaker_id)
{
    mSpeakers.erase(speaker_id);

    speaker_list_t::iterator sorted_speaker_it = mSpeakersSorted.begin();

    for(; sorted_speaker_it != mSpeakersSorted.end(); ++sorted_speaker_it)
    {
        if (speaker_id == (*sorted_speaker_it)->mID)
        {
            mSpeakersSorted.erase(sorted_speaker_it);
            break;
        }
    }

    LL_DEBUGS("Speakers") << "Removed speaker " << speaker_id << LL_ENDL;
    fireEvent(new LLSpeakerListChangeEvent(this, speaker_id), "remove");

    update(TRUE);

    return false;
}

LLPointer<LLSpeaker> LLSpeakerMgr::findSpeaker(const LLUUID& speaker_id)
{
    //In some conditions map causes crash if it is empty(Windows only), adding check (EK)
    if (mSpeakers.size() == 0)
        return NULL;
    speaker_map_t::iterator found_it = mSpeakers.find(speaker_id);
    if (found_it == mSpeakers.end())
    {
        return NULL;
    }
    return found_it->second;
}

void LLSpeakerMgr::getSpeakerList(speaker_list_t* speaker_list, BOOL include_text)
{
    speaker_list->clear();
    for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it)
    {
        LLPointer<LLSpeaker> speakerp = speaker_it->second;
        // what about text only muted or inactive?
        if (include_text || speakerp->mStatus != LLSpeaker::STATUS_TEXT_ONLY)
        {
            speaker_list->push_back(speakerp);
        }
    }
}

const LLUUID LLSpeakerMgr::getSessionID()
{
    return mVoiceChannel ? mVoiceChannel->getSessionID() : LLUUID();
}

bool LLSpeakerMgr::isSpeakerToBeRemoved(const LLUUID& speaker_id)
{
    return mSpeakerDelayRemover && mSpeakerDelayRemover->isTimerStarted(speaker_id);
}

void LLSpeakerMgr::setSpeakerTyping(const LLUUID& speaker_id, BOOL typing)
{
    LLPointer<LLSpeaker> speakerp = findSpeaker(speaker_id);
    if (speakerp.notNull())
    {
        speakerp->mTyping = typing;
    }
}

// speaker has chatted via either text or voice
void LLSpeakerMgr::speakerChatted(const LLUUID& speaker_id)
{
    LLPointer<LLSpeaker> speakerp = findSpeaker(speaker_id);
    if (speakerp.notNull())
    {
        speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32();
        speakerp->mHasSpoken = TRUE;
        fireEvent(new LLSpeakerUpdateSpeakerEvent(speakerp), "update_speaker");
    }
}

BOOL LLSpeakerMgr::isVoiceActive()
{
    // mVoiceChannel = NULL means current voice channel, whatever it is
    return LLVoiceClient::getInstance()->voiceEnabled() && mVoiceChannel && mVoiceChannel->isActive();
}


//
// LLIMSpeakerMgr
//
LLIMSpeakerMgr::LLIMSpeakerMgr(LLVoiceChannel* channel) : LLSpeakerMgr(channel)
{
}

void LLIMSpeakerMgr::updateSpeakerList()
{
    // don't do normal updates which are pulled from voice channel
    // rely on user list reported by sim

    // We need to do this to allow PSTN callers into group chats to show in the list.
    LLSpeakerMgr::updateSpeakerList();

    return;
}

void LLIMSpeakerMgr::setSpeakers(const LLSD& speakers)
{
    if ( !speakers.isMap() ) return;

    if ( speakers.has("agent_info") && speakers["agent_info"].isMap() )
    {
        LLSD::map_const_iterator speaker_it;
        for(speaker_it = speakers["agent_info"].beginMap();
            speaker_it != speakers["agent_info"].endMap();
            ++speaker_it)
        {
            LLUUID agent_id(speaker_it->first);

            LLPointer<LLSpeaker> speakerp = setSpeaker(
                agent_id,
                LLStringUtil::null,
                LLSpeaker::STATUS_TEXT_ONLY);

            if ( speaker_it->second.isMap() )
            {
                BOOL is_moderator = speakerp->mIsModerator;
                speakerp->mIsModerator = speaker_it->second["is_moderator"];
                speakerp->mModeratorMutedText =
                    speaker_it->second["mutes"]["text"];
                // Fire event only if moderator changed
                if ( is_moderator != speakerp->mIsModerator )
                {
                    LL_DEBUGS("Speakers") << "Speaker " << agent_id << (is_moderator ? "is now" : "no longer is") << " a moderator" << LL_ENDL;
                    fireEvent(new LLSpeakerUpdateModeratorEvent(speakerp), "update_moderator");
                }
            }
        }
    }
    else if ( speakers.has("agents" ) && speakers["agents"].isArray() )
    {
        //older, more decprecated way.  Need here for
        //using older version of servers
        LLSD::array_const_iterator speaker_it;
        for(speaker_it = speakers["agents"].beginArray();
            speaker_it != speakers["agents"].endArray();
            ++speaker_it)
        {
            const LLUUID agent_id = (*speaker_it).asUUID();

            LLPointer<LLSpeaker> speakerp = setSpeaker(
                agent_id,
                LLStringUtil::null,
                LLSpeaker::STATUS_TEXT_ONLY);
        }
    }
}

void LLIMSpeakerMgr::updateSpeakers(const LLSD& update)
{
    if ( !update.isMap() ) return;

    if ( update.has("agent_updates") && update["agent_updates"].isMap() )
    {
        LLSD::map_const_iterator update_it;
        for(
            update_it = update["agent_updates"].beginMap();
            update_it != update["agent_updates"].endMap();
            ++update_it)
        {
            LLUUID agent_id(update_it->first);
            LLPointer<LLSpeaker> speakerp = findSpeaker(agent_id);

            LLSD agent_data = update_it->second;

            if (agent_data.isMap() && agent_data.has("transition"))
            {
                if (agent_data["transition"].asString() == "LEAVE")
                {
                    setSpeakerNotInChannel(speakerp);
                }
                else if (agent_data["transition"].asString() == "ENTER")
                {
                    // add or update speaker
                    speakerp = setSpeaker(agent_id);
                }
                else
                {
                    LL_WARNS() << "bad membership list update from 'agent_updates' for agent " << agent_id << ", transition " << ll_print_sd(agent_data["transition"]) << LL_ENDL;
                }
            }

            if (speakerp.isNull()) continue;

            // should have a valid speaker from this point on
            if (agent_data.isMap() && agent_data.has("info"))
            {
                LLSD agent_info = agent_data["info"];

                if (agent_info.has("is_moderator"))
                {
                    BOOL is_moderator = speakerp->mIsModerator;
                    speakerp->mIsModerator = agent_info["is_moderator"];
                    // Fire event only if moderator changed
                    if ( is_moderator != speakerp->mIsModerator )
                    {
                        LL_DEBUGS("Speakers") << "Speaker " << agent_id << (is_moderator ? "is now" : "no longer is") << " a moderator" << LL_ENDL;
                        fireEvent(new LLSpeakerUpdateModeratorEvent(speakerp), "update_moderator");
                    }
                }

                if (agent_info.has("mutes"))
                {
                    speakerp->mModeratorMutedText = agent_info["mutes"]["text"];
                }
            }
        }
    }
    else if ( update.has("updates") && update["updates"].isMap() )
    {
        LLSD::map_const_iterator update_it;
        for (
            update_it = update["updates"].beginMap();
            update_it != update["updates"].endMap();
            ++update_it)
        {
            LLUUID agent_id(update_it->first);
            LLPointer<LLSpeaker> speakerp = findSpeaker(agent_id);

            std::string agent_transition = update_it->second.asString();
            if (agent_transition == "LEAVE")
            {
                setSpeakerNotInChannel(speakerp);
            }
            else if ( agent_transition == "ENTER")
            {
                // add or update speaker
                speakerp = setSpeaker(agent_id);
            }
            else
            {
                LL_WARNS() << "bad membership list update from 'updates' for agent " << agent_id << ", transition " << agent_transition << LL_ENDL;
            }
        }
    }
}

void LLIMSpeakerMgr::toggleAllowTextChat(const LLUUID& speaker_id)
{
    LLPointer<LLSpeaker> speakerp = findSpeaker(speaker_id);
    if (!speakerp || !mVoiceChannel) return;

    std::string url = gAgent.getRegionCapability("ChatSessionRequest");
    LLSD data;
    data["method"] = "mute update";
    data["session-id"] = getSessionID();
    data["params"] = LLSD::emptyMap();
    data["params"]["agent_id"] = speaker_id;
    data["params"]["mute_info"] = LLSD::emptyMap();
    //current value represents ability to type, so invert
    data["params"]["mute_info"]["text"] = !speakerp->mModeratorMutedText;

    LLCoros::instance().launch("LLIMSpeakerMgr::moderationActionCoro",
        boost::bind(&LLIMSpeakerMgr::moderationActionCoro, this, url, data));
}

void LLIMSpeakerMgr::moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute)
{
    LLPointer<LLSpeaker> speakerp = findSpeaker(avatar_id);
    if (!speakerp || !mVoiceChannel) return;

    // *NOTE: mantipov: probably this condition will be incorrect when avatar will be blocked for
    // text chat via moderation (LLSpeaker::mModeratorMutedText == TRUE)
    bool is_in_voice = speakerp->mStatus <= LLSpeaker::STATUS_VOICE_ACTIVE || speakerp->mStatus == LLSpeaker::STATUS_MUTED;

    // do not send voice moderation changes for avatars not in voice channel
    if (!is_in_voice) return;

    std::string url = gAgent.getRegionCapability("ChatSessionRequest");
    LLSD data;
    data["method"] = "mute update";
    data["session-id"] = getSessionID();
    data["params"] = LLSD::emptyMap();
    data["params"]["agent_id"] = avatar_id;
    data["params"]["mute_info"] = LLSD::emptyMap();
    data["params"]["mute_info"]["voice"] = !unmute;

    LLCoros::instance().launch("LLIMSpeakerMgr::moderationActionCoro",
        boost::bind(&LLIMSpeakerMgr::moderationActionCoro, this, url, data));
}

void LLIMSpeakerMgr::moderationActionCoro(std::string url, LLSD action)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("moderationActionCoro", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
    LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions);

    httpOpts->setWantHeaders(true);

    LLUUID sessionId = action["session-id"];

    LLSD result = httpAdapter->postAndSuspend(httpRequest, url, action, httpOpts);

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if (!status)
    {
        if (gIMMgr)
        {
            //403 == you're not a mod
            //should be disabled if you're not a moderator
            if (status == LLCore::HttpStatus(HTTP_FORBIDDEN))
            {
                gIMMgr->showSessionEventError(
                    "mute",
                    "not_a_mod_error",
                    sessionId);
            }
            else
            {
                gIMMgr->showSessionEventError(
                    "mute",
                    "generic_request_error",
                    sessionId);
            }
        }
        return;
    }
}

void LLIMSpeakerMgr::moderateVoiceAllParticipants( bool unmute_everyone )
{
    if (mVoiceModerated == !unmute_everyone)
    {
        // session already in requested state. Just force participants which do not match it.
        forceVoiceModeratedMode(mVoiceModerated);
    }
    else
    {
        // otherwise set moderated mode for a whole session.
        moderateVoiceSession(getSessionID(), !unmute_everyone);
    }
}

void LLIMSpeakerMgr::processSessionUpdate(const LLSD& session_update)
{
    if (session_update.has("moderated_mode") &&
        session_update["moderated_mode"].has("voice"))
    {
        mVoiceModerated = session_update["moderated_mode"]["voice"];
    }
}

void LLIMSpeakerMgr::moderateVoiceSession(const LLUUID& session_id, bool disallow_voice)
{
    std::string url = gAgent.getRegionCapability("ChatSessionRequest");
    LLSD data;
    data["method"] = "session update";
    data["session-id"] = session_id;
    data["params"] = LLSD::emptyMap();

    data["params"]["update_info"] = LLSD::emptyMap();

    data["params"]["update_info"]["moderated_mode"] = LLSD::emptyMap();
    data["params"]["update_info"]["moderated_mode"]["voice"] = disallow_voice;

    LLCoros::instance().launch("LLIMSpeakerMgr::moderationActionCoro",
        boost::bind(&LLIMSpeakerMgr::moderationActionCoro, this, url, data));
}

void LLIMSpeakerMgr::forceVoiceModeratedMode(bool should_be_muted)
{
    for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it)
    {
        LLUUID speaker_id = speaker_it->first;
        LLSpeaker* speakerp = speaker_it->second;

        // participant does not match requested state
        if (should_be_muted != (bool)speakerp->mModeratorMutedVoice)
        {
            moderateVoiceParticipant(speaker_id, !should_be_muted);
        }
    }
}

//
// LLActiveSpeakerMgr
//

LLActiveSpeakerMgr::LLActiveSpeakerMgr() : LLSpeakerMgr(NULL)
{
}

void LLActiveSpeakerMgr::updateSpeakerList()
{
    // point to whatever the current voice channel is
    mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel();

    // always populate from active voice channel
    if (LLVoiceChannel::getCurrentVoiceChannel() != mVoiceChannel) //MA: seems this is always false
    {
        LL_DEBUGS("Speakers") << "Removed all speakers" << LL_ENDL;
        fireEvent(new LLSpeakerListChangeEvent(this, LLUUID::null), "clear");
        mSpeakers.clear();
        mSpeakersSorted.clear();
        mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel();
        mSpeakerDelayRemover->removeAllTimers();
    }
    LLSpeakerMgr::updateSpeakerList();

    // clean up text only speakers
    for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it)
    {
        LLSpeaker* speakerp = speaker_it->second;
        if (speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY)
        {
            // automatically flag text only speakers for removal
            speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL;
        }
    }

}



//
// LLLocalSpeakerMgr
//

LLLocalSpeakerMgr::LLLocalSpeakerMgr() : LLSpeakerMgr(LLVoiceChannelProximal::getInstance())
{
}

LLLocalSpeakerMgr::~LLLocalSpeakerMgr ()
{
}

void LLLocalSpeakerMgr::updateSpeakerList()
{
    // pull speakers from voice channel
    LLSpeakerMgr::updateSpeakerList();

    if (gDisconnected)//the world is cleared.
    {
        return ;
    }

    // pick up non-voice speakers in chat range
    uuid_vec_t avatar_ids;
    std::vector<LLVector3d> positions;
    LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), CHAT_NORMAL_RADIUS);
    for(U32 i=0; i<avatar_ids.size(); i++)
    {
        setSpeaker(avatar_ids[i]);
    }

    // check if text only speakers have moved out of chat range
    for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it)
    {
        LLUUID speaker_id = speaker_it->first;
        LLPointer<LLSpeaker> speakerp = speaker_it->second;
        if (speakerp.notNull() && speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY)
        {
            LLVOAvatar* avatarp = (LLVOAvatar*)gObjectList.findObject(speaker_id);
            if (!avatarp || dist_vec_squared(avatarp->getPositionAgent(), gAgent.getPositionAgent()) > CHAT_NORMAL_RADIUS * CHAT_NORMAL_RADIUS)
            {
                setSpeakerNotInChannel(speakerp);
            }
        }
    }
}