/** 
 * @file llnamelistctrl.cpp
 * @brief A list of names, automatically refreshed from name cache.
 *
 * $LicenseInfo:firstyear=2003&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 "llnamelistctrl.h"

#include <boost/tokenizer.hpp>

#include "llavatarnamecache.h"
#include "llcachename.h"
#include "llfloater.h"
#include "llfloaterreg.h"
#include "llfloatersnapshot.h" // gSnapshotFloaterView
#include "llinventory.h"
#include "llscrolllistitem.h"
#include "llscrolllistcell.h"
#include "llscrolllistcolumn.h"
#include "llsdparam.h"
#include "lltooltip.h"
#include "lltrans.h"

static LLDefaultChildRegistry::Register<LLNameListCtrl> r("name_list");

static const S32 info_icon_size = 16;

void LLNameListCtrl::NameTypeNames::declareValues()
{
	declare("INDIVIDUAL", LLNameListCtrl::INDIVIDUAL);
	declare("GROUP", LLNameListCtrl::GROUP);
	declare("SPECIAL", LLNameListCtrl::SPECIAL);
}

LLNameListCtrl::Params::Params()
:	name_column(""),
	allow_calling_card_drop("allow_calling_card_drop", false),
	short_names("short_names", false)
{
}

LLNameListCtrl::LLNameListCtrl(const LLNameListCtrl::Params& p)
:	LLScrollListCtrl(p),
	mNameColumnIndex(p.name_column.column_index),
	mNameColumn(p.name_column.column_name),
	mAllowCallingCardDrop(p.allow_calling_card_drop),
	mShortNames(p.short_names),
	mPendingLookupsRemaining(0)
{}

// public
LLScrollListItem* LLNameListCtrl::addNameItem(const LLUUID& agent_id, EAddPosition pos,
								 BOOL enabled, const std::string& suffix, const std::string& prefix)
{
	//LL_INFOS() << "LLNameListCtrl::addNameItem " << agent_id << LL_ENDL;

	NameItem item;
	item.value = agent_id;
	item.enabled = enabled;
	item.target = INDIVIDUAL;

	return addNameItemRow(item, pos, suffix, prefix);
}

// virtual, public
BOOL LLNameListCtrl::handleDragAndDrop( 
		S32 x, S32 y, MASK mask,
		BOOL drop,
		EDragAndDropType cargo_type, void *cargo_data, 
		EAcceptance *accept,
		std::string& tooltip_msg)
{
	if (!mAllowCallingCardDrop)
	{
		return FALSE;
	}

	BOOL handled = FALSE;

	if (cargo_type == DAD_CALLINGCARD)
	{
		if (drop)
		{
			LLInventoryItem* item = (LLInventoryItem *)cargo_data;
			addNameItem(item->getCreatorUUID());
		}

		*accept = ACCEPT_YES_MULTI;
	}
	else
	{
		*accept = ACCEPT_NO;
		if (tooltip_msg.empty())
		{
			if (!getToolTip().empty())
			{
				tooltip_msg = getToolTip();
			}
			else
			{
				// backwards compatable English tooltip (should be overridden in xml)
				tooltip_msg.assign("Drag a calling card here\nto add a resident.");
			}
		}
	}

	handled = TRUE;
	LL_DEBUGS("UserInput") << "dragAndDrop handled by LLNameListCtrl " << getName() << LL_ENDL;

	return handled;
}

void LLNameListCtrl::showInspector(const LLUUID& avatar_id, bool is_group, bool is_experience)
{
	if(is_experience)
	{
		LLFloaterReg::showInstance("experience_profile", avatar_id, true);
		return;
	}

	if (is_group)
		LLFloaterReg::showInstance("inspect_group", LLSD().with("group_id", avatar_id));
	else
		LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", avatar_id));
}

void	LLNameListCtrl::mouseOverHighlightNthItem( S32 target_index )
{
	S32 cur_index = getHighlightedItemInx();
	if (cur_index != target_index)
	{
		bool is_mouse_over_name_cell = false;

		S32 mouse_x, mouse_y;
		LLUI::getInstance()->getMousePositionLocal(this, &mouse_x, &mouse_y);

		S32 column_index = getColumnIndexFromOffset(mouse_x);
		LLScrollListItem* hit_item = hitItem(mouse_x, mouse_y);
		if (hit_item && column_index == mNameColumnIndex)
		{
			// Get the name cell which is currently under the mouse pointer.
			LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
			if (hit_cell)
			{
				is_mouse_over_name_cell = getCellRect(cur_index, column_index).pointInRect(mouse_x, mouse_y);
			}
		}

		// If the tool tip is visible and the mouse is over the currently highlighted item's name cell,
		// we should not reset the highlighted item index i.e. set mHighlightedItem = -1
		// and should not increase the width of the text inside the cell because it may
		// overlap the tool tip icon.
		if (LLToolTipMgr::getInstance()->toolTipVisible() && is_mouse_over_name_cell)
			return;

		if(0 <= cur_index && cur_index < (S32)getItemList().size())
		{
			LLScrollListItem* item = getItemList()[cur_index];
			if (item)
			{
				LLScrollListText* cell = dynamic_cast<LLScrollListText*>(item->getColumn(mNameColumnIndex));
				if (cell)
					cell->setTextWidth(cell->getTextWidth() + info_icon_size);
			}
			else
			{
				LL_WARNS() << "highlighted name list item is NULL" << LL_ENDL;
			}
		}
		if(target_index != -1)
		{
			LLScrollListItem* item = getItemList()[target_index];
			LLScrollListText* cell = dynamic_cast<LLScrollListText*>(item->getColumn(mNameColumnIndex));
			if (item)
			{
				if (cell)
					cell->setTextWidth(cell->getTextWidth() - info_icon_size);
			}
			else
			{
				LL_WARNS() << "target name item is NULL" << LL_ENDL;
			}
		}
	}

	LLScrollListCtrl::mouseOverHighlightNthItem(target_index);
}

//virtual
BOOL LLNameListCtrl::handleToolTip(S32 x, S32 y, MASK mask)
{
	BOOL handled = FALSE;
	S32 column_index = getColumnIndexFromOffset(x);
	LLNameListItem* hit_item = dynamic_cast<LLNameListItem*>(hitItem(x, y));
	LLFloater* floater = gFloaterView->getParentFloater(this);
	if (floater 
		&& floater->isFrontmost()
		&& hit_item
		&& column_index == mNameColumnIndex)
	{
		// ...this is the column with the avatar name
		LLUUID avatar_id = hit_item->getUUID();
		if (avatar_id.notNull())
		{
			// ...valid avatar id

			LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
			if (hit_cell)
			{
				S32 row_index = getItemIndex(hit_item);
				LLRect cell_rect = getCellRect(row_index, column_index);
				// Convert rect local to screen coordinates
				LLRect sticky_rect;
				localRectToScreen(cell_rect, &sticky_rect);

				// Spawn at right side of cell
				LLPointer<LLUIImage> icon = LLUI::getUIImage("Info_Small");
				S32 screenX = sticky_rect.mRight - info_icon_size;
				S32 screenY = sticky_rect.mTop - (sticky_rect.getHeight() - icon->getHeight()) / 2;
				LLCoordGL pos(screenX, screenY);

				LLFloater* snapshot_floatr = gSnapshotFloaterView->getFrontmostClosableFloater();
				if (!snapshot_floatr || !snapshot_floatr->getRect().pointInRect(screenX + icon->getWidth(), screenY))
				{
					// Should we show a group or an avatar inspector?
					bool is_group = hit_item->isGroup();
					bool is_experience = hit_item->isExperience();

					LLToolTip::Params params;
					params.background_visible(false);
					params.click_callback(boost::bind(&LLNameListCtrl::showInspector, this, avatar_id, is_group, is_experience));
					params.delay_time(0.0f);		// spawn instantly on hover
					params.image(icon);
					params.message("");
					params.padding(0);
					params.pos(pos);
					params.sticky_rect(sticky_rect);

					LLToolTipMgr::getInstance()->show(params);
					handled = TRUE;
				}
			}
		}
	}
	if (!handled)
	{
		handled = LLScrollListCtrl::handleToolTip(x, y, mask);
	}
	return handled;
}

// public
void LLNameListCtrl::addGroupNameItem(const LLUUID& group_id, EAddPosition pos,
									  BOOL enabled)
{
	NameItem item;
	item.value = group_id;
	item.enabled = enabled;
	item.target = GROUP;

	addNameItemRow(item, pos);
}

// public
void LLNameListCtrl::addGroupNameItem(LLNameListCtrl::NameItem& item, EAddPosition pos)
{
	item.target = GROUP;
	addNameItemRow(item, pos);
}

LLScrollListItem* LLNameListCtrl::addNameItem(LLNameListCtrl::NameItem& item, EAddPosition pos)
{
	item.target = INDIVIDUAL;
	return addNameItemRow(item, pos);
}

LLScrollListItem* LLNameListCtrl::addElement(const LLSD& element, EAddPosition pos, void* userdata)
{
	LLNameListCtrl::NameItem item_params;
	LLParamSDParser parser;
	parser.readSD(element, item_params);
	item_params.userdata = userdata;
	return addNameItemRow(item_params, pos);
}


LLScrollListItem* LLNameListCtrl::addNameItemRow(
	const LLNameListCtrl::NameItem& name_item,
	EAddPosition pos,
	const std::string& suffix,
	const std::string& prefix)
{
	LLUUID id = name_item.value().asUUID();
	LLNameListItem* item = new LLNameListItem(name_item,name_item.target() == GROUP, name_item.target() == EXPERIENCE);

	if (!item) return NULL;

	LLScrollListCtrl::addRow(item, name_item, pos);

	// use supplied name by default
	std::string fullname = name_item.name;
	switch(name_item.target)
	{
	case GROUP:
		if (!gCacheName->getGroupName(id, fullname))
		{
			avatar_name_cache_connection_map_t::iterator it = mGroupNameCacheConnections.find(id);
			if (it != mGroupNameCacheConnections.end())
			{
				if (it->second.connected())
				{
					it->second.disconnect();
				}
				mGroupNameCacheConnections.erase(it);
			}
			mGroupNameCacheConnections[id] = gCacheName->getGroup(id, boost::bind(&LLNameListCtrl::onGroupNameCache, this, _1, _2, item->getHandle()));
		}
		break;
	case SPECIAL:
		// just use supplied name
		break;
	case INDIVIDUAL:
		{
			LLAvatarName av_name;
			if (id.isNull())
			{
				fullname = LLTrans::getString("AvatarNameNobody");
			}
			else if (LLAvatarNameCache::get(id, &av_name))
			{
				if (mShortNames)
					fullname = av_name.getDisplayName();
				else
					fullname = av_name.getCompleteName();
			}
			else
			{
				// ...schedule a callback
				avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(id);
				if (it != mAvatarNameCacheConnections.end())
				{
					if (it->second.connected())
					{
						it->second.disconnect();
					}
					mAvatarNameCacheConnections.erase(it);
				}
				mAvatarNameCacheConnections[id] = LLAvatarNameCache::get(id,boost::bind(&LLNameListCtrl::onAvatarNameCache,this, _1, _2, suffix, prefix, item->getHandle()));

				if(mPendingLookupsRemaining <= 0)
				{
					// BAKER TODO:
					// We might get into a state where mPendingLookupsRemaining might
					//	go negative.  So just reset it right now and figure out if it's
					//	possible later :)
					mPendingLookupsRemaining = 0;
					mNameListCompleteSignal(false);
				}
				mPendingLookupsRemaining++;
			}
			break;
		}
	case EXPERIENCE:
		// just use supplied name
	default:
		break;
	}
	
	// Append optional suffix.
	if (!suffix.empty())
	{
		fullname.append(suffix);
	}

	LLScrollListCell* cell = item->getColumn(mNameColumnIndex);
	if (cell)
	{
		cell->setValue(prefix + fullname);
		cell->setAltValue(name_item.alt_value());
	}

	dirtyColumns();

	// this column is resizable
	LLScrollListColumn* columnp = getColumn(mNameColumnIndex);
	if (columnp && columnp->mHeader)
	{
		columnp->mHeader->setHasResizableElement(TRUE);
	}

	return item;
}

// public
void LLNameListCtrl::removeNameItem(const LLUUID& agent_id)
{
	// Find the item specified with agent_id.
	S32 idx = -1;
	for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++)
	{
		LLScrollListItem* item = *it;
		if (item->getUUID() == agent_id)
		{
			idx = getItemIndex(item);
			break;
		}
	}

	// Remove it.
	if (idx >= 0)
	{
		selectNthItem(idx); // not sure whether this is needed, taken from previous implementation
		deleteSingleItem(idx);

		mPendingLookupsRemaining--;
	}
}

// public
LLScrollListItem* LLNameListCtrl::getNameItemByAgentId(const LLUUID& agent_id)
{
	for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++)
	{
		LLScrollListItem* item = *it;
		if (item && item->getUUID() == agent_id)
		{
			return item;
		}
	}
	return NULL;
}

void LLNameListCtrl::onAvatarNameCache(const LLUUID& agent_id,
									   const LLAvatarName& av_name,
									   std::string suffix,
									   std::string prefix,
									   LLHandle<LLNameListItem> item)
{
	avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(agent_id);
	if (it != mAvatarNameCacheConnections.end())
	{
		if (it->second.connected())
		{
			it->second.disconnect();
		}
		mAvatarNameCacheConnections.erase(it);
	}

	std::string name;
	if (mShortNames)
		name = av_name.getDisplayName();
	else
		name = av_name.getCompleteName();

	// Append optional suffix.
	if (!suffix.empty())
	{
		name.append(suffix);
	}

	if (!prefix.empty())
	{
	    name.insert(0, prefix);
	}

	LLNameListItem* list_item = item.get();
	if (list_item && list_item->getUUID() == agent_id)
	{
		LLScrollListCell* cell = list_item->getColumn(mNameColumnIndex);
		if (cell)
		{
			cell->setValue(name);
			setNeedsSort();
		}
	}
	
	//////////////////////////////////////////////////////////////////////////
	// BAKER - FIX NameListCtrl
 	//if (mPendingLookupsRemaining <= 0)
 	{
 		// We might get into a state where mPendingLookupsRemaining might
 		//	go negative.  So just reset it right now and figure out if it's
 		//	possible later :)
 		//mPendingLookupsRemaining = 0;
		
 		mNameListCompleteSignal(true);
 	}
 	//else
 	{
 	//	mPendingLookupsRemaining--;
 	}
	//////////////////////////////////////////////////////////////////////////

	dirtyColumns();
}

void LLNameListCtrl::onGroupNameCache(const LLUUID& group_id, const std::string name, LLHandle<LLNameListItem> item)
{
	avatar_name_cache_connection_map_t::iterator it = mGroupNameCacheConnections.find(group_id);
	if (it != mGroupNameCacheConnections.end())
	{
		if (it->second.connected())
		{
			it->second.disconnect();
		}
		mGroupNameCacheConnections.erase(it);
	}

	LLNameListItem* list_item = item.get();
	if (list_item && list_item->getUUID() == group_id)
	{
		LLScrollListCell* cell = list_item->getColumn(mNameColumnIndex);
		if (cell)
		{
			cell->setValue(name);
			setNeedsSort();
		}
	}

	dirtyColumns();
}

void LLNameListCtrl::updateColumns(bool force_update)
{
	LLScrollListCtrl::updateColumns(force_update);

	if (!mNameColumn.empty())
	{
		LLScrollListColumn* name_column = getColumn(mNameColumn);
		if (name_column)
		{
			mNameColumnIndex = name_column->mIndex;
		}
	}
}

void LLNameListCtrl::sortByName(BOOL ascending)
{
	sortByColumnIndex(mNameColumnIndex,ascending);
}