/** 
 * @file llvoicechannel.cpp
 * @brief Voice Channel related classes
 *
 * $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 "llagent.h"
#include "llfloaterreg.h"
#include "llimview.h"
#include "llnotifications.h"
#include "llnotificationsutil.h"
#include "llpanel.h"
#include "llrecentpeople.h"
#include "llviewercontrol.h"
#include "llviewerregion.h"
#include "llvoicechannel.h"
#include "llcorehttputil.h"

LLVoiceChannel::voice_channel_map_t LLVoiceChannel::sVoiceChannelMap;
LLVoiceChannel::voice_channel_map_uri_t LLVoiceChannel::sVoiceChannelURIMap;
LLVoiceChannel* LLVoiceChannel::sCurrentVoiceChannel = NULL;
LLVoiceChannel* LLVoiceChannel::sSuspendedVoiceChannel = NULL;
LLVoiceChannel::channel_changed_signal_t LLVoiceChannel::sCurrentVoiceChannelChangedSignal;

BOOL LLVoiceChannel::sSuspended = FALSE;

//
// Constants
//
const U32 DEFAULT_RETRIES_COUNT = 3;

//
// LLVoiceChannel
//
LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& session_name) : 
	mSessionID(session_id), 
	mState(STATE_NO_CHANNEL_INFO), 
	mSessionName(session_name),
	mCallDirection(OUTGOING_CALL),
	mIgnoreNextSessionLeave(FALSE),
	mCallEndedByAgent(false)
{
	mNotifyArgs["VOICE_CHANNEL_NAME"] = mSessionName;

	if (!sVoiceChannelMap.insert(std::make_pair(session_id, this)).second)
	{
		// a voice channel already exists for this session id, so this instance will be orphaned
		// the end result should simply be the failure to make voice calls
		LL_WARNS("Voice") << "Duplicate voice channels registered for session_id " << session_id << LL_ENDL;
	}
}

LLVoiceChannel::~LLVoiceChannel()
{
	if (sSuspendedVoiceChannel == this)
	{
		sSuspendedVoiceChannel = NULL;
	}
	if (sCurrentVoiceChannel == this)
	{
		sCurrentVoiceChannel = NULL;
		// Must check instance exists here, the singleton MAY have already been destroyed.
		if(LLVoiceClient::instanceExists())
		{
			LLVoiceClient::getInstance()->removeObserver(this);
		}
	}
	
	sVoiceChannelMap.erase(mSessionID);
	sVoiceChannelURIMap.erase(mURI);
}

void LLVoiceChannel::setChannelInfo(
	const std::string& uri,
	const std::string& credentials)
{
	setURI(uri);

	mCredentials = credentials;

	if (mState == STATE_NO_CHANNEL_INFO)
	{
		if (mURI.empty())
		{
			LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs);
			LL_WARNS("Voice") << "Received empty URI for channel " << mSessionName << LL_ENDL;
			deactivate();
		}
		else if (mCredentials.empty())
		{
			LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs);
			LL_WARNS("Voice") << "Received empty credentials for channel " << mSessionName << LL_ENDL;
			deactivate();
		}
		else
		{
			setState(STATE_READY);

			// if we are supposed to be active, reconnect
			// this will happen on initial connect, as we request credentials on first use
			if (sCurrentVoiceChannel == this)
			{
				// just in case we got new channel info while active
				// should move over to new channel
				activate();
			}
		}
	}
}

void LLVoiceChannel::onChange(EStatusType type, const std::string &channelURI, bool proximal)
{
	if (channelURI != mURI)
	{
		return;
	}

	if (type < BEGIN_ERROR_STATUS)
	{
		handleStatusChange(type);
	}
	else
	{
		handleError(type);
	}
}

void LLVoiceChannel::handleStatusChange(EStatusType type)
{
	// status updates
	switch(type)
	{
	case STATUS_LOGIN_RETRY:
        // no user notice
		break;
	case STATUS_LOGGED_IN:
		break;
	case STATUS_LEFT_CHANNEL:
		if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended)
		{
			// if forceably removed from channel
			// update the UI and revert to default channel
			deactivate();
		}
		mIgnoreNextSessionLeave = FALSE;
		break;
	case STATUS_JOINING:
		if (callStarted())
		{
			setState(STATE_RINGING);
		}
		break;
	case STATUS_JOINED:
		if (callStarted())
		{
			setState(STATE_CONNECTED);
		}
	default:
		break;
	}
}

// default behavior is to just deactivate channel
// derived classes provide specific error messages
void LLVoiceChannel::handleError(EStatusType type)
{
	deactivate();
	setState(STATE_ERROR);
}

BOOL LLVoiceChannel::isActive()
{ 
	// only considered active when currently bound channel matches what our channel
	return callStarted() && LLVoiceClient::getInstance()->getCurrentChannel() == mURI; 
}

BOOL LLVoiceChannel::callStarted()
{
	return mState >= STATE_CALL_STARTED;
}

void LLVoiceChannel::deactivate()
{
	if (mState >= STATE_RINGING)
	{
		// ignore session leave event
		mIgnoreNextSessionLeave = TRUE;
	}

	if (callStarted())
	{
		setState(STATE_HUNG_UP);

		//Default mic is OFF when leaving voice calls
		if (gSavedSettings.getBOOL("AutoDisengageMic") &&
			sCurrentVoiceChannel == this &&
			LLVoiceClient::getInstance()->getUserPTTState())
		{
			gSavedSettings.setBOOL("PTTCurrentlyEnabled", true);
			LLVoiceClient::getInstance()->setUserPTTState(false);
		}
	}
	LLVoiceClient::getInstance()->removeObserver(this);
	
	if (sCurrentVoiceChannel == this)
	{
		// default channel is proximal channel
		sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance();
		sCurrentVoiceChannel->activate();
	}
}

void LLVoiceChannel::activate()
{
	if (callStarted())
	{
		return;
	}

	// deactivate old channel and mark ourselves as the active one
	if (sCurrentVoiceChannel != this)
	{
		// mark as current before deactivating the old channel to prevent
		// activating the proximal channel between IM calls
		LLVoiceChannel* old_channel = sCurrentVoiceChannel;
		sCurrentVoiceChannel = this;
		mCallDialogPayload["old_channel_name"] = "";
		if (old_channel)
		{
			mCallDialogPayload["old_channel_name"] = old_channel->getSessionName();
			old_channel->deactivate();
		}
	}

	if (mState == STATE_NO_CHANNEL_INFO)
	{
		// responsible for setting status to active
		getChannelInfo();
	}
	else
	{
		setState(STATE_CALL_STARTED);
	}
	
	LLVoiceClient::getInstance()->addObserver(this);
	
	//do not send earlier, channel should be initialized, should not be in STATE_NO_CHANNEL_INFO state
	sCurrentVoiceChannelChangedSignal(this->mSessionID);
}

void LLVoiceChannel::getChannelInfo()
{
	// pretend we have everything we need
	if (sCurrentVoiceChannel == this)
	{
		setState(STATE_CALL_STARTED);
	}
}

//static 
LLVoiceChannel* LLVoiceChannel::getChannelByID(const LLUUID& session_id)
{
	voice_channel_map_t::iterator found_it = sVoiceChannelMap.find(session_id);
	if (found_it == sVoiceChannelMap.end())
	{
		return NULL;
	}
	else
	{
		return found_it->second;
	}
}

//static 
LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri)
{
	voice_channel_map_uri_t::iterator found_it = sVoiceChannelURIMap.find(uri);
	if (found_it == sVoiceChannelURIMap.end())
	{
		return NULL;
	}
	else
	{
		return found_it->second;
	}
}

LLVoiceChannel* LLVoiceChannel::getCurrentVoiceChannel()
{
	return sCurrentVoiceChannel;
}

void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id)
{
	sVoiceChannelMap.erase(sVoiceChannelMap.find(mSessionID));
	mSessionID = new_session_id;
	sVoiceChannelMap.insert(std::make_pair(mSessionID, this));
}

void LLVoiceChannel::setURI(std::string uri)
{
	sVoiceChannelURIMap.erase(mURI);
	mURI = uri;
	sVoiceChannelURIMap.insert(std::make_pair(mURI, this));
}

void LLVoiceChannel::setState(EState state)
{
	switch(state)
	{
	case STATE_RINGING:
		//TODO: remove or redirect this call status notification
//		LLCallInfoDialog::show("ringing", mNotifyArgs);
		break;
	case STATE_CONNECTED:
		//TODO: remove or redirect this call status notification
//		LLCallInfoDialog::show("connected", mNotifyArgs);
		break;
	case STATE_HUNG_UP:
		//TODO: remove or redirect this call status notification
//		LLCallInfoDialog::show("hang_up", mNotifyArgs);
		break;
	default:
		break;
	}

	doSetState(state);
}

void LLVoiceChannel::doSetState(const EState& new_state)
{
	EState old_state = mState;
	mState = new_state;

	if (!mStateChangedCallback.empty())
		mStateChangedCallback(old_state, mState, mCallDirection, mCallEndedByAgent, mSessionID);
}

//static
void LLVoiceChannel::initClass()
{
	sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance();
}

//static 
void LLVoiceChannel::suspend()
{
	if (!sSuspended)
	{
		sSuspendedVoiceChannel = sCurrentVoiceChannel;
		sSuspended = TRUE;
	}
}

//static 
void LLVoiceChannel::resume()
{
	if (sSuspended)
	{
		if (LLVoiceClient::getInstance()->voiceEnabled())
		{
			if (sSuspendedVoiceChannel)
			{
				sSuspendedVoiceChannel->activate();
			}
			else
			{
				LLVoiceChannelProximal::getInstance()->activate();
			}
		}
		sSuspended = FALSE;
	}
}

boost::signals2::connection LLVoiceChannel::setCurrentVoiceChannelChangedCallback(channel_changed_callback_t cb, bool at_front)
{
	if (at_front)
	{
		return sCurrentVoiceChannelChangedSignal.connect(cb,  boost::signals2::at_front);
	}
	else
	{
		return sCurrentVoiceChannelChangedSignal.connect(cb);
	}
}

//
// LLVoiceChannelGroup
//

LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name) : 
	LLVoiceChannel(session_id, session_name)
{
	mRetries = DEFAULT_RETRIES_COUNT;
	mIsRetrying = FALSE;
}

void LLVoiceChannelGroup::deactivate()
{
	if (callStarted())
	{
		LLVoiceClient::getInstance()->leaveNonSpatialChannel();
	}
	LLVoiceChannel::deactivate();
}

void LLVoiceChannelGroup::activate()
{
	if (callStarted()) return;

	LLVoiceChannel::activate();

	if (callStarted())
	{
		// we have the channel info, just need to use it now
		LLVoiceClient::getInstance()->setNonSpatialChannel(
			mURI,
			mCredentials);

		if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel
		{
			LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionID);
			// Adding ad-hoc call participants to Recent People List.
			// If it's an outgoing ad-hoc, we can use mInitialTargetIDs that holds IDs of people we
			// called(both online and offline) as source to get people for recent (STORM-210).
			if (session->isOutgoingAdHoc())
			{
				for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin();
					it!=session->mInitialTargetIDs.end();++it)
				{
					const LLUUID id = *it;
					LLRecentPeople::instance().add(id);
				}
			}
			// If this ad-hoc is incoming then trying to get ids of people from mInitialTargetIDs
			// would lead to EXT-8246. So in this case we get them from speakers list.
			else
			{
				LLIMModel::addSpeakersToRecent(mSessionID);
			}
		}

		//Mic default state is OFF on initiating/joining Ad-Hoc/Group calls
		if (LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle())
		{
			LLVoiceClient::getInstance()->inputUserControlState(true);
		}
		
	}
}

void LLVoiceChannelGroup::getChannelInfo()
{
	LLViewerRegion* region = gAgent.getRegion();
	if (region)
	{
		std::string url = region->getCapability("ChatSessionRequest");

        LLCoros::instance().launch("LLVoiceChannelGroup::voiceCallCapCoro",
            boost::bind(&LLVoiceChannelGroup::voiceCallCapCoro, this, url));
	}
}

void LLVoiceChannelGroup::setChannelInfo(
	const std::string& uri,
	const std::string& credentials)
{
	setURI(uri);

	mCredentials = credentials;

	if (mState == STATE_NO_CHANNEL_INFO)
	{
		if(!mURI.empty() && !mCredentials.empty())
		{
			setState(STATE_READY);

			// if we are supposed to be active, reconnect
			// this will happen on initial connect, as we request credentials on first use
			if (sCurrentVoiceChannel == this)
			{
				// just in case we got new channel info while active
				// should move over to new channel
				activate();
			}
		}
		else
		{
			//*TODO: notify user
			LL_WARNS("Voice") << "Received invalid credentials for channel " << mSessionName << LL_ENDL;
			deactivate();
		}
	}
	else if ( mIsRetrying )
	{
		// we have the channel info, just need to use it now
		LLVoiceClient::getInstance()->setNonSpatialChannel(
			mURI,
			mCredentials);
	}
}

void LLVoiceChannelGroup::handleStatusChange(EStatusType type)
{
	// status updates
	switch(type)
	{
	case STATUS_JOINED:
		mRetries = 3;
		mIsRetrying = FALSE;
	default:
		break;
	}

	LLVoiceChannel::handleStatusChange(type);
}

void LLVoiceChannelGroup::handleError(EStatusType status)
{
	std::string notify;
	switch(status)
	{
	case ERROR_CHANNEL_LOCKED:
	case ERROR_CHANNEL_FULL:
		notify = "VoiceChannelFull";
		break;
	case ERROR_NOT_AVAILABLE:
		//clear URI and credentials
		//set the state to be no info
		//and activate
		if ( mRetries > 0 )
		{
			mRetries--;
			mIsRetrying = TRUE;
			mIgnoreNextSessionLeave = TRUE;

			getChannelInfo();
			return;
		}
		else
		{
			notify = "VoiceChannelJoinFailed";
			mRetries = DEFAULT_RETRIES_COUNT;
			mIsRetrying = FALSE;
		}

		break;

	case ERROR_UNKNOWN:
	default:
		break;
	}

	// notification
	if (!notify.empty())
	{
		LLNotificationPtr notification = LLNotificationsUtil::add(notify, mNotifyArgs);
		// echo to im window
		gIMMgr->addMessage(mSessionID, LLUUID::null, SYSTEM_FROM, notification->getMessage());
	}

	LLVoiceChannel::handleError(status);
}

void LLVoiceChannelGroup::setState(EState state)
{
	switch(state)
	{
	case STATE_RINGING:
		if ( !mIsRetrying )
		{
			//TODO: remove or redirect this call status notification
//			LLCallInfoDialog::show("ringing", mNotifyArgs);
		}

		doSetState(state);
		break;
	default:
		LLVoiceChannel::setState(state);
	}
}

void LLVoiceChannelGroup::voiceCallCapCoro(std::string url)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceCallCapCoro", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    LLSD postData;
    postData["method"] = "call";
    postData["session-id"] = mSessionID;

    LL_INFOS("Voice", "voiceCallCapCoro") << "Generic POST for " << url << LL_ENDL;

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

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

    LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID);
    if (!channelp)
    {
        LL_WARNS("Voice") << "Unable to retrieve channel with Id = " << mSessionID << LL_ENDL;
        return;
    }

    if (!status)
    {
        if (status == LLCore::HttpStatus(HTTP_FORBIDDEN))
        {
            //403 == no ability
            LLNotificationsUtil::add(
                "VoiceNotAllowed",
                channelp->getNotifyArgs());
        }
        else
        {
            LLNotificationsUtil::add(
                "VoiceCallGenericError",
                channelp->getNotifyArgs());
        }
        channelp->deactivate();
        return;
    }

    result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);

    LLSD::map_const_iterator iter;
    for (iter = result.beginMap(); iter != result.endMap(); ++iter)
    {
        LL_DEBUGS("Voice") << "LLVoiceCallCapResponder::result got "
            << iter->first << LL_ENDL;
    }

    channelp->setChannelInfo(
        result["voice_credentials"]["channel_uri"].asString(),
        result["voice_credentials"]["channel_credentials"].asString());

}


//
// LLVoiceChannelProximal
//
LLVoiceChannelProximal::LLVoiceChannelProximal() : 
	LLVoiceChannel(LLUUID::null, LLStringUtil::null)
{
}

BOOL LLVoiceChannelProximal::isActive()
{
	return callStarted() && LLVoiceClient::getInstance()->inProximalChannel(); 
}

void LLVoiceChannelProximal::activate()
{
	if (callStarted()) return;

	if((LLVoiceChannel::sCurrentVoiceChannel != this) && (LLVoiceChannel::getState() == STATE_CONNECTED))
	{
		// we're connected to a non-spatial channel, so disconnect.
		LLVoiceClient::getInstance()->leaveNonSpatialChannel();	
	}
	LLVoiceChannel::activate();
	
}

void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal)
{
	if (!proximal)
	{
		return;
	}

	if (type < BEGIN_ERROR_STATUS)
	{
		handleStatusChange(type);
	}
	else
	{
		handleError(type);
	}
}

void LLVoiceChannelProximal::handleStatusChange(EStatusType status)
{
	// status updates
	switch(status)
	{
	case STATUS_LEFT_CHANNEL:
		// do not notify user when leaving proximal channel
		return;
	case STATUS_VOICE_DISABLED:
		LLVoiceClient::getInstance()->setUserPTTState(false);
		gAgent.setVoiceConnected(false);
		//skip showing "Voice not available at your current location" when agent voice is disabled (EXT-4749)
		if(LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking())
		{
			//TODO: remove or redirect this call status notification
//			LLCallInfoDialog::show("unavailable", mNotifyArgs);
		}
		return;
	default:
		break;
	}
	LLVoiceChannel::handleStatusChange(status);
}


void LLVoiceChannelProximal::handleError(EStatusType status)
{
	std::string notify;
	switch(status)
	{
	  case ERROR_CHANNEL_LOCKED:
	  case ERROR_CHANNEL_FULL:
		notify = "ProximalVoiceChannelFull";
		break;
	  default:
		 break;
	}

	// notification
	if (!notify.empty())
	{
		LLNotificationsUtil::add(notify, mNotifyArgs);
	}

	LLVoiceChannel::handleError(status);
}

void LLVoiceChannelProximal::deactivate()
{
	if (callStarted())
	{
		setState(STATE_HUNG_UP);
	}
}


//
// LLVoiceChannelP2P
//
LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id) : 
		LLVoiceChannelGroup(session_id, session_name), 
		mOtherUserID(other_user_id),
		mReceivedCall(FALSE)
{
	// make sure URI reflects encoded version of other user's agent id
	setURI(LLVoiceClient::getInstance()->sipURIFromID(other_user_id));
}

void LLVoiceChannelP2P::handleStatusChange(EStatusType type)
{
	LL_INFOS("Voice") << "P2P CALL CHANNEL STATUS CHANGE: incoming=" << int(mReceivedCall) << " newstatus=" << LLVoiceClientStatusObserver::status2string(type) << " (mState=" << mState << ")" << LL_ENDL;

	// status updates
	switch(type)
	{
	case STATUS_LEFT_CHANNEL:
		if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended)
		{
			// *TODO: use it to show DECLINE voice notification
			if (mState == STATE_RINGING)
			{
				// other user declined call
				LLNotificationsUtil::add("P2PCallDeclined", mNotifyArgs);
			}
			else
			{
				// other user hung up, so we didn't end the call				
				mCallEndedByAgent = false;			
			}
			deactivate();
		}
		mIgnoreNextSessionLeave = FALSE;
		return;
	case STATUS_JOINING:
		// because we join session we expect to process session leave event in the future. EXT-7371
		// may be this should be done in the LLVoiceChannel::handleStatusChange.
		mIgnoreNextSessionLeave = FALSE;
		break;

	default:
		break;
	}

	LLVoiceChannel::handleStatusChange(type);
}

void LLVoiceChannelP2P::handleError(EStatusType type)
{
	switch(type)
	{
	case ERROR_NOT_AVAILABLE:
		LLNotificationsUtil::add("P2PCallNoAnswer", mNotifyArgs);
		break;
	default:
		break;
	}

	LLVoiceChannel::handleError(type);
}

void LLVoiceChannelP2P::activate()
{
	if (callStarted()) return;

	//call will be counted as ended by user unless this variable is changed in handleStatusChange()
	mCallEndedByAgent = true;

	LLVoiceChannel::activate();

	if (callStarted())
	{
		// no session handle yet, we're starting the call
		if (mSessionHandle.empty())
		{
			mReceivedCall = FALSE;
			LLVoiceClient::getInstance()->callUser(mOtherUserID);
		}
		// otherwise answering the call
		else
		{
			if (!LLVoiceClient::getInstance()->answerInvite(mSessionHandle))
			{
				mCallEndedByAgent = false;
				mSessionHandle.clear();
				handleError(ERROR_UNKNOWN);
				return;
			}
			// using the session handle invalidates it.  Clear it out here so we can't reuse it by accident.
			mSessionHandle.clear();
		}

		// Add the party to the list of people with which we've recently interacted.
		addToTheRecentPeopleList();

		//Default mic is ON on initiating/joining P2P calls
		if (!LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle())
		{
			LLVoiceClient::getInstance()->inputUserControlState(true);
		}
	}
}

void LLVoiceChannelP2P::getChannelInfo()
{
	// pretend we have everything we need, since P2P doesn't use channel info
	if (sCurrentVoiceChannel == this)
	{
		setState(STATE_CALL_STARTED);
	}
}

// receiving session from other user who initiated call
void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI)
{ 
	BOOL needs_activate = FALSE;
	if (callStarted())
	{
		// defer to lower agent id when already active
		if (mOtherUserID < gAgent.getID())
		{
			// pretend we haven't started the call yet, so we can connect to this session instead
			deactivate();
			needs_activate = TRUE;
		}
		else
		{
			// we are active and have priority, invite the other user again
			// under the assumption they will join this new session
			mSessionHandle.clear();
			LLVoiceClient::getInstance()->callUser(mOtherUserID);
			return;
		}
	}

	mSessionHandle = handle;

	// The URI of a p2p session should always be the other end's SIP URI.
	if(!inURI.empty())
	{
		setURI(inURI);
	}
	else
	{
		LL_WARNS("Voice") << "incoming SIP URL is not provided. Channel may not work properly." << LL_ENDL;
		// See LLVoiceClient::sessionAddedEvent()
		setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID));
	}
	
	mReceivedCall = TRUE;

	if (needs_activate)
	{
		activate();
	}
}

void LLVoiceChannelP2P::setState(EState state)
{
	LL_INFOS("Voice") << "P2P CALL STATE CHANGE: incoming=" << int(mReceivedCall) << " oldstate=" << mState << " newstate=" << state << LL_ENDL;

	if (mReceivedCall) // incoming call
	{
		// you only "answer" voice invites in p2p mode
		// so provide a special purpose message here
		if (mReceivedCall && state == STATE_RINGING)
		{
			//TODO: remove or redirect this call status notification
//			LLCallInfoDialog::show("answering", mNotifyArgs);
			doSetState(state);
			return;
		}
	}

	LLVoiceChannel::setState(state);
}

void LLVoiceChannelP2P::addToTheRecentPeopleList()
{
	LLRecentPeople::instance().add(mOtherUserID);
}
