/**
 * @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 constexpr 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),
    mHoverIconName("Info_Small"),
    mNameListType(INDIVIDUAL)
{}

// 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 (isSpecialType())
    {
        mIconClickedSignal(avatar_id);
        return;
    }
    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) || isSpecialType()))
    {
        // ...this is the column with the avatar name
        LLUUID item_id = isSpecialType() ? hit_item->getSpecialID() : hit_item->getUUID();
        if (item_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, isSpecialType() ? getNumColumns() - 1 : 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(mHoverIconName);
                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, item_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;
}

// virtual
bool LLNameListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
    LLNameListItem* hit_item = dynamic_cast<LLNameListItem*>(hitItem(x, y));
    LLFloater* floater = gFloaterView->getParentFloater(this);
    if (floater && floater->isFrontmost() && hit_item)
    {
        if(hit_item->isGroup())
        {
            ContextMenuType prev_menu = getContextMenuType();
            setContextMenu(MENU_GROUP);
            bool handled = LLScrollListCtrl::handleRightMouseDown(x, y, mask);
            setContextMenu(prev_menu);
            return handled;
        }
    }
    return LLScrollListCtrl::handleRightMouseDown(x, y, mask);
}

// 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:
        {
        item->setSpecialID(name_item.special_id());
        return item;
        }
    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(true);
                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;
        LLUUID cur_id = isSpecialType() ? dynamic_cast<LLNameListItem*>(item)->getSpecialID() : item->getUUID();
        if (cur_id == 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::selectItemBySpecialId(const LLUUID& special_id)
{
    if (special_id.isNull())
    {
        return;
    }

    for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++)
    {
        LLNameListItem* item = dynamic_cast<LLNameListItem*>(*it);
        if (item && item->getSpecialID() == special_id)
        {
            item->setSelected(true);
            break;
        }
    }
}

LLUUID LLNameListCtrl::getSelectedSpecialId()
{
    LLNameListItem* item = dynamic_cast<LLNameListItem*>(getFirstSelected());
    if(item)
    {
        return item->getSpecialID();
    }
    return LLUUID();
}

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);
}