/** 
 * @file llsyswellwindow.cpp
 * @brief                                    // TODO
 * $LicenseInfo:firstyear=2000&license=viewergpl$
 * 
 * Copyright (c) 2000-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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.
 * $/LicenseInfo$
 */


#include "llviewerprecompiledheaders.h" // must be first include

#include "llflatlistview.h"

#include "llsyswellwindow.h"

#include "llbottomtray.h"
#include "llviewercontrol.h"
#include "llviewerwindow.h"

#include "llchiclet.h"
#include "lltoastpanel.h"
#include "llnotificationmanager.h"


// IM session ID can be the same as Avatar UUID. (See LLIMMgr::computeSessionID)
// Probably notification ID also can be the same as Avatar UUID.
// In case when session ID & notification ID are the same it will be impossible to add both 
// appropriate Items into Flat List.
// Functions below are intended to wrap passed LLUUID into LLSD value with different "type".
// Use them anywhere you need to add, get, remove items via the list
inline
LLSD get_notification_value(const LLUUID& notification_id)
{
	return LLSD()
		.insert("type", "notification")
		.insert("uuid", notification_id);
}

inline
LLSD get_session_value(const LLUUID& session_id)
{
	return LLSD()
		.insert("type", "im_chiclet")
		.insert("uuid", session_id);
}


//---------------------------------------------------------------------------------
LLSysWellWindow::LLSysWellWindow(const LLSD& key) : LLDockableFloater(NULL, key),
													mChannel(NULL),
													mMessageList(NULL),
													mSeparator(NULL)
{
	LLIMMgr::getInstance()->addSessionObserver(this);
	LLIMChiclet::sFindChicletsSignal.connect(boost::bind(&LLSysWellWindow::findIMChiclet, this, _1));

	mTypedItemsCount[IT_NOTIFICATION] = 0;
	mTypedItemsCount[IT_INSTANT_MESSAGE] = 0;
}

//---------------------------------------------------------------------------------
BOOL LLSysWellWindow::postBuild()
{
	mMessageList = getChild<LLFlatListView>("notification_list");

	// init connections to the list's update events
	connectListUpdaterToSignal("notify");
	connectListUpdaterToSignal("groupnotify");

	// get a corresponding channel
	initChannel();

	LLPanel::Params params;
	mSeparator = LLUICtrlFactory::create<LLPanel>(params);
	LLUICtrlFactory::instance().buildPanel(mSeparator, "panel_separator.xml");

	LLRect rc = mSeparator->getRect();
	rc.setOriginAndSize(0, 0, mMessageList->getItemsRect().getWidth(), rc.getHeight());
	mSeparator->setRect(rc);
	mSeparator->setFollows(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_TOP);
	mSeparator->setVisible(FALSE);

	mMessageList->addItem(mSeparator);

	return LLDockableFloater::postBuild();
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::setMinimized(BOOL minimize)
{
	// we don't show empty Message Well window
	setVisible(!minimize && !isWindowEmpty());

	LLFloater::setMinimized(minimize);
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::connectListUpdaterToSignal(std::string notification_type)
{
	LLNotificationsUI::LLNotificationManager* manager = LLNotificationsUI::LLNotificationManager::getInstance();
	LLNotificationsUI::LLEventHandler* n_handler = manager->getHandlerForNotification(notification_type);
	if(n_handler)
	{
		n_handler->setNotificationIDCallback(boost::bind(&LLSysWellWindow::removeItemByID, this, _1));
	}
	else
	{
		llwarns << "LLSysWellWindow::connectListUpdaterToSignal() - could not get a handler for '" << notification_type <<"' type of notifications" << llendl;
	}
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::onChicletClick()
{
	// 1 - remove StartUp toast and channel if present
	if(!LLNotificationsUI::LLScreenChannel::getStartUpToastShown())
	{
		LLNotificationsUI::LLChannelManager::getInstance()->onStartUpToastClose();
	}

	// 2 - toggle instance of SysWell's chiclet-window
	toggleWindow();
}

//---------------------------------------------------------------------------------
LLSysWellWindow::~LLSysWellWindow()
{
	LLIMMgr::getInstance()->removeSessionObserver(this);
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::addItem(LLSysWellItem::Params p)
{
	LLSD value = get_notification_value(p.notification_id);
	// do not add clones
	if( mMessageList->getItemByValue(value))
		return;

	LLSysWellItem* new_item = new LLSysWellItem(p);
	if (mMessageList->addItem(new_item, value, ADD_TOP))
	{
		handleItemAdded(IT_NOTIFICATION);

		reshapeWindow();

		new_item->setOnItemCloseCallback(boost::bind(&LLSysWellWindow::onItemClose, this, _1));
		new_item->setOnItemClickCallback(boost::bind(&LLSysWellWindow::onItemClick, this, _1));
	}
	else
	{
		llwarns << "Unable to add Notification into the list, notification ID: " << p.notification_id
			<< ", title: " << p.title
			<< llendl;

		new_item->die();
	}
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::clear()
{
	mMessageList->clear();
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::removeItemByID(const LLUUID& id)
{
	if(mMessageList->removeItemByValue(get_notification_value(id)))
	{
		handleItemRemoved(IT_NOTIFICATION);
		reshapeWindow();
	}
	else
	{
		llwarns << "Unable to remove notification from the list, ID: " << id
			<< llendl;
	}

	// hide chiclet window if there are no items left
	if(isWindowEmpty())
	{
		setVisible(FALSE);
	}
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::onItemClick(LLSysWellItem* item)
{
	LLUUID id = item->getID();
	if(mChannel)
		mChannel->loadStoredToastByNotificationIDToChannel(id);
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::onItemClose(LLSysWellItem* item)
{
	LLUUID id = item->getID();
	removeItemByID(id);
	if(mChannel)
		mChannel->killToastByNotificationID(id);
}

//--------------------------------------------------------------------------
void LLSysWellWindow::onStoreToast(LLPanel* info_panel, LLUUID id)
{
	LLSysWellItem::Params p;	
	p.notification_id = id;
	p.title = static_cast<LLToastPanel*>(info_panel)->getTitle();
	addItem(p);
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::initChannel() 
{
	LLNotificationsUI::LLScreenChannelBase* channel = LLNotificationsUI::LLChannelManager::getInstance()->findChannelByID(
																LLUUID(gSavedSettings.getString("NotificationChannelUUID")));
	mChannel = dynamic_cast<LLNotificationsUI::LLScreenChannel*>(channel);
	if(mChannel)
	{
		mChannel->setOnStoreToastCallback(boost::bind(&LLSysWellWindow::onStoreToast, this, _1, _2));
	}
	else
	{
		llwarns << "LLSysWellWindow::initChannel() - could not get a requested screen channel" << llendl;
	}
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::getAllowedRect(LLRect& rect)
{
	rect = gViewerWindow->getWorldViewRect();
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::toggleWindow()
{
	if (getDockControl() == NULL)
	{
		setDockControl(new LLDockControl(
				LLBottomTray::getInstance()->getSysWell(), this,
				getDockTongue(), LLDockControl::TOP, boost::bind(&LLSysWellWindow::getAllowedRect, this, _1)));
	}

	if(!getVisible())
	{
		if(mChannel)
		{
			mChannel->removeAndStoreAllStorableToasts();
		}
		if(isWindowEmpty())
		{
			return;
		}

		setVisible(TRUE);
	}
	else
	{
		setVisible(FALSE);
	}
	//set window in foreground
	setFocus(getVisible());
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::setVisible(BOOL visible)
{
	if(visible)
	{
		if (LLBottomTray::instanceExists())
		{
			LLBottomTray::getInstance()->getSysWell()->setToggleState(TRUE);
		}
	}
	else
	{
		if (LLBottomTray::instanceExists())
		{
			LLBottomTray::getInstance()->getSysWell()->setToggleState(FALSE);
		}
	}

	LLDockableFloater::setVisible(visible);

	// update notification channel state	
	if(mChannel)
	{
		mChannel->updateShowToastsState();
	}
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::setDocked(bool docked, bool pop_on_undock)
{
	LLDockableFloater::setDocked(docked, pop_on_undock);

	// update notification channel state
	if(mChannel)
	{
		mChannel->updateShowToastsState();
	}
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::reshapeWindow()
{
	// save difference between floater height and the list height to take it into account while calculating new window height
	// it includes height from floater top to list top and from floater bottom and list bottom
	static S32 parent_list_delta_height = getRect().getHeight() - mMessageList->getRect().getHeight();

	S32 notif_list_height = mMessageList->getItemsRect().getHeight() + 2 * mMessageList->getBorderWidth();

	LLRect curRect = getRect();

	S32 new_window_height = notif_list_height + parent_list_delta_height;

	if (new_window_height > MAX_WINDOW_HEIGHT)
	{
		new_window_height = MAX_WINDOW_HEIGHT;
	}
	S32 newY = curRect.mTop + new_window_height - curRect.getHeight();
	curRect.setLeftTopAndSize(curRect.mLeft, newY, MIN_WINDOW_WIDTH, new_window_height);
	reshape(curRect.getWidth(), curRect.getHeight(), TRUE);
	setRect(curRect);
}

//---------------------------------------------------------------------------------
LLChiclet* LLSysWellWindow::findIMChiclet(const LLUUID& sessionId)
{
	LLChiclet* res = NULL;
	RowPanel* panel = mMessageList->getTypedItemByValue<RowPanel>(get_session_value(sessionId));
	if (panel != NULL)
	{
		res = panel->mChiclet;
	}

	return res;
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::addIMRow(const LLUUID& sessionId, S32 chicletCounter,
		const std::string& name, const LLUUID& otherParticipantId)
{
	RowPanel* item = new RowPanel(this, sessionId, chicletCounter, name, otherParticipantId);
	if (mMessageList->insertItemAfter(mSeparator, item, get_session_value(sessionId)))
	{
		handleItemAdded(IT_INSTANT_MESSAGE);
	}
	else
	{
		llwarns << "Unable to add IM Row into the list, sessionID: " << sessionId
			<< ", name: " << name
			<< ", other participant ID: " << otherParticipantId
			<< llendl;

		item->die();
	}
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::delIMRow(const LLUUID& sessionId)
{
	if (mMessageList->removeItemByValue(get_session_value(sessionId)))
	{
		handleItemRemoved(IT_INSTANT_MESSAGE);
	}
	else
	{
		llwarns << "Unable to remove IM Row from the list, sessionID: " << sessionId
			<< llendl;
	}

	// remove all toasts that belong to this session from a screen
	if(mChannel)
		mChannel->removeToastsBySessionID(sessionId);

	// hide chiclet window if there are no items left
	if(isWindowEmpty())
	{
		setVisible(FALSE);
	}
}

//---------------------------------------------------------------------------------
bool LLSysWellWindow::isWindowEmpty()
{
	// keep in mind, mSeparator is always in the list
	return mMessageList->size() == 1;
}

//---------------------------------------------------------------------------------
//virtual
void LLSysWellWindow::sessionAdded(const LLUUID& session_id,
		const std::string& name, const LLUUID& other_participant_id)
{
	//*TODO get rid of get_session_value, session_id's are unique, cause performance degradation with lots chiclets (IB)
	if (mMessageList->getItemByValue(get_session_value(session_id)) == NULL)
	{
		S32 chicletCounter = LLIMModel::getInstance()->getNumUnread(session_id);
		if (chicletCounter > -1)
		{
			addIMRow(session_id, chicletCounter, name, other_participant_id);	
			reshapeWindow();
		}
	}
}

//---------------------------------------------------------------------------------
//virtual
void LLSysWellWindow::sessionRemoved(const LLUUID& sessionId)
{
	delIMRow(sessionId);
	reshapeWindow();
	LLBottomTray::getInstance()->getSysWell()->updateUreadIMNotifications();
}

void LLSysWellWindow::handleItemAdded(EItemType added_item_type)
{
	bool should_be_shown = ++mTypedItemsCount[added_item_type] == 1 && anotherTypeExists(added_item_type);

	if (should_be_shown && !mSeparator->getVisible())
	{
		mSeparator->setVisible(TRUE);

		// refresh list to recalculate mSeparator position
		mMessageList->reshape(mMessageList->getRect().getWidth(), mMessageList->getRect().getHeight());
	}
}

void LLSysWellWindow::handleItemRemoved(EItemType removed_item_type)
{
	bool should_be_hidden = --mTypedItemsCount[removed_item_type] == 0;

	if (should_be_hidden && mSeparator->getVisible())
	{
		mSeparator->setVisible(FALSE);

		// refresh list to recalculate mSeparator position
		mMessageList->reshape(mMessageList->getRect().getWidth(), mMessageList->getRect().getHeight());
	}
}

bool LLSysWellWindow::anotherTypeExists(EItemType item_type)
{
	bool exists = false;
	switch(item_type)
	{
	case IT_INSTANT_MESSAGE:
		if (mTypedItemsCount[IT_NOTIFICATION] > 0)
		{
			exists = true;
		}
		break;
	case IT_NOTIFICATION:
		if (mTypedItemsCount[IT_INSTANT_MESSAGE] > 0)
		{
			exists = true;
		}
		break;
	}
	return exists;
}

//---------------------------------------------------------------------------------
LLSysWellWindow::RowPanel::RowPanel(const LLSysWellWindow* parent, const LLUUID& sessionId,
		S32 chicletCounter, const std::string& name, const LLUUID& otherParticipantId) :
		LLPanel(LLPanel::Params()), mChiclet(NULL), mParent(parent)
{
	LLUICtrlFactory::getInstance()->buildPanel(this, "panel_activeim_row.xml", NULL);

	// Choose which of the pre-created chiclets (IM/group) to use.
	// The other one gets hidden.

	LLIMChiclet::EType im_chiclet_type = LLIMChiclet::getIMSessionType(sessionId);
	switch (im_chiclet_type)
	{
	case LLIMChiclet::TYPE_GROUP:
	case LLIMChiclet::TYPE_AD_HOC:
		mChiclet = getChild<LLIMChiclet>("group_chiclet");
		childSetVisible("p2p_chiclet", false);
		break;
	case LLIMChiclet::TYPE_UNKNOWN: // assign mChiclet a non-null value anyway
	case LLIMChiclet::TYPE_IM:
		mChiclet = getChild<LLIMChiclet>("p2p_chiclet");
		childSetVisible("group_chiclet", false);
		break;
	}

	// Initialize chiclet.
	mChiclet->setCounter(chicletCounter);
	mChiclet->setSessionId(sessionId);
	mChiclet->setIMSessionName(name);
	mChiclet->setOtherParticipantId(otherParticipantId);

	LLTextBox* contactName = getChild<LLTextBox>("contact_name");
	contactName->setValue(name);

	mCloseBtn = getChild<LLButton>("hide_btn");
	mCloseBtn->setCommitCallback(boost::bind(&LLSysWellWindow::RowPanel::onClose, this));
}

//---------------------------------------------------------------------------------
LLSysWellWindow::RowPanel::~RowPanel()
{
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::RowPanel::onClose()
{
	gIMMgr->removeSession(mChiclet->getSessionId());
	// This row panel will be removed from the list in LLSysWellWindow::sessionRemoved().
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::RowPanel::onMouseEnter(S32 x, S32 y, MASK mask)
{
	setTransparentColor(LLUIColorTable::instance().getColor("SysWellItemSelected"));
}

//---------------------------------------------------------------------------------
void LLSysWellWindow::RowPanel::onMouseLeave(S32 x, S32 y, MASK mask)
{
	setTransparentColor(LLUIColorTable::instance().getColor("SysWellItemUnselected"));
}

//---------------------------------------------------------------------------------
// virtual
BOOL LLSysWellWindow::RowPanel::handleMouseDown(S32 x, S32 y, MASK mask)
{
	// Pass the mouse down event to the chiclet (EXT-596).
	if (!mChiclet->pointInView(x, y) && !mCloseBtn->getRect().pointInRect(x, y)) // prevent double call of LLIMChiclet::onMouseDown()
		mChiclet->onMouseDown();

	return LLPanel::handleMouseDown(x, y, mask);
}

// EOF
