/** 
 * @file lloutputmonitorctrl.cpp
 * @brief LLOutputMonitorCtrl base class
 *
 * $LicenseInfo:firstyear=2001&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 "lloutputmonitorctrl.h"

// library includes 
#include "llfloaterreg.h"
#include "llui.h"

// viewer includes
#include "llvoiceclient.h"
#include "llmutelist.h"
#include "llagent.h"

#include "llspeakers.h"		// <FS:Zi> Add new control to have a nearby voice output monitor

// default options set in output_monitor.xml
static LLDefaultChildRegistry::Register<LLOutputMonitorCtrl> r("output_monitor");

// The defaults will be initialized in the constructor.
//LLColor4	LLOutputMonitorCtrl::sColorMuted;
//LLColor4	LLOutputMonitorCtrl::sColorOverdriven;
//LLColor4	LLOutputMonitorCtrl::sColorNormal;
LLColor4	LLOutputMonitorCtrl::sColorBound;
//S32			LLOutputMonitorCtrl::sRectsNumber 		= 0;
//F32			LLOutputMonitorCtrl::sRectWidthRatio	= 0.f;
//F32			LLOutputMonitorCtrl::sRectHeightRatio	= 0.f;

LLOutputMonitorCtrl::Params::Params()
:	draw_border("draw_border"),
	image_mute("image_mute"),
	image_off("image_off"),
	image_on("image_on"),
	image_level_1("image_level_1"),
	image_level_2("image_level_2"),
	image_level_3("image_level_3"),
	auto_update("auto_update"),
	speaker_id("speaker_id")
{
};

LLOutputMonitorCtrl::LLOutputMonitorCtrl(const LLOutputMonitorCtrl::Params& p)
:	LLView(p),
	mPower(0),
	mImageMute(p.image_mute),
	mImageOff(p.image_off),
	mImageOn(p.image_on),
	mImageLevel1(p.image_level_1),
	mImageLevel2(p.image_level_2),
	mImageLevel3(p.image_level_3),
	mAutoUpdate(p.auto_update),
	mSpeakerId(p.speaker_id),
	mIsModeratorMuted(false),
	mIsAgentControl(false),
	mIndicatorToggled(false),
	mShowParticipantsSpeaking(false),
	mChannelState(INACTIVE_CHANNEL),
	mAlwaysVisible(false) // <FS:Ansariel> Option to not hide it if speaker ID is set to null
{
	//static LLUIColor output_monitor_muted_color = LLUIColorTable::instance().getColor("OutputMonitorMutedColor", LLColor4::orange);
	//static LLUIColor output_monitor_overdriven_color = LLUIColorTable::instance().getColor("OutputMonitorOverdrivenColor", LLColor4::red);
	//static LLUIColor output_monitor_normal_color = LLUIColorTable::instance().getColor("OutputMonitorNotmalColor", LLColor4::green);
	static LLUIColor output_monitor_bound_color = LLUIColorTable::instance().getColor("OutputMonitorBoundColor", LLColor4::white);
	//static LLUICachedControl<S32> output_monitor_rects_number("OutputMonitorRectanglesNumber", 20);
	//static LLUICachedControl<F32> output_monitor_rect_width_ratio("OutputMonitorRectangleWidthRatio", 0.5f);
	//static LLUICachedControl<F32> output_monitor_rect_height_ratio("OutputMonitorRectangleHeightRatio", 0.8f);

	// IAN BUG compare to existing pattern where these are members - some will change per-widget and need to be anyway
	// sent feedback to PE
	
	// *TODO: it looks suboptimal to load the defaults every time an output monitor is constructed.
	//sColorMuted			= output_monitor_muted_color;
	//sColorOverdriven	= output_monitor_overdriven_color;
	//sColorNormal		= output_monitor_normal_color;
	sColorBound			= output_monitor_bound_color;
	//sRectsNumber		= output_monitor_rects_number;
	//sRectWidthRatio		= output_monitor_rect_width_ratio;
	//sRectHeightRatio	= output_monitor_rect_height_ratio;

	mBorder = p.draw_border;

	//with checking mute state
	setSpeakerId(mSpeakerId);
}

LLOutputMonitorCtrl::~LLOutputMonitorCtrl()
{
	LLMuteList::getInstance()->removeObserver(this);
	LLSpeakingIndicatorManager::unregisterSpeakingIndicator(mSpeakerId, this);
}

void LLOutputMonitorCtrl::setPower(F32 val)
{
	mPower = llmax(0.f, llmin(1.f, val));
}

void LLOutputMonitorCtrl::draw()
{
	// Copied from llmediaremotectrl.cpp
	// *TODO: Give the LLOutputMonitorCtrl an agent-id to monitor, then
	// call directly into LLVoiceClient::getInstance() to ask if that agent-id is muted, is
	// speaking, and what power.  This avoids duplicating data, which can get
	// out of sync.
	// <FS:Ansariel> Centralized voice power level
	//const F32 LEVEL_0 = LLVoiceClient::OVERDRIVEN_POWER_LEVEL / 3.f;
	//const F32 LEVEL_1 = LLVoiceClient::OVERDRIVEN_POWER_LEVEL * 2.f / 3.f;
	//const F32 LEVEL_2 = LLVoiceClient::OVERDRIVEN_POWER_LEVEL;
	// </FS:Ansariel> Centralized voice power level

	if (getVisible() && mAutoUpdate && !getIsMuted() && mSpeakerId.notNull())
	{
		setPower(LLVoiceClient::getInstance()->getCurrentPower(mSpeakerId));
		if(mIsAgentControl)
		{
			setIsTalking(LLVoiceClient::getInstance()->getUserPTTState());
		}
		else
		{
			setIsTalking(LLVoiceClient::getInstance()->getIsSpeaking(mSpeakerId));
		}
	}

	if ((mPower == 0.f && !mIsTalking) && mShowParticipantsSpeaking)
	{
		std::set<LLUUID> participant_uuids;
		LLVoiceClient::instance().getParticipantList(participant_uuids);
		std::set<LLUUID>::const_iterator part_it = participant_uuids.begin();

		F32 power = 0;
		for (; part_it != participant_uuids.end(); ++part_it)
		{
			power = LLVoiceClient::instance().getCurrentPower(*part_it);
			if (power)
			{
				mPower = power;
				break;
			}
		}
	}

	LLPointer<LLUIImage> icon;
	// <FS:Ansariel> Centralized voice power level
	//if (getIsMuted())
	//{
	//	icon = mImageMute;
	//}
	//else if (mPower == 0.f && !mIsTalking)
	//{
	//	// only show off if PTT is not engaged
	//	icon = mImageOff;
	//}
	//else if (mPower < LEVEL_0)
	//{
	//	// PTT is on, possibly with quiet background noise
	//	icon = mImageOn;
	//}
	//else if (mPower < LEVEL_1)
	//{
	//	icon = mImageLevel1;
	//}
	//else if (mPower < LEVEL_2)
	//{
	//	icon = mImageLevel2;
	//}
	//else
	//{
	//	// overdriven
	//	icon = mImageLevel3;
	//}
	EVoicePowerLevel power_level = LLVoiceClient::getInstance()->getPowerLevel(mSpeakerId);
	switch (power_level)
	{
		case VPL_MUTED:
			icon = mImageMute;
			break;
		case VPL_PTT_Off:
			icon = mImageOff;
			break;
		case VPL_PTT_On:
			icon = mImageOn;
			break;
		case VPL_Level1:
			icon = mImageLevel1;
			break;
		case VPL_Level2:
			icon = mImageLevel2;
			break;
		case VPL_Level3:
			icon = mImageLevel3;
			break;
		default:
			break;
	}
	// </FS:Ansariel> Centralized voice power level

	if (icon)
	{
		icon->draw(0, 0);
	}

	//
	// Fill the monitor with a bunch of small rectangles.
	// The rectangles will be filled with gradient color,
	// beginning with sColorNormal and ending with sColorOverdriven.
	// 
	// *TODO: would using a (partially drawn) pixmap instead be faster?
	//
	const int monh		= getRect().getHeight();
	const int monw		= getRect().getWidth();
	//int maxrects		= sRectsNumber;
	//const int period	= llmax(1, monw / maxrects, 0, 0);                    // "1" - min value for the period
	//const int rectw		= llmax(1, llfloor(period * sRectWidthRatio), 0, 0);  // "1" - min value for the rect's width
	//const int recth		= llfloor(monh * sRectHeightRatio);

	//if(period == 1 && rectw == 1) //if we have so small control, then "maxrects = monitor's_width - 2*monitor_border's_width
	//	maxrects = monw-2;

	//const int nrects	= mIsMuted ? maxrects : llfloor(mPower * maxrects); // how many rects to draw?
	//const int rectbtm	= (monh - recth) / 2;
	//const int recttop	= rectbtm + recth;
	//
	//LLColor4 rect_color;
	//
	//for (int i=1, xpos = 0; i <= nrects; i++)
	//{
	//	// Calculate color to use for the current rectangle.
	//	if (mIsMuted)
	//	{
	//		rect_color = sColorMuted;
	//	}
	//	else
	//	{
	//		F32 frac = (mPower * i/nrects) / LLVoiceClient::OVERDRIVEN_POWER_LEVEL;
	//		// Use overdriven color if the power exceeds overdriven level.
	//		if (frac > 1.0f)
	//			frac = 1.0f;
	//		rect_color = lerp(sColorNormal, sColorOverdriven, frac);
	//	}

	//	// Draw rectangle filled with the color.
	//	gl_rect_2d(xpos, recttop, xpos+rectw, rectbtm, rect_color, TRUE);
	//	xpos += period;
	//}

	//
	// Draw bounding box.
	//
	if(mBorder)
		gl_rect_2d(0, monh, monw, 0, sColorBound, FALSE);
}

// virtual
BOOL LLOutputMonitorCtrl::handleMouseUp(S32 x, S32 y, MASK mask)
{
	// <FS:Ansariel> Show voice volume instead of an empty floater
	//if (mSpeakerId != gAgentID)
	if (mSpeakerId.notNull() && mSpeakerId != gAgentID && !mShowParticipantsSpeaking)
	// </FS:Ansariel>
	{
		LLFloaterReg::showInstance("floater_voice_volume", LLSD().with("avatar_id", mSpeakerId));
	}
	// <FS:Ansariel> Show voice volume instead of an empty floater
	//else if (mShowParticipantsSpeaking)
	else if (mShowParticipantsSpeaking || mSpeakerId.isNull())
	// </FS:Ansariel>
	{
		LLFloaterReg::showInstance("chat_voice", LLSD());
	}

	return TRUE;
}

void LLOutputMonitorCtrl::setIsActiveChannel(bool val)
{
    setChannelState(val ? ACTIVE_CHANNEL : INACTIVE_CHANNEL);
}

void LLOutputMonitorCtrl::setChannelState(EChannelState state)
{
    mChannelState = state;
    if (state == INACTIVE_CHANNEL)
    {
        // switchIndicator will set it to TRUE when channel becomes active
        setVisible(FALSE);
    }
}

void LLOutputMonitorCtrl::setSpeakerId(const LLUUID& speaker_id, const LLUUID& session_id/* = LLUUID::null*/, bool show_other_participants_speaking /* = false */)
{
	if (speaker_id.isNull() && mSpeakerId.notNull())
	{
		LLSpeakingIndicatorManager::unregisterSpeakingIndicator(mSpeakerId, this);
		// <FS:Ansariel> Option to not hide it if speaker ID is set to null
		if (!mAlwaysVisible)
            switchIndicator(false);
        mSpeakerId = speaker_id;
	}

	if (speaker_id.isNull() || (speaker_id == mSpeakerId))
	{
		return;
	}

	if (mSpeakerId.notNull())
	{
		// Unregister previous registration to avoid crash. EXT-4782.
		LLSpeakingIndicatorManager::unregisterSpeakingIndicator(mSpeakerId, this);
	}

	mShowParticipantsSpeaking = show_other_participants_speaking;
	mSpeakerId = speaker_id;
	LLSpeakingIndicatorManager::registerSpeakingIndicator(mSpeakerId, this, session_id);

	//mute management
	if (mAutoUpdate)
	{
		if (speaker_id == gAgentID)
		{
			mIsMuted = false;
		}
		else
		{
			// check only blocking on voice. EXT-3542
			mIsMuted = LLMuteList::getInstance()->isMuted(mSpeakerId, LLMute::flagVoiceChat);
			LLMuteList::getInstance()->addObserver(this);
		}
	}
}

void LLOutputMonitorCtrl::onChangeDetailed(const LLMute& mute)
{
    if (mute.mID == mSpeakerId)
    {
        // Check only blocking on voice.
        // Logic goes in reverse, if flag is set, action is allowed
        mIsMuted = !(LLMute::flagVoiceChat & mute.mFlags);
    }
}

// virtual
void LLOutputMonitorCtrl::switchIndicator(bool switch_on)
{
    // <FS:Ansariel> [FS communication UI]
    //if ((mChannelState != INACTIVE_CHANNEL) && (getVisible() != (BOOL)switch_on))
    if (getVisible() != (BOOL)switch_on)
    // </FS:Ansariel> [FS communication UI]
    {
        setVisible(switch_on);
        
        //Let parent adjust positioning of icons adjacent to speaker indicator
        //(when speaker indicator hidden, adjacent icons move to right and when speaker
        //indicator visible, adjacent icons move to the left) 
        if (getParent() && getParent()->isInVisibleChain())
        {
            notifyParentVisibilityChanged();
            //Ignore toggled state in case it was set when parent visibility was hidden
            mIndicatorToggled = false;
        }
        else
        {
            //Makes sure to only adjust adjacent icons when parent becomes visible
            //(!mIndicatorToggled ensures that changes of TFT and FTF are discarded, real state changes are TF or FT)
            mIndicatorToggled = !mIndicatorToggled;
        }

    }
}

//////////////////////////////////////////////////////////////////////////
// PRIVATE SECTION
//////////////////////////////////////////////////////////////////////////
void LLOutputMonitorCtrl::notifyParentVisibilityChanged()
{
	LL_DEBUGS("SpeakingIndicator") << "Notify parent that visibility was changed: " << mSpeakerId << ", new_visibility: " << getVisible() << LL_ENDL;

	LLSD params = LLSD().with("visibility_changed", getVisible());

	notifyParent(params);
}

//////////////////////////////////////////////////////////////////////////
// <FS:Zi> Add new control to have a nearby voice output monitor

// default options set in output_monitor.xml
static LLDefaultChildRegistry::Register<NearbyVoiceMonitor> r2("nearby_voice_monitor");

NearbyVoiceMonitor::Params::Params()
:	auto_hide("auto_hide", false)
{
}

NearbyVoiceMonitor::NearbyVoiceMonitor(const NearbyVoiceMonitor::Params& p)
:	LLOutputMonitorCtrl(p),
	mAutoHide(p.auto_hide)
{
	mAlwaysVisible = true;
	mSpeakerMgr = LLLocalSpeakerMgr::getInstance();
}

void NearbyVoiceMonitor::draw()
{
	LLSpeakerMgr::speaker_list_t speaker_list;
	LLUUID id;
	bool draw = false;

	mSpeakerMgr->update(TRUE);
	mSpeakerMgr->getSpeakerList(&speaker_list, FALSE);

	for (LLSpeakerMgr::speaker_list_t::const_iterator it = speaker_list.begin(); it != speaker_list.end(); ++it)
	{
		LLPointer<LLSpeaker> speaker = *it;
		if (speaker->mSpeechVolume > 0.f || speaker->mStatus == LLSpeaker::STATUS_SPEAKING)
		{
			draw = true;
			id = speaker->mID;
			break;
		}
	}

	setSpeakerId(id);

	if (!mAutoHide || draw)
	{
		LLOutputMonitorCtrl::draw();
	}
}
// </FS:Zi> Add new control to have a nearby voice output monitor
//////////////////////////////////////////////////////////////////////////

// EOF
