/** 
 * @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; }
	const LLUUID getSessionID();
	bool isSpeakerToBeRemoved(const LLUUID& speaker_id);

	/**
	 * Removes avaline speaker.
	 *
	 * This is a HACK due to server does not send information that Avaline caller ends call.
	 * It can be removed when server is updated. See EXT-4301 for details
	 */
	bool removeAvalineSpeaker(const LLUUID& speaker_id) { return removeSpeaker(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);

	// <FS:Zi> make text chat block in groups not a toggle to prevent accidental unmuting
	// void toggleAllowTextChat(const LLUUID& speaker_id);
	void allowTextChat(const LLUUID& speaker_id, bool allow);

	/**
	 * 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();
};

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

#endif // LL_LLSPEAKERS_H
