/**
 * @file llspeakers.h
 * @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$
 */

#ifndef LL_LLSPEAKERS_H
#define LL_LLSPEAKERS_H

#include "llevent.h"
#include "lleventtimer.h"
#include "llvoicechannel.h"
#include "lleventcoro.h"
#include "llcoros.h"

class LLSpeakerMgr;
class LLAvatarName;

// data for a given participant in a voice channel
class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider<LLSpeaker>, public boost::signals2::trackable
{
public:
    typedef enum e_speaker_type
    {
        SPEAKER_AGENT,
        SPEAKER_OBJECT,
        SPEAKER_EXTERNAL    // Speaker that doesn't map to an avatar or object (i.e. PSTN caller in a group)
    } ESpeakerType;

    typedef enum e_speaker_status
    {
        STATUS_SPEAKING,
        STATUS_HAS_SPOKEN,
        STATUS_VOICE_ACTIVE,
        STATUS_TEXT_ONLY,
        STATUS_NOT_IN_CHANNEL,
        STATUS_MUTED
    } ESpeakerStatus;


    LLSpeaker(const LLUUID& id, const std::string& name = LLStringUtil::null, const ESpeakerType type = SPEAKER_AGENT);
    ~LLSpeaker() {};
    void lookupName();

    void onNameCache(const LLUUID& id, const LLAvatarName& full_name);

    bool isInVoiceChannel();

    ESpeakerStatus  mStatus;            // current activity status in speech group
    F32             mLastSpokeTime;     // timestamp when this speaker last spoke
    F32             mSpeechVolume;      // current speech amplitude (timea average rms amplitude?)
    std::string     mDisplayName;       // cache user name for this speaker
    bool            mHasSpoken;         // has this speaker said anything this session?
    bool            mHasLeftCurrentCall;    // has this speaker left the current voice call?
    LLColor4        mDotColor;
    LLUUID          mID;
    bool            mTyping;
    S32             mSortIndex;
    ESpeakerType    mType;
    bool            mIsModerator;
    bool            mModeratorMutedVoice;
    bool            mModeratorMutedText;
};

class LLSpeakerUpdateSpeakerEvent : public LLOldEvents::LLEvent
{
public:
    LLSpeakerUpdateSpeakerEvent(LLSpeaker* source);
    /*virtual*/ LLSD getValue();
private:
    const LLUUID& mSpeakerID;
};

class LLSpeakerUpdateModeratorEvent : public LLOldEvents::LLEvent
{
public:
    LLSpeakerUpdateModeratorEvent(LLSpeaker* source);
    /*virtual*/ LLSD getValue();
private:
    const LLUUID& mSpeakerID;
    bool mIsModerator;
};

class LLSpeakerTextModerationEvent : public LLOldEvents::LLEvent
{
public:
    LLSpeakerTextModerationEvent(LLSpeaker* source);
    /*virtual*/ LLSD getValue();
};

class LLSpeakerVoiceModerationEvent : public LLOldEvents::LLEvent
{
public:
    LLSpeakerVoiceModerationEvent(LLSpeaker* source);
    /*virtual*/ LLSD getValue();
};

class LLSpeakerListChangeEvent : public LLOldEvents::LLEvent
{
public:
    LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id);
    /*virtual*/ LLSD getValue();

private:
    const LLUUID& mSpeakerID;
};

/**
 * class LLSpeakerActionTimer
 *
 * Implements a timer that calls stored callback action for stored speaker after passed period.
 *
 * Action is called until callback returns "true".
 * In this case the timer will be removed via LLEventTimer::updateClass().
 * Otherwise it should be deleted manually in place where it is used.
 * If action callback is not set timer will tick only once and deleted.
 */
class LLSpeakerActionTimer : public LLEventTimer
{
public:
    typedef boost::function<bool(const LLUUID&)>    action_callback_t;
    typedef std::map<LLUUID, LLSpeakerActionTimer*> action_timers_map_t;
    typedef action_timers_map_t::value_type         action_value_t;
    typedef action_timers_map_t::const_iterator     action_timer_const_iter_t;
    typedef action_timers_map_t::iterator           action_timer_iter_t;

    /**
     * Constructor.
     *
     * @param action_cb - callback which will be called each time after passed action period.
     * @param action_period - time in seconds timer should tick.
     * @param speaker_id - LLUUID of speaker which will be passed into action callback.
     */
    LLSpeakerActionTimer(action_callback_t action_cb, F32 action_period, const LLUUID& speaker_id);
    virtual ~LLSpeakerActionTimer() {};

    /**
     * Implements timer "tick".
     *
     * If action callback is not specified returns true. Instance will be deleted by LLEventTimer::updateClass().
     */
    virtual bool tick();

    /**
     * Clears the callback.
     *
     * Use this instead of deleteing this object.
     * The next call to tick() will return true and that will destroy this object.
     */
    void unset();
private:
    action_callback_t   mActionCallback;
    LLUUID              mSpeakerId;
};

/**
 * Represents a functionality to store actions for speakers with delay.
 * Is based on LLSpeakerActionTimer.
 */
class LLSpeakersDelayActionsStorage
{
public:
    LLSpeakersDelayActionsStorage(LLSpeakerActionTimer::action_callback_t action_cb, F32 action_delay);
    ~LLSpeakersDelayActionsStorage();

    /**
     * Sets new LLSpeakerActionTimer with passed speaker UUID.
     */
    void setActionTimer(const LLUUID& speaker_id);

    /**
     * Removes stored LLSpeakerActionTimer for passed speaker UUID from internal map and optionally deletes it.
     *
     * @see onTimerActionCallback()
     */
    void unsetActionTimer(const LLUUID& speaker_id);

    void removeAllTimers();

    bool isTimerStarted(const LLUUID& speaker_id);
private:
    /**
     * Callback of the each instance of LLSpeakerActionTimer.
     *
     * Unsets an appropriate timer instance and calls action callback for specified speacker_id.
     *
     * @see unsetActionTimer()
     */
    bool onTimerActionCallback(const LLUUID& speaker_id);

    LLSpeakerActionTimer::action_timers_map_t   mActionTimersMap;
    LLSpeakerActionTimer::action_callback_t     mActionCallback;

    /**
     * Delay to call action callback for speakers after timer was set.
     */
    F32 mActionDelay;

};


class LLSpeakerMgr : public LLOldEvents::LLObservable
{
    LOG_CLASS(LLSpeakerMgr);

public:
    LLSpeakerMgr(LLVoiceChannel* channelp);
    virtual ~LLSpeakerMgr();

    LLPointer<LLSpeaker> findSpeaker(const LLUUID& avatar_id);
    void update(bool resort_ok);
    void setSpeakerTyping(const LLUUID& speaker_id, bool typing);
    void speakerChatted(const LLUUID& speaker_id);
    LLPointer<LLSpeaker> setSpeaker(const LLUUID& id,
                    const std::string& name = LLStringUtil::null,
                    LLSpeaker::ESpeakerStatus status = LLSpeaker::STATUS_TEXT_ONLY,
                    LLSpeaker::ESpeakerType = LLSpeaker::SPEAKER_AGENT);

    bool isVoiceActive();

    typedef std::vector<LLPointer<LLSpeaker> > speaker_list_t;
    void getSpeakerList(speaker_list_t* speaker_list, bool include_text);
    LLVoiceChannel* getVoiceChannel() { return mVoiceChannel; }
    void setVoiceChannel(LLVoiceChannel *voiceChannel) { mVoiceChannel = voiceChannel;  }
    const LLUUID getSessionID();
    bool isSpeakerToBeRemoved(const LLUUID& speaker_id);

    /**
     * Initializes mVoiceModerated depend on LLSpeaker::mModeratorMutedVoice of agent's participant.
     *
     * Is used only to implement workaround to initialize mVoiceModerated on first join to group chat. See EXT-6937
     */
    void initVoiceModerateMode();

protected:
    virtual void updateSpeakerList();
    void setSpeakerNotInChannel(LLPointer<LLSpeaker> speackerp);
    bool removeSpeaker(const LLUUID& speaker_id);

    typedef std::map<LLUUID, LLPointer<LLSpeaker> > speaker_map_t;
    speaker_map_t       mSpeakers;
    bool                mSpeakerListUpdated;
    LLTimer             mGetListTime;

    speaker_list_t      mSpeakersSorted;
    LLFrameTimer        mSpeechTimer;
    LLVoiceChannel*     mVoiceChannel;

    /**
     * time out speakers when they are not part of current session
     */
    LLSpeakersDelayActionsStorage* mSpeakerDelayRemover;

    // *TODO: should be moved back into LLIMSpeakerMgr when a way to request the current voice channel
    // moderation mode is implemented: See EXT-6937
    bool mVoiceModerated;

    // *TODO: To be removed when a way to request the current voice channel
    // moderation mode is implemented: See EXT-6937
    bool mModerateModeHandledFirstTime;
};

class LLIMSpeakerMgr : public LLSpeakerMgr
{
    LOG_CLASS(LLIMSpeakerMgr);

public:
    LLIMSpeakerMgr(LLVoiceChannel* channel);

    void updateSpeakers(const LLSD& update);
    void setSpeakers(const LLSD& speakers);

    void toggleAllowTextChat(const LLUUID& speaker_id);

    /**
     * Mutes/Unmutes avatar for current group voice chat.
     *
     * It only marks avatar as muted for session and does not use local Agent's Block list.
     * It does not mute Agent itself.
     *
     * @param[in] avatar_id UUID of avatar to be processed
     * @param[in] unmute if false - specified avatar will be muted, otherwise - unmuted.
     *
     * @see moderateVoiceAllParticipants()
     */
    void moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute);

    /**
     * Mutes/Unmutes all avatars for current group voice chat.
     *
     * It only marks avatars as muted for session and does not use local Agent's Block list.
     * It calls forceVoiceModeratedMode() in case of session is already in requested state.
     *
     * @param[in] unmute_everyone if false - avatars will be muted, otherwise - unmuted.
     *
     * @see moderateVoiceParticipant()
     */
    void moderateVoiceAllParticipants(bool unmute_everyone);

    void processSessionUpdate(const LLSD& session_update);

protected:
    virtual void updateSpeakerList();

    void moderateVoiceSession(const LLUUID& session_id, bool disallow_voice);

    /**
     * Process all participants to mute/unmute them according to passed voice session state.
     */
    void forceVoiceModeratedMode(bool should_be_muted);

    void moderationActionCoro(std::string url, LLSD action);

};

class LLActiveSpeakerMgr : public LLSpeakerMgr, public LLSingleton<LLActiveSpeakerMgr>
{
    LLSINGLETON(LLActiveSpeakerMgr);
    LOG_CLASS(LLActiveSpeakerMgr);

protected:
    virtual void updateSpeakerList() override;
};

class LLLocalSpeakerMgr : public LLSpeakerMgr, public LLSingleton<LLLocalSpeakerMgr>
{
    LLSINGLETON(LLLocalSpeakerMgr);
    ~LLLocalSpeakerMgr ();
    LOG_CLASS(LLLocalSpeakerMgr);
protected:
    virtual void updateSpeakerList() override;
};

#endif // LL_LLSPEAKERS_H