/** 
 * @file llpanelgroupinvite.cpp
 *
 * $LicenseInfo:firstyear=2006&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 "llpanelgroupinvite.h"

#include "llagent.h"
#include "llavatarnamecache.h"
#include "llfloateravatarpicker.h"
#include "llbutton.h"
#include "llcallingcard.h"
#include "llcombobox.h"
#include "llgroupactions.h"
#include "llgroupmgr.h"
#include "llnamelistctrl.h"
#include "llnotificationsutil.h"
#include "llscrolllistitem.h"
#include "llspinctrl.h"
#include "lltextbox.h"
#include "llviewerobject.h"
#include "llviewerobjectlist.h"
#include "lluictrlfactory.h"
#include "llviewerwindow.h"

class LLPanelGroupInvite::impl
{
public:
	impl(const LLUUID& group_id);
	~impl();

	void addUsers(const std::vector<std::string>& names,
				  const uuid_vec_t& agent_ids);
	void submitInvitations();
	void addRoleNames(LLGroupMgrGroupData* gdatap);
	void handleRemove();
	void handleSelection();

	static void callbackClickCancel(void* userdata);
	static void callbackClickOK(void* userdata);
	static void callbackClickAdd(void* userdata);
	static void callbackClickRemove(void* userdata);
	static void callbackSelect(LLUICtrl* ctrl, void* userdata);
	static void callbackAddUsers(const uuid_vec_t& agent_ids,
								 void* user_data);
	
	static void onAvatarNameCache(const LLUUID& agent_id,
											 const LLAvatarName& av_name,
											 void* user_data);

	bool inviteOwnerCallback(const LLSD& notification, const LLSD& response);

public:
	LLUUID mGroupID;

	std::string		mLoadingText;
	LLNameListCtrl	*mInvitees;
	LLComboBox      *mRoleNames;
	LLButton		*mOKButton;
 	LLButton		*mRemoveButton;
	LLTextBox		*mGroupName;
	std::string		mOwnerWarning;
	std::string		mAlreadyInGroup;
	std::string		mTooManySelected;
	bool		mConfirmedOwnerInvite;
	std::set<LLUUID>	mInviteeIDs;

	void (*mCloseCallback)(void* data);

	void* mCloseCallbackUserData;

	boost::signals2::connection mAvatarNameCacheConnection;
};


LLPanelGroupInvite::impl::impl(const LLUUID& group_id):
	mGroupID( group_id ),
	mLoadingText (),
	mInvitees ( NULL ),
	mRoleNames( NULL ),
	mOKButton ( NULL ),
	mRemoveButton( NULL ),
	mGroupName( NULL ),
	mConfirmedOwnerInvite( false ),
	mCloseCallback( NULL ),
	mCloseCallbackUserData( NULL ),
	mAvatarNameCacheConnection()
{
}

LLPanelGroupInvite::impl::~impl()
{
	if (mAvatarNameCacheConnection.connected())
	{
		mAvatarNameCacheConnection.disconnect();
	}
}

const S32 MAX_GROUP_INVITES = 100; // Max invites per request. 100 to match server cap.

void LLPanelGroupInvite::impl::addUsers(const std::vector<std::string>& names,
										const uuid_vec_t& agent_ids)
{
	std::string name;
	LLUUID id;

	if (names.size() + mInviteeIDs.size() > MAX_GROUP_INVITES)
	{
		// Fail! Show a warning and don't add any names.
		LLSD msg;
		msg["MESSAGE"] = mTooManySelected;
		LLNotificationsUtil::add("GenericAlert", msg);
		return;
	}

	for (S32 i = 0; i < (S32)names.size(); i++)
	{
		name = names[i];
		id = agent_ids[i];

		// Make sure this agent isn't already in the list.
		if (mInviteeIDs.find(id) != mInviteeIDs.end())
		{
			continue;
		}

		//add the name to the names list
		LLSD row;
		row["id"] = id;
		row["columns"][0]["value"] = name;

		mInvitees->addElement(row);
		mInviteeIDs.insert(id);
	}
}

void LLPanelGroupInvite::impl::submitInvitations()
{
	std::map<LLUUID, LLUUID> role_member_pairs;

	LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID);

	// Default to everyone role.
	LLUUID role_id = LLUUID::null;

	if (mRoleNames)
	{
		role_id = mRoleNames->getCurrentID();
		
		// owner role: display confirmation and wait for callback
		if ((role_id == gdatap->mOwnerRole) && (!mConfirmedOwnerInvite))
		{
			LLSD args;
			args["MESSAGE"] = mOwnerWarning;
			LLNotificationsUtil::add("GenericAlertYesCancel", args, LLSD(), boost::bind(&LLPanelGroupInvite::impl::inviteOwnerCallback, this, _1, _2));
			return; // we'll be called again if user confirms
		}
	}

	bool already_in_group = false;
	//loop over the users
	std::vector<LLScrollListItem*> items = mInvitees->getAllData();
	for (std::vector<LLScrollListItem*>::iterator iter = items.begin();
		 iter != items.end(); ++iter)
	{
		LLScrollListItem* item = *iter;
		if(LLGroupActions::isAvatarMemberOfGroup(mGroupID, item->getUUID()))
		{
			already_in_group = true;
			continue;
		}
		role_member_pairs[item->getUUID()] = role_id;
	}
	
	if (role_member_pairs.size() > MAX_GROUP_INVITES)
	{
		// Fail!
		LLSD msg;
		msg["MESSAGE"] = mTooManySelected;
		LLNotificationsUtil::add("GenericAlert", msg);
		(*mCloseCallback)(mCloseCallbackUserData);
		return;
	}

	LLGroupMgr::getInstance()->sendGroupMemberInvites(mGroupID, role_member_pairs);
	
	if(already_in_group)
	{
		LLSD msg;
		msg["MESSAGE"] = mAlreadyInGroup;
		LLNotificationsUtil::add("GenericAlert", msg);
	}

	//then close
	(*mCloseCallback)(mCloseCallbackUserData);
}

bool LLPanelGroupInvite::impl::inviteOwnerCallback(const LLSD& notification, const LLSD& response)
{
	S32 option = LLNotificationsUtil::getSelectedOption(notification, response);

	switch(option)
	{
	case 0:
		// user confirmed that they really want a new group owner
		mConfirmedOwnerInvite = true;
		submitInvitations();
		break;
	case 1:
		// fall through
	default:
		break;
	}
	return false;
}



void LLPanelGroupInvite::impl::addRoleNames(LLGroupMgrGroupData* gdatap)
{
	LLGroupMgrGroupData::member_list_t::iterator agent_iter =
		gdatap->mMembers.find(gAgent.getID());

	//loop over the agent's roles in the group
	//then add those roles to the list of roles that the agent
	//can invite people to be.
	//if the user is the owner then we add
	//all of the roles in the group,
	//else if they have the add to roles power
	//we add every role but owner,
	//else if they have the limited add to roles power
	//we add every role the user is in,
	//else we just add to everyone
	bool is_owner = FALSE;
	bool can_assign_any = gAgent.hasPowerInGroup(mGroupID,
												 GP_ROLE_ASSIGN_MEMBER);
	bool can_assign_limited = gAgent.hasPowerInGroup(mGroupID,
													 GP_ROLE_ASSIGN_MEMBER_LIMITED);
	LLGroupMemberData* member_data = NULL;
	//get the member data for the agent if it exists
	if (agent_iter != gdatap->mMembers.end())
	{
		member_data = (*agent_iter).second;
		if (member_data && mRoleNames)
		{
			is_owner = member_data->isOwner();
		}//end if member data is not null
	}//end if agent is in the group



	LLGroupMgrGroupData::role_list_t::iterator rit = gdatap->mRoles.begin();
	LLGroupMgrGroupData::role_list_t::iterator end = gdatap->mRoles.end();

	//populate the role list:
	for ( ; rit != end; ++rit)
	{
		LLUUID role_id = (*rit).first;
		LLRoleData rd;
		if ( gdatap->getRoleData(role_id,rd) )
		{
			// Owners can add any role.
			if ( is_owner 
				// Even 'can_assign_any' can't add owner role.
				|| (can_assign_any && role_id != gdatap->mOwnerRole)
				// Add all roles user is in
				|| (can_assign_limited && member_data && member_data->isInRole(role_id))
				// Everyone role.
				|| role_id == LLUUID::null )
			{
				mRoleNames->add(rd.mRoleName,
								role_id,
								ADD_BOTTOM);
			}
		}
	}
}

//static
void LLPanelGroupInvite::impl::callbackClickAdd(void* userdata)
{
	LLPanelGroupInvite* panelp = (LLPanelGroupInvite*) userdata;

	if ( panelp )
	{
		//Right now this is hard coded with some knowledge that it is part
		//of a floater since the avatar picker needs to be added as a dependent
		//floater to the parent floater.
		//Soon the avatar picker will be embedded into this panel
		//instead of being it's own separate floater.  But that is next week.
		//This will do for now. -jwolk May 10, 2006
        LLView * button = panelp->findChild<LLButton>("add_button");
        LLFloater * root_floater = gFloaterView->getParentFloater(panelp);
		LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(
			boost::bind(impl::callbackAddUsers, _1, panelp->mImplementation), TRUE, FALSE, FALSE, root_floater->getName(), button);
		if (picker)
		{
			root_floater->addDependentFloater(picker);
		}
	}
}

//static
void LLPanelGroupInvite::impl::callbackClickRemove(void* userdata)
{
	impl* selfp = (impl*) userdata;

	if ( selfp ) selfp->handleRemove();
}

void LLPanelGroupInvite::impl::handleRemove()
{
	// Check if there is anything selected.
	std::vector<LLScrollListItem*> selection = 
			mInvitees->getAllSelected();
	if (selection.empty()) return;

	std::vector<LLScrollListItem*>::iterator iter;
	for(iter = selection.begin(); iter != selection.end(); ++iter)
	{
		mInviteeIDs.erase( (*iter)->getUUID() );
	}

	// Remove all selected invitees.
	mInvitees->deleteSelectedItems();
	mRemoveButton->setEnabled(FALSE);
}

// static
void LLPanelGroupInvite::impl::callbackSelect(
									LLUICtrl* ctrl, void* userdata)
{
	impl* selfp = (impl*) userdata;
	if ( selfp ) selfp->handleSelection();
}

void LLPanelGroupInvite::impl::handleSelection()
{
	// Check if there is anything selected.
	std::vector<LLScrollListItem*> selection = 
			mInvitees->getAllSelected();
	if (selection.empty())
	{
		mRemoveButton->setEnabled(FALSE);
	}
	else
	{
		mRemoveButton->setEnabled(TRUE);
	}
}

void LLPanelGroupInvite::impl::callbackClickCancel(void* userdata)
{
	impl* selfp = (impl*) userdata;

	if ( selfp ) 
	{
		(*(selfp->mCloseCallback))(selfp->mCloseCallbackUserData);
	}
}

void LLPanelGroupInvite::impl::callbackClickOK(void* userdata)
{
	impl* selfp = (impl*) userdata;

	if ( selfp ) selfp->submitInvitations();
}



//static
void LLPanelGroupInvite::impl::callbackAddUsers(const uuid_vec_t& agent_ids, void* user_data)
{	
	std::vector<std::string> names;
	for (S32 i = 0; i < (S32)agent_ids.size(); i++)
	{
		LLAvatarName av_name;
		if (LLAvatarNameCache::get(agent_ids[i], &av_name))
		{
			LLPanelGroupInvite::impl::onAvatarNameCache(agent_ids[i], av_name, user_data);
		}
		else 
		{
			impl* selfp = (impl*) user_data;
			if (selfp)
			{
				if (selfp->mAvatarNameCacheConnection.connected())
				{
					selfp->mAvatarNameCacheConnection.disconnect();
				}
				// *TODO : Add a callback per avatar name being fetched.
				selfp->mAvatarNameCacheConnection = LLAvatarNameCache::get(agent_ids[i],boost::bind(&LLPanelGroupInvite::impl::onAvatarNameCache, _1, _2, user_data));
			}
		}
	}	
	
}

void LLPanelGroupInvite::impl::onAvatarNameCache(const LLUUID& agent_id,
											 const LLAvatarName& av_name,
											 void* user_data)
{
	impl* selfp = (impl*) user_data;

	if (selfp)
	{
		if (selfp->mAvatarNameCacheConnection.connected())
		{
			selfp->mAvatarNameCacheConnection.disconnect();
		}
		std::vector<std::string> names;
		uuid_vec_t agent_ids;
		agent_ids.push_back(agent_id);
		names.push_back(av_name.getCompleteName());
		
		selfp->addUsers(names, agent_ids);
	}
}


LLPanelGroupInvite::LLPanelGroupInvite(const LLUUID& group_id)
	: LLPanel(),
	  mImplementation(new impl(group_id)),
	  mPendingUpdate(FALSE)
{
	// Pass on construction of this panel to the control factory.
	buildFromFile( "panel_group_invite.xml");
}

LLPanelGroupInvite::~LLPanelGroupInvite()
{
	delete mImplementation;
}

void LLPanelGroupInvite::setCloseCallback(void (*close_callback)(void*),
										  void* data)
{
	mImplementation->mCloseCallback         = close_callback;
	mImplementation->mCloseCallbackUserData = data;
}

void LLPanelGroupInvite::clear()
{
	mStoreSelected = LLUUID::null;
	mImplementation->mInvitees->deleteAllItems();
	mImplementation->mRoleNames->clear();
	mImplementation->mRoleNames->removeall();
	mImplementation->mOKButton->setEnabled(FALSE);
	mImplementation->mInviteeIDs.clear();
}

void LLPanelGroupInvite::addUsers(uuid_vec_t& agent_ids)
{
	std::vector<std::string> names;
	for (S32 i = 0; i < (S32)agent_ids.size(); i++)
	{
		std::string fullname;
		LLUUID agent_id = agent_ids[i];
		LLViewerObject* dest = gObjectList.findObject(agent_id);
		if(dest && dest->isAvatar())
		{
			LLNameValue* nvfirst = dest->getNVPair("FirstName");
			LLNameValue* nvlast = dest->getNVPair("LastName");
			if(nvfirst && nvlast)
			{
				fullname = LLCacheName::buildFullName(
					nvfirst->getString(), nvlast->getString());

			}
			if (!fullname.empty())
			{
				names.push_back(fullname);
			} 
			else 
			{
				LL_WARNS() << "llPanelGroupInvite: Selected avatar has no name: " << dest->getID() << LL_ENDL;
				names.push_back("(Unknown)");
			}
		}
		else
		{
			//looks like user try to invite offline avatar (or the avatar from the other region)
			//for offline avatar_id gObjectList.findObject() will return null
			//so we need to do this additional search in avatar tracker, see EXT-4732
			LLAvatarName av_name;
			if (!LLAvatarNameCache::get(agent_id, &av_name))
			{
				// actually it should happen, just in case
				//LLAvatarNameCache::get(LLUUID(agent_id), boost::bind(&LLPanelGroupInvite::addUserCallback, this, _1, _2));
				// for this special case!
				//when there is no cached name we should remove resident from agent_ids list to avoid breaking of sequence
				// removed id will be added in callback
				agent_ids.erase(agent_ids.begin() + i);
			}
			else
			{
				names.push_back(av_name.getAccountName());
			}
		}
	}
	mImplementation->addUsers(names, agent_ids);
}

void LLPanelGroupInvite::addUserCallback(const LLUUID& id, const LLAvatarName& av_name)
{
	std::vector<std::string> names;
	uuid_vec_t agent_ids;
	agent_ids.push_back(id);
	names.push_back(av_name.getAccountName());

	mImplementation->addUsers(names, agent_ids);
}

void LLPanelGroupInvite::draw()
{
	LLPanel::draw();
	if (mPendingUpdate)
	{
		updateLists();
	}
}
 
void LLPanelGroupInvite::update()
{
	mPendingUpdate = FALSE;
	if (mImplementation->mGroupName) 
	{
		mImplementation->mGroupName->setText(mImplementation->mLoadingText);
	}
	if ( mImplementation->mRoleNames ) 
	{
		mStoreSelected = mImplementation->mRoleNames->getCurrentID();
		mImplementation->mRoleNames->clear();
		mImplementation->mRoleNames->removeall();
		mImplementation->mRoleNames->add(mImplementation->mLoadingText, LLUUID::null, ADD_BOTTOM);
		mImplementation->mRoleNames->setCurrentByID(LLUUID::null);
	}

	updateLists();
}

void LLPanelGroupInvite::updateLists()
{
	LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mImplementation->mGroupID);
	bool waiting = false;

	if (gdatap) 
	{
		if (gdatap->isGroupPropertiesDataComplete()) 
		{
			if (mImplementation->mGroupName) 
			{
				mImplementation->mGroupName->setText(gdatap->mName);
			}
		} 
		else 
		{
			waiting = true;
		}
		if (gdatap->isRoleDataComplete() && gdatap->isMemberDataComplete()
			&& (gdatap->isRoleMemberDataComplete() || !gdatap->mMembers.size())) // MAINT-5270: large groups receives an empty members list without some powers, so RoleMemberData wouldn't be complete for them
		{
			if ( mImplementation->mRoleNames )
			{
				mImplementation->mRoleNames->clear();
				mImplementation->mRoleNames->removeall();

				//add the role names and select the everybody role by default
				mImplementation->addRoleNames(gdatap);
				mImplementation->mRoleNames->setCurrentByID(mStoreSelected);
			}
		} 
		else 
		{
			waiting = true;
		}
	} 
	else 
	{
		waiting = true;
	}

	if (waiting) 
	{
		if (!mPendingUpdate) 
		{
			// Note: this will partially fail if some requests are already in progress
			LLGroupMgr::getInstance()->sendGroupPropertiesRequest(mImplementation->mGroupID);
			LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mImplementation->mGroupID);
			LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(mImplementation->mGroupID);
			LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mImplementation->mGroupID);
		}
        else if (gdatap)
        {
			// <FS:Beq> FIRE-22912 Group send fail on OpenSim
			// Debug shows that GroupProperties data is frequently failing to be returned
			// Note that this method has a time-based SPAM check for existing pending requests.
            if (!gdatap->isGroupPropertiesDataComplete())
            {
                LLGroupMgr::getInstance()->sendGroupPropertiesRequest(mImplementation->mGroupID);
            }
			// </FS:Beq>
            // restart requests that were interrupted/dropped/failed to start
            if (!gdatap->isRoleDataPending() && !gdatap->isRoleDataComplete())
            {
                LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mImplementation->mGroupID);
            }
            if (!gdatap->isRoleMemberDataPending() && !gdatap->isRoleMemberDataComplete())
            {
                LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(mImplementation->mGroupID);
            }
            // sendCapGroupMembersRequest has a per frame send limitation that could have
            // interrupted previous request
            if (!gdatap->isMemberDataPending() && !gdatap->isMemberDataComplete())
            {
                LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mImplementation->mGroupID);
            }
        }
		mPendingUpdate = TRUE;
	} 
	else
	{
		mPendingUpdate = FALSE;
		if (mImplementation->mOKButton && mImplementation->mRoleNames->getItemCount()) 
		{
			mImplementation->mOKButton->setEnabled(TRUE);
		}
	}
}

BOOL LLPanelGroupInvite::postBuild()
{
	BOOL recurse = TRUE;

	mImplementation->mLoadingText = getString("loading");
	mImplementation->mRoleNames = getChild<LLComboBox>("role_name",
															   recurse);
	mImplementation->mGroupName = getChild<LLTextBox>("group_name_text", recurse);
	mImplementation->mInvitees = 
		getChild<LLNameListCtrl>("invitee_list", recurse);
	if ( mImplementation->mInvitees )
	{
		mImplementation->mInvitees->setCommitOnSelectionChange(TRUE);
		mImplementation->mInvitees->setCommitCallback(impl::callbackSelect, mImplementation);
	}

	LLButton* button = getChild<LLButton>("add_button", recurse);
	if ( button )
	{
		// default to opening avatarpicker automatically
		// (*impl::callbackClickAdd)((void*)this);
		button->setClickedCallback(impl::callbackClickAdd, this);
	}

	mImplementation->mRemoveButton = 
			getChild<LLButton>("remove_button", recurse);
	if ( mImplementation->mRemoveButton )
	{
		mImplementation->mRemoveButton->setClickedCallback(impl::callbackClickRemove, mImplementation);
		mImplementation->mRemoveButton->setEnabled(FALSE);
	}

	mImplementation->mOKButton = 
		getChild<LLButton>("invite_button", recurse);
	if ( mImplementation->mOKButton )
 	{
		mImplementation->mOKButton->setClickedCallback(impl::callbackClickOK, mImplementation);
		mImplementation->mOKButton->setEnabled(FALSE);
 	}

	button = getChild<LLButton>("cancel_button", recurse);
	if ( button )
	{
		button->setClickedCallback(impl::callbackClickCancel, mImplementation);
	}

	mImplementation->mOwnerWarning = getString("confirm_invite_owner_str");
	mImplementation->mAlreadyInGroup = getString("already_in_group");
	mImplementation->mTooManySelected = getString("invite_selection_too_large");

	update();
	
	return (mImplementation->mRoleNames &&
			mImplementation->mInvitees &&
			mImplementation->mRemoveButton);
}
