/** 
 * @file llimview.cpp
 * @brief Container for Instant Messaging
 *
 * Copyright (c) 2001-2007, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlife.com/developers/opensource/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at http://secondlife.com/developers/opensource/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 */

#include "llviewerprecompiledheaders.h"

#include "llimview.h"

#include "llfontgl.h"
#include "llrect.h"
#include "llerror.h"
#include "llbutton.h"
#include "llstring.h"
#include "linked_lists.h"
#include "llvieweruictrlfactory.h"

#include "llagent.h"
#include "llcallingcard.h"
#include "llviewerwindow.h"
#include "llresmgr.h"
#include "llfloaternewim.h"
#include "llhttpnode.h"
#include "llimpanel.h"
#include "llresizebar.h"
#include "lltabcontainer.h"
#include "viewer.h"
#include "llfloater.h"
#include "llresizehandle.h"
#include "llkeyboard.h"
#include "llui.h"
#include "llviewermenu.h"
#include "llcallingcard.h"
#include "lltoolbar.h"

const EInstantMessage GROUP_DIALOG = IM_SESSION_GROUP_START;
const EInstantMessage DEFAULT_DIALOG = IM_NOTHING_SPECIAL;

//
// Globals
//
LLIMView* gIMView = NULL;

//
// Statics
//
static LLString sOnlyUserMessage;
static LLString sOfflineMessage;

static std::map<std::string,LLString> sEventStringsMap;
static std::map<std::string,LLString> sErrorStringsMap;
static std::map<std::string,LLString> sForceCloseSessionMap;
//
// Helper Functions
//

// returns true if a should appear before b
static BOOL group_dictionary_sort( LLGroupData* a, LLGroupData* b )
{
	return (LLString::compareDict( a->mName, b->mName ) < 0);
}


// the other_participant_id is either an agent_id, a group_id, or an inventory
// folder item_id (collection of calling cards)
static LLUUID compute_session_id(EInstantMessage dialog,
								 const LLUUID& other_participant_id)
{
	LLUUID session_id;
	if (IM_SESSION_GROUP_START == dialog)
	{
		// slam group session_id to the group_id (other_participant_id)
		session_id = other_participant_id;
	}
	else if (IM_SESSION_CONFERENCE_START == dialog)
	{
		session_id.generate();
	}
	else
	{
		LLUUID agent_id = gAgent.getID();
		if (other_participant_id == agent_id)
		{
			// if we try to send an IM to ourselves then the XOR would be null
			// so we just make the session_id the same as the agent_id
			session_id = agent_id;
		}
		else
		{
			// peer-to-peer or peer-to-asset session_id is the XOR
			session_id = other_participant_id ^ agent_id;
		}
	}
	return session_id;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// LLFloaterIM
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

LLFloaterIM::LLFloaterIM() 
{
	gUICtrlFactory->buildFloater(this, "floater_im.xml");
}

BOOL LLFloaterIM::postBuild()
{
	requires("only_user_message", WIDGET_TYPE_TEXT_BOX);
	requires("offline_message", WIDGET_TYPE_TEXT_BOX);
	requires("generic_request_error", WIDGET_TYPE_TEXT_BOX);
	requires("insufficient_perms_error", WIDGET_TYPE_TEXT_BOX);
	requires("generic_request_error", WIDGET_TYPE_TEXT_BOX);
	requires("add_session_event", WIDGET_TYPE_TEXT_BOX);
	requires("message_session_event", WIDGET_TYPE_TEXT_BOX);
	requires("removed_from_group", WIDGET_TYPE_TEXT_BOX);

	if (checkRequirements())
	{
		sOnlyUserMessage = childGetText("only_user_message");
		sOfflineMessage = childGetText("offline_message");

		sErrorStringsMap["generic"] =
			childGetText("generic_request_error");
		sErrorStringsMap["unverified"] =
			childGetText("insufficient_perms_error");
		sErrorStringsMap["no_user_911"] =
			childGetText("user_no_help");

		sEventStringsMap["add"] = childGetText("add_session_event");;
		sEventStringsMap["message"] =
			childGetText("message_session_event");;
		sEventStringsMap["teleport"] =
			childGetText("teleport_session_event");;

		sForceCloseSessionMap["removed"] =
			childGetText("removed_from_group");

		return TRUE;
	}
	return FALSE;
}

//// virtual
//BOOL LLFloaterIM::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent)
//{
//	BOOL handled = FALSE;
//	if (getEnabled()
//		&& mask == (MASK_CONTROL|MASK_SHIFT))
//	{
//		if (key == 'W')
//		{
//			LLFloater* floater = getActiveFloater();
//			if (floater)
//			{
//				if (mTabContainer->getTabCount() == 1)
//				{
//					// trying to close last tab, close
//					// entire window.
//					close();
//					handled = TRUE;
//				}
//			}
//		}
//	}
//	return handled || LLMultiFloater::handleKeyHere(key, mask, called_from_parent);
//}

void LLFloaterIM::onClose(bool app_quitting)
{
	if (!app_quitting)
	{
		gSavedSettings.setBOOL("ShowIM", FALSE);
	}
	setVisible(FALSE);
}

//virtual
void LLFloaterIM::addFloater(LLFloater* floaterp, BOOL select_added_floater, LLTabContainer::eInsertionPoint insertion_point)
{
	// this code is needed to fix the bug where new IMs received will resize the IM floater.
	// SL-29075, SL-24556, and others
	LLRect parent_rect = getRect();
	S32 dheight = LLFLOATER_HEADER_SIZE + TABCNTR_HEADER_HEIGHT;
	LLRect rect(0, parent_rect.getHeight()-dheight, parent_rect.getWidth(), 0);
	floaterp->reshape(rect.getWidth(), rect.getHeight(), TRUE);
	LLMultiFloater::addFloater(floaterp, select_added_floater, insertion_point);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLIMViewFriendObserver
//
// Bridge to suport knowing when the inventory has changed.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class LLIMViewFriendObserver : public LLFriendObserver
{
public:
	LLIMViewFriendObserver(LLIMView* tv) : mTV(tv) {}
	virtual ~LLIMViewFriendObserver() {}
	virtual void changed(U32 mask)
	{
		if(mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE))
		{
			mTV->refresh();
		}
	}
protected:
	LLIMView* mTV;
};


//
// Public Static Member Functions
//

// This is a helper function to determine what kind of im session
// should be used for the given agent.
// static
EInstantMessage LLIMView::defaultIMTypeForAgent(const LLUUID& agent_id)
{
	EInstantMessage type = IM_NOTHING_SPECIAL;
	if(is_agent_friend(agent_id))
	{
		if(LLAvatarTracker::instance().isBuddyOnline(agent_id))
		{
			type = IM_SESSION_CONFERENCE_START;
		}
	}
	return type;
}

// static
//void LLIMView::onPinButton(void*)
//{
//	BOOL state = gSavedSettings.getBOOL( "PinTalkViewOpen" );
//	gSavedSettings.setBOOL( "PinTalkViewOpen", !state );
//}

// static 
void LLIMView::toggle(void*)
{
	static BOOL return_to_mouselook = FALSE;

	// Hide the button and show the floater or vice versa.
	llassert( gIMView );
	BOOL old_state = gIMView->getFloaterOpen();
	
	// If we're in mouselook and we triggered the Talk View, we want to talk.
	if( gAgent.cameraMouselook() && old_state )
	{
		return_to_mouselook = TRUE;
		gAgent.changeCameraToDefault();
		return;
	}

	BOOL new_state = !old_state;

	if (new_state)
	{
		// ...making visible
		if ( gAgent.cameraMouselook() )
		{
			return_to_mouselook = TRUE;
			gAgent.changeCameraToDefault();
		}
	}
	else
	{
		// ...hiding
		if ( gAgent.cameraThirdPerson() && return_to_mouselook )
		{
			gAgent.changeCameraToMouselook();
		}
		return_to_mouselook = FALSE;
	}

	gIMView->setFloaterOpen( new_state );
}

//
// Member Functions
//

LLIMView::LLIMView(const std::string& name, const LLRect& rect) :
	LLView(name, rect, FALSE),
	mFriendObserver(NULL),
	mIMReceived(FALSE)
{
	gIMView = this;
	mFriendObserver = new LLIMViewFriendObserver(this);
	LLAvatarTracker::instance().addObserver(mFriendObserver);

	mTalkFloater = new LLFloaterIM();

	// New IM Panel
	mNewIMFloater = new LLFloaterNewIM();
	mTalkFloater->addFloater(mNewIMFloater, TRUE);

	// Tabs sometimes overlap resize handle
	mTalkFloater->moveResizeHandleToFront();
}

LLIMView::~LLIMView()
{
	LLAvatarTracker::instance().removeObserver(mFriendObserver);
	delete mFriendObserver;
	// Children all cleaned up by default view destructor.
}

EWidgetType LLIMView::getWidgetType() const
{
	return WIDGET_TYPE_TALK_VIEW;
}

LLString LLIMView::getWidgetTag() const
{
	return LL_TALK_VIEW_TAG;
}

// Add a message to a session. 
void LLIMView::addMessage(
	const LLUUID& session_id,
	const LLUUID& other_participant_id,
	const char* from,
	const char* msg,
	const char* session_name,
	EInstantMessage dialog,
	U32 parent_estate_id,
	const LLUUID& region_id,
	const LLVector3& position)
{
	LLFloaterIMPanel* floater;
	LLUUID new_session_id = session_id;
	if (new_session_id.isNull())
	{
		new_session_id = compute_session_id(dialog, other_participant_id);
	}
	floater = findFloaterBySession(new_session_id);
	if (!floater)
	{
		floater = findFloaterBySession(other_participant_id);
		if (floater)
		{
			llinfos << "found the IM session " << session_id 
				<< " by participant " << other_participant_id << llendl;
		}
	}
	if(floater)
	{
		floater->addHistoryLine(msg);
	}
	else
	{
		//if we have recently requsted to be dropped from a session
		//but are still receiving messages from the session, don't make
		//a new floater
//		if ( mSessionsDropRequested.has(session_id.asString()) )
//		{
//			return ;
//		}

		const char* name = from;
		if(session_name && (strlen(session_name)>1))
		{
			name = session_name;
		}

		
		floater = createFloater(new_session_id, other_participant_id, name, dialog, FALSE);

		// When we get a new IM, and if you are a god, display a bit
		// of information about the source. This is to help liaisons
		// when answering questions.
		if(gAgent.isGodlike())
		{
			// XUI:translate
			std::ostringstream bonus_info;
			bonus_info << "*** parent estate: "
				<< parent_estate_id
				<< ((parent_estate_id == 1) ? ", mainland" : "")
				<< ((parent_estate_id == 5) ? ", teen" : "");

			// once we have web-services (or something) which returns
			// information about a region id, we can print this out
			// and even have it link to map-teleport or something.
			//<< "*** region_id: " << region_id << std::endl
			//<< "*** position: " << position << std::endl;

			floater->addHistoryLine(bonus_info.str());
		}

		floater->addHistoryLine(msg);
		make_ui_sound("UISndNewIncomingIMSession");
	}

	if( !mTalkFloater->getVisible() && !floater->getVisible())
	{
		//if the IM window is not open and the floater is not visible (i.e. not torn off)
		LLFloater* previouslyActiveFloater = mTalkFloater->getActiveFloater();

		// select the newly added floater (or the floater with the new line added to it).
		// it should be there.
		mTalkFloater->selectFloater(floater);

		//there was a previously unseen IM, make that old tab flashing
		//it is assumed that the most recently unseen IM tab is the one current selected/active
		if ( previouslyActiveFloater && getIMReceived() )
		{
			mTalkFloater->setFloaterFlashing(previouslyActiveFloater, TRUE);
		}

		//notify of a new IM
		notifyNewIM();
	}
}

void LLIMView::notifyNewIM()
{
	if(!gIMView->getFloaterOpen())
	{
		mIMReceived = TRUE;
	}
}

BOOL LLIMView::getIMReceived() const
{
	return mIMReceived;
}

// This method returns TRUE if the local viewer has a session
// currently open keyed to the uuid. 
BOOL LLIMView::isIMSessionOpen(const LLUUID& uuid)
{
	LLFloaterIMPanel* floater = findFloaterBySession(uuid);
	if(floater) return TRUE;
	return FALSE;
}

// This adds a session to the talk view. The name is the local name of
// the session, dialog specifies the type of session. If the session
// exists, it is brought forward.  Specifying id = NULL results in an
// im session to everyone. Returns the uuid of the session.
LLUUID LLIMView::addSession(const std::string& name,
							EInstantMessage dialog,
							const LLUUID& other_participant_id)
{
	LLUUID session_id = compute_session_id(dialog, other_participant_id);

	LLFloaterIMPanel* floater = findFloaterBySession(session_id);
	if(!floater)
	{
		LLDynamicArray<LLUUID> ids;
		ids.put(other_participant_id);

		floater = createFloater(session_id,
								other_participant_id,
								name,
								ids,
								dialog,
								TRUE);

		noteOfflineUsers(floater, ids);
		mTalkFloater->showFloater(floater);
	}
	else
	{
		floater->open();
	}
	//mTabContainer->selectTabPanel(panel);
	floater->setInputFocus(TRUE);
	return floater->getSessionID();
}

// Adds a session using the given session_id.  If the session already exists 
// the dialog type is assumed correct. Returns the uuid of the session.
LLUUID LLIMView::addSession(const std::string& name,
							EInstantMessage dialog,
							const LLUUID& other_participant_id,
							const LLDynamicArray<LLUUID>& ids)
{
	if (0 == ids.getLength())
	{
		return LLUUID::null;
	}

	LLUUID session_id = compute_session_id(dialog,
										   other_participant_id);

	LLFloaterIMPanel* floater = findFloaterBySession(session_id);
	if(!floater)
	{
		// On creation, use the first element of ids as the
		// "other_participant_id"
		floater = createFloater(session_id,
								other_participant_id,
								name,
								ids,
								dialog,
								TRUE);

		if ( !floater ) return LLUUID::null;

		noteOfflineUsers(floater, ids);
	}
	mTalkFloater->showFloater(floater);
	//mTabContainer->selectTabPanel(panel);
	floater->setInputFocus(TRUE);
	return floater->getSessionID();
}

// This removes the panel referenced by the uuid, and then restores
// internal consistency. The internal pointer is not deleted.
void LLIMView::removeSession(const LLUUID& session_id)
{
	LLFloaterIMPanel* floater = findFloaterBySession(session_id);
	if(floater)
	{
		mFloaters.erase(floater->getHandle());
		mTalkFloater->removeFloater(floater);
		//mTabContainer->removeTabPanel(floater);
	}

//	if ( session_id.notNull() )
//	{
//		mSessionsDropRequested[session_id.asString()] = LLSD();
//	}
}

void LLIMView::refresh()
{
	S32 old_scroll_pos = mNewIMFloater->getScrollPos();
	mNewIMFloater->clearAllTargets();

	// build a list of groups.
	LLLinkedList<LLGroupData> group_list( group_dictionary_sort );

	LLGroupData* group;
	S32 count = gAgent.mGroups.count();
	S32 i;
	// read/sort groups on the first pass.
	for(i = 0; i < count; ++i)
	{
		group = &(gAgent.mGroups.get(i));
		group_list.addDataSorted( group );
	}

	// add groups to the floater on the second pass.
	for(group = group_list.getFirstData();
		group;
		group = group_list.getNextData())
	{
		mNewIMFloater->addGroup(group->mID, (void*)(&GROUP_DIALOG), TRUE, FALSE);
	}

	// build a set of buddies in the current buddy list.
	LLCollectAllBuddies collector;
	LLAvatarTracker::instance().applyFunctor(collector);
	LLCollectAllBuddies::buddy_map_t::iterator it;
	LLCollectAllBuddies::buddy_map_t::iterator end;
	it = collector.mOnline.begin();
	end = collector.mOnline.end();
	for( ; it != end; ++it)
	{
		mNewIMFloater->addAgent((*it).second, (void*)(&DEFAULT_DIALOG), TRUE);
	}
	it = collector.mOffline.begin();
	end = collector.mOffline.end();
	for( ; it != end; ++it)
	{
		mNewIMFloater->addAgent((*it).second, (void*)(&DEFAULT_DIALOG), FALSE);
	}

	mNewIMFloater->setScrollPos( old_scroll_pos );
}

// JC - This used to set console visibility.  It doesn't any more.
void LLIMView::setFloaterOpen(BOOL set_open)
{
	gSavedSettings.setBOOL("ShowIM", set_open);

	//RN "visible" and "open" are considered synonomous for now
	if (set_open)
	{
		mTalkFloater->open();		/*Flawfinder: ignore*/
	}
	else
	{
		mTalkFloater->close();
	}

	if( set_open )
	{
		// notifyNewIM();

		// We're showing the IM, so mark view as non-pending
		mIMReceived = FALSE;
	}
}


BOOL LLIMView::getFloaterOpen()
{
	return mTalkFloater->getVisible();
}
 
void LLIMView::pruneSessions()
{
	if(mNewIMFloater)
	{
		BOOL removed = TRUE;
		LLFloaterIMPanel* floater = NULL;
		while(removed)
		{
			removed = FALSE;
			std::set<LLViewHandle>::iterator handle_it;
			for(handle_it = mFloaters.begin();
				handle_it != mFloaters.end();
				++handle_it)
			{
				floater = (LLFloaterIMPanel*)LLFloater::getFloaterByHandle(*handle_it);
				if(floater && !mNewIMFloater->isUUIDAvailable(floater->getOtherParticipantID()))
				{
					// remove this floater
					removed = TRUE;
					mFloaters.erase(handle_it++);
					floater->close();
					break;
				}
			}
		}
	}
}


void LLIMView::disconnectAllSessions()
{
	if(mNewIMFloater)
	{
		mNewIMFloater->setEnabled(FALSE);
	}
	LLFloaterIMPanel* floater = NULL;
	std::set<LLViewHandle>::iterator handle_it;
	for(handle_it = mFloaters.begin();
		handle_it != mFloaters.end();
		)
	{
		floater = (LLFloaterIMPanel*)LLFloater::getFloaterByHandle(*handle_it);

		// MUST do this BEFORE calling floater->onClose() because that may remove the item from the set, causing the subsequent increment to crash.
		++handle_it;

		if (floater)
		{
			floater->setEnabled(FALSE);
			floater->onClose(TRUE);
		}
	}
}


// This method returns the im panel corresponding to the uuid
// provided. The uuid can either be a session id or an agent
// id. Returns NULL if there is no matching panel.
LLFloaterIMPanel* LLIMView::findFloaterBySession(const LLUUID& session_id)
{
	LLFloaterIMPanel* rv = NULL;
	std::set<LLViewHandle>::iterator handle_it;
	for(handle_it = mFloaters.begin();
		handle_it != mFloaters.end();
		++handle_it)
	{
		rv = (LLFloaterIMPanel*)LLFloater::getFloaterByHandle(*handle_it);
		if(rv && session_id == rv->getSessionID())
		{
			break;
		}
		rv = NULL;
	}
	return rv;
}


BOOL LLIMView::hasSession(const LLUUID& session_id)
{
	return (findFloaterBySession(session_id) != NULL);
}


// create a floater and update internal representation for
// consistency. Returns the pointer, caller (the class instance since
// it is a private method) is not responsible for deleting the
// pointer.  Add the floater to this but do not select it.
LLFloaterIMPanel* LLIMView::createFloater(
	const LLUUID& session_id,
	const LLUUID& other_participant_id,
	const std::string& session_label,
	EInstantMessage dialog,
	BOOL user_initiated)
{
	if (session_id.isNull())
	{
		llwarns << "Creating LLFloaterIMPanel with null session ID" << llendl;
	}

	llinfos << "LLIMView::createFloater: from " << other_participant_id 
			<< " in session " << session_id << llendl;
	LLFloaterIMPanel* floater = new LLFloaterIMPanel(session_label,
													 LLRect(),
													 session_label,
													 session_id,
													 other_participant_id,
													 dialog);
	LLTabContainerCommon::eInsertionPoint i_pt = user_initiated ? LLTabContainerCommon::RIGHT_OF_CURRENT : LLTabContainerCommon::END;
	mTalkFloater->addFloater(floater, FALSE, i_pt);
	mFloaters.insert(floater->getHandle());
	return floater;
}

LLFloaterIMPanel* LLIMView::createFloater(
	const LLUUID& session_id,
	const LLUUID& other_participant_id,
	const std::string& session_label,
	const LLDynamicArray<LLUUID>& ids,
	EInstantMessage dialog,
	BOOL user_initiated)
{
	if (session_id.isNull())
	{
		llwarns << "Creating LLFloaterIMPanel with null session ID" << llendl;
	}

	llinfos << "LLIMView::createFloater: from " << other_participant_id 
			<< " in session " << session_id << llendl;
	LLFloaterIMPanel* floater = new LLFloaterIMPanel(session_label,
													 LLRect(),
													 session_label,
													 session_id,
													 other_participant_id,
													 ids,
													 dialog);
	LLTabContainerCommon::eInsertionPoint i_pt = user_initiated ? LLTabContainerCommon::RIGHT_OF_CURRENT : LLTabContainerCommon::END;
	mTalkFloater->addFloater(floater, FALSE, i_pt);
	mFloaters.insert(floater->getHandle());
	return floater;
}

void LLIMView::noteOfflineUsers(LLFloaterIMPanel* floater,
								  const LLDynamicArray<LLUUID>& ids)
{
	S32 count = ids.count();
	if(count == 0)
	{
		floater->addHistoryLine(sOnlyUserMessage);
	}
	else
	{
		const LLRelationship* info = NULL;
		LLAvatarTracker& at = LLAvatarTracker::instance();
		for(S32 i = 0; i < count; ++i)
		{
			info = at.getBuddyInfo(ids.get(i));
			char first[DB_FIRST_NAME_BUF_SIZE];		/*Flawfinder: ignore*/
			char last[DB_LAST_NAME_BUF_SIZE];		/*Flawfinder: ignore*/
			if(info && !info->isOnline()
			   && gCacheName->getName(ids.get(i), first, last))
			{
				LLUIString offline = sOfflineMessage;
				offline.setArg("[FIRST]", first);
				offline.setArg("[LAST]", last);
				floater->addHistoryLine(offline);
			}
		}
	}
}

void LLIMView::processIMTypingStart(const LLIMInfo* im_info)
{
	processIMTypingCore(im_info, TRUE);
}

void LLIMView::processIMTypingStop(const LLIMInfo* im_info)
{
	processIMTypingCore(im_info, FALSE);
}

void LLIMView::processIMTypingCore(const LLIMInfo* im_info, BOOL typing)
{
	LLUUID session_id = compute_session_id(im_info->mIMType, im_info->mFromID);
	LLFloaterIMPanel* floater = findFloaterBySession(session_id);
	if (floater)
	{
		floater->processIMTyping(im_info, typing);
	}
}

void LLIMView::updateFloaterSessionID(const LLUUID& old_session_id,
									  const LLUUID& new_session_id)
{
	LLFloaterIMPanel* floater = findFloaterBySession(old_session_id);
	if (floater)
	{
		floater->sessionInitReplyReceived(new_session_id);
	}
}

void LLIMView::onDropRequestReplyReceived(const LLUUID& session_id)
{
	mSessionsDropRequested.erase(session_id.asString());
}

void onConfirmForceCloseError(S32 option, void* data)
{
	//only 1 option really
	LLFloaterIMPanel* floater = ((LLFloaterIMPanel*) data);

	if ( floater ) floater->onClose(FALSE);
}

class LLViewerIMSessionStartReply : public LLHTTPNode
{
public:
	virtual void describe(Description& desc) const
	{
		desc.shortInfo("Used for receiving a reply to a request to initialize an IM session");
		desc.postAPI();
		desc.input(
			"{\"client_session_id\": UUID, \"session_id\": UUID, \"success\" boolean, \"reason\": string");
		desc.source(__FILE__, __LINE__);
	}

	virtual void post(ResponsePtr response,
					  const LLSD& context,
					  const LLSD& input) const
	{
		LLSD body;
		LLUUID temp_session_id;
		LLUUID session_id;
		bool success;

		body = input["body"];
		success = body["success"].asBoolean();
		temp_session_id = body["temp_session_id"].asUUID();

		if ( success )
		{
			session_id = body["session_id"].asUUID();
			gIMView->updateFloaterSessionID(temp_session_id,
											session_id);
		}
		else
		{
			//throw an error dialog and close the temp session's
			//floater
			LLFloaterIMPanel* floater = 
				gIMView->findFloaterBySession(temp_session_id);
			if (floater)
			{
				LLString::format_map_t args;
				args["[REASON]"] =
					sErrorStringsMap[body["error"].asString()];
				args["[RECIPIENT]"] = floater->getTitle();

				gViewerWindow->alertXml("IMSessionStartError",
										args,
										onConfirmForceCloseError,
										floater);

			}
		}
	}
};

class LLViewerIMSessionEventReply : public LLHTTPNode
{
public:
	virtual void describe(Description& desc) const
	{
		desc.shortInfo("Used for receiving a reply to a IM session event");
		desc.postAPI();
		desc.input(
			"{\"event\": string, \"reason\": string, \"success\": boolean, \"session_id\": UUID");
		desc.source(__FILE__, __LINE__);
	}

	virtual void post(ResponsePtr response,
					  const LLSD& context,
					  const LLSD& input) const
	{
		LLUUID session_id;
		bool success;

		LLSD body = input["body"];
		success = body["success"].asBoolean();
		session_id = body["session_id"].asUUID();

		if ( !success )
		{
			//throw an error dialog
			LLFloaterIMPanel* floater = 
				gIMView->findFloaterBySession(session_id);
			if (floater)
			{
				LLString::format_map_t args;
				args["[REASON]"] = 
					sErrorStringsMap[body["error"].asString()];
				args["[EVENT]"] =
					sEventStringsMap[body["event"].asString()];
				args["[RECIPIENT]"] = floater->getTitle();

				gViewerWindow->alertXml("IMSessionEventError",
										args);
			}
		}
	}
};

class LLViewerForceCloseIMSession: public LLHTTPNode
{
public:
	virtual void post(ResponsePtr response,
					  const LLSD& context,
					  const LLSD& input) const
	{
		LLUUID session_id;
		LLString reason;

		session_id = input["body"]["session_id"].asUUID();
		reason = input["body"]["reason"].asString();

		LLFloaterIMPanel* floater =
			gIMView ->findFloaterBySession(session_id);

		if ( floater )
		{
			LLString::format_map_t args;

			args["[NAME]"] = floater->getTitle();
			args["[REASON]"] = sForceCloseSessionMap[reason];

			gViewerWindow->alertXml("ForceCloseIMSession",
									args,
									onConfirmForceCloseError,
									floater);
		}
	}
};

class LLViewerIMSessionDropReply : public LLHTTPNode
{
public:
	virtual void post(ResponsePtr response,
					  const LLSD& context,
					  const LLSD& input) const
	{
		LLUUID session_id;
		bool success;

		success = input["body"]["success"].asBoolean();
		session_id = input["body"]["session_id"].asUUID();

		if ( !success )
		{
			//throw an error alert?
		}

		gIMView->onDropRequestReplyReceived(session_id);
	}
};

LLHTTPRegistration<LLViewerIMSessionStartReply>
   gHTTPRegistrationMessageImsessionstartreply(
	   "/message/IMSessionStartReply");

LLHTTPRegistration<LLViewerIMSessionEventReply>
   gHTTPRegistrationMessageImsessioneventreply(
	   "/message/IMSessionEventReply");

LLHTTPRegistration<LLViewerForceCloseIMSession>
    gHTTPRegistrationMessageForceCloseImSession(
		"/message/ForceCloseIMSession");

LLHTTPRegistration<LLViewerIMSessionDropReply>
    gHTTPRegistrationMessageImSessionDropReply(
		"/message/IMSessionDropReply");