/** 
 * @file llgroupmgr.cpp
 * @brief LLGroupMgr class implementation
 *
 * $LicenseInfo:firstyear=2004&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$
 */

/**
 * Manager for aggregating all client knowledge for specific groups
 * Keeps a cache of group information.
 */

#include "llviewerprecompiledheaders.h"

#include "llgroupmgr.h"

#include <algorithm>

#include "llappviewer.h"
#include "llagent.h"
#include "llavatarnamecache.h"
#include "llui.h"
#include "message.h"
#include "roles_constants.h"
#include "lltransactiontypes.h"
#include "llstatusbar.h"
#include "lleconomy.h"
#include "llviewerwindow.h"
#include "llpanelgroup.h"
#include "llgroupactions.h"
#include "llnotificationsutil.h"
#include "lltrans.h"
#include "llviewerregion.h"

#include "llcorehttputil.h"
#include "roles_constants.h"
#include <boost/format.hpp>
#include <boost/regex.hpp>
#include <utility>

constexpr U32 MAX_CACHED_GROUPS = 20;

//
// LLRoleActionSet
//
LLRoleActionSet::LLRoleActionSet()
: mActionSetData(nullptr)
{ }

LLRoleActionSet::~LLRoleActionSet()
{
	delete mActionSetData;
	std::for_each(mActions.begin(), mActions.end(), DeletePointer());
	mActions.clear();
}

//
// LLGroupMemberData
//

LLGroupMemberData::LLGroupMemberData(const LLUUID& id, 
										S32 contribution,
										U64 agent_powers,
                                        std::string title,
                                        std::string online_status,
										BOOL is_owner) : 
	mID(id), 
	mContribution(contribution), 
	mAgentPowers(agent_powers), 
	mTitle(std::move(title)), 
	mOnlineStatus(std::move(online_status)),
	mIsOwner(is_owner)
{
}

LLGroupMemberData::~LLGroupMemberData()
{
}

void LLGroupMemberData::addRole(const LLUUID& role, LLGroupRoleData* rd)
{
	mRolesList[role] = rd;
}

bool LLGroupMemberData::removeRole(const LLUUID& role)
{
	role_list_t::iterator it = mRolesList.find(role);

	if (it != mRolesList.end())
	{
		mRolesList.erase(it);
		return true;
	}

	return false;
}

//
// LLGroupRoleData
//

LLGroupRoleData::LLGroupRoleData(const LLUUID& role_id, 
								const std::string& role_name,
								const std::string& role_title,
								const std::string& role_desc,
								const U64 role_powers,
								const S32 member_count) :
	mRoleID(role_id),
	mMemberCount(member_count),
	mMembersNeedsSort(FALSE)
{
	mRoleData.mRoleName = role_name;
	mRoleData.mRoleTitle = role_title;
	mRoleData.mRoleDescription = role_desc;
	mRoleData.mRolePowers = role_powers;
	mRoleData.mChangeType = LLRoleChangeType::RC_UPDATE_NONE;
}

LLGroupRoleData::LLGroupRoleData(const LLUUID& role_id, 
								LLRoleData role_data,
								const S32 member_count) :
	mRoleID(role_id),
	mRoleData(role_data),
	mMemberCount(member_count),
	mMembersNeedsSort(FALSE)
{

}

S32 LLGroupRoleData::getMembersInRole(uuid_vec_t members,
									  BOOL needs_sort)
{
	if (mRoleID.isNull())
	{
		// This is the everyone role, just return the size of members, 
		// because everyone is in the everyone role.
		return members.size();
	}

	// Sort the members list, if needed.
	if (mMembersNeedsSort)
	{
		std::sort(mMemberIDs.begin(), mMemberIDs.end());
		mMembersNeedsSort = FALSE;
	}
	if (needs_sort)
	{
		// Sort the members parameter.
		std::sort(members.begin(), members.end());
	}

	// Return the number of members in the intersection.
	S32 max_size = llmin( members.size(), mMemberIDs.size() );
	uuid_vec_t in_role( max_size );
	uuid_vec_t::iterator in_role_end;
	in_role_end = std::set_intersection(mMemberIDs.begin(), mMemberIDs.end(),
									members.begin(), members.end(),
									in_role.begin());
	return in_role_end - in_role.begin();
}
			  
void LLGroupRoleData::addMember(const LLUUID& member)
{
	mMembersNeedsSort = TRUE;
	mMemberIDs.push_back(member);
}

bool LLGroupRoleData::removeMember(const LLUUID& member)
{
	uuid_vec_t::iterator it = std::find(mMemberIDs.begin(),mMemberIDs.end(),member);

	if (it != mMemberIDs.end())
	{
		mMembersNeedsSort = TRUE;
		mMemberIDs.erase(it);
		return true;
	}

	return false;
}

void LLGroupRoleData::clearMembers()
{
	mMembersNeedsSort = FALSE;
	mMemberIDs.clear();
}

//
// LLRoleData
//

LLRoleData::LLRoleData() : mRolePowers(0), mChangeType(LLRoleChangeType::RC_UPDATE_NONE)
{ }

//
//
//

LLRoleMemberChange::LLRoleMemberChange() : mChange(LLRoleMemberChangeType::RMC_NONE)
{ }

//
// LLGroupMgrGroupData
//

LLGroupMgrGroupData::LLGroupMgrGroupData(const LLUUID& id) : 
	mID(id), 
	mShowInList(TRUE), 
	mOpenEnrollment(FALSE), 
	mMembershipFee(0),
	mAllowPublish(FALSE),
	mListInProfile(FALSE),
	mMaturePublish(FALSE),
	mChanged(FALSE),
	mMemberCount(0),
	mRoleCount(0),
	mPendingBanRequest(false),
	mReceivedRoleMemberPairs(0),
	mMemberDataComplete(false),
	mRoleDataComplete(false),
	mRoleMemberDataComplete(false),
	mGroupPropertiesDataComplete(false),
	mPendingRoleMemberRequest(false),
	mAccessTime(0.0f)
{
	mMemberVersion.generate();
}

void LLGroupMgrGroupData::setAccessed()
{
	mAccessTime = (F32)LLFrameTimer::getTotalSeconds();
}

BOOL LLGroupMgrGroupData::getRoleData(const LLUUID& role_id, LLRoleData& role_data)
{
	role_data_map_t::const_iterator it;

	// Do we have changes for it?
	it = mRoleChanges.find(role_id);
	if (it != mRoleChanges.end()) 
	{
        if ((*it).second.mChangeType == LLRoleChangeType::RC_DELETE) return FALSE;

		role_data = (*it).second;
		return TRUE;
	}

	// Ok, no changes, hasn't been deleted, isn't a new role, just find the role.
	role_list_t::const_iterator rit = mRoles.find(role_id);
	if (rit != mRoles.end())
	{
		role_data = (*rit).second->getRoleData();
		return TRUE;
	}

	// This role must not exist.
	return FALSE;
}


void LLGroupMgrGroupData::setRoleData(const LLUUID& role_id, LLRoleData role_data)
{
	// If this is a newly created group, we need to change the data in the created list.
	auto it = mRoleChanges.find(role_id);
	if (it != mRoleChanges.end())
	{
        if ((*it).second.mChangeType == LLRoleChangeType::RC_CREATE)
		{
			role_data.mChangeType = LLRoleChangeType::RC_CREATE;
			mRoleChanges[role_id] = role_data;
			return;
		}
		else if ((*it).second.mChangeType == LLRoleChangeType::RC_DELETE)
		{
			// Don't do anything for a role being deleted.
			return;
		}
	}

	// Not a new role, so put it in the changes list.
	LLRoleData old_role_data;
	role_list_t::iterator rit = mRoles.find(role_id);
	if (rit != mRoles.end())
	{
		bool data_change = ( ((*rit).second->mRoleData.mRoleDescription != role_data.mRoleDescription)
							|| ((*rit).second->mRoleData.mRoleName != role_data.mRoleName)
							|| ((*rit).second->mRoleData.mRoleTitle != role_data.mRoleTitle) );
		bool powers_change = ((*rit).second->mRoleData.mRolePowers != role_data.mRolePowers);
		
		if (!data_change && !powers_change)
		{
			// We are back to the original state, the changes have been 'undone' so take out the change.
			mRoleChanges.erase(role_id);
			return;
		}

		if (data_change && powers_change)
		{
			role_data.mChangeType = LLRoleChangeType::RC_UPDATE_ALL;
		}
		else if (data_change)
		{
			role_data.mChangeType = LLRoleChangeType::RC_UPDATE_DATA;
		}
		else
		{
			role_data.mChangeType = LLRoleChangeType::RC_UPDATE_POWERS;
		}

		mRoleChanges[role_id] = role_data;
	}
	else
	{
		LL_WARNS() << "Change being made to non-existant role " << role_id << LL_ENDL;
	}
}

BOOL LLGroupMgrGroupData::pendingRoleChanges()
{
	return (!mRoleChanges.empty());
}

// This is a no-op if the role has already been created.
void LLGroupMgrGroupData::createRole(const LLUUID& role_id, LLRoleData role_data)
{
	if (mRoleChanges.find(role_id) != mRoleChanges.end())
	{
		LL_WARNS() << "create role for existing role! " << role_id << LL_ENDL;
	}
	else
	{
        role_data.mChangeType = LLRoleChangeType::RC_CREATE;
		mRoleChanges[role_id] = role_data;
	}
}

void LLGroupMgrGroupData::deleteRole(const LLUUID& role_id)
{
	role_data_map_t::iterator it;

	// If this was a new role, just discard it.
	it = mRoleChanges.find(role_id);
	if (it != mRoleChanges.end() 
        && (*it).second.mChangeType == LLRoleChangeType::RC_CREATE)
	{
		mRoleChanges.erase(it);
		return;
	}

	LLRoleData rd;
    rd.mChangeType = LLRoleChangeType::RC_DELETE;
	mRoleChanges[role_id] = rd;
}

void LLGroupMgrGroupData::addRolePower(const LLUUID &role_id, U64 power)
{
	LLRoleData rd;
	if (getRoleData(role_id,rd))
	{	
		rd.mRolePowers |= power;
		setRoleData(role_id,rd);
	}
	else
	{
		LL_WARNS() << "addRolePower: no role data found for " << role_id << LL_ENDL;
	}
}

void LLGroupMgrGroupData::removeRolePower(const LLUUID &role_id, U64 power)
{
	LLRoleData rd;
	if (getRoleData(role_id,rd))
	{
		rd.mRolePowers &= ~power;
		setRoleData(role_id,rd);
	}
	else
	{
		LL_WARNS() << "removeRolePower: no role data found for " << role_id << LL_ENDL;
	}
}

U64 LLGroupMgrGroupData::getRolePowers(const LLUUID& role_id)
{
	LLRoleData rd;
	if (getRoleData(role_id,rd))
	{
		return rd.mRolePowers;
	}
	else
	{
		LL_WARNS() << "getRolePowers: no role data found for " << role_id << LL_ENDL;
		return GP_NO_POWERS;
	}
}

void LLGroupMgrGroupData::removeData()
{
	// Remove member data first, because removeRoleData will walk the member list
	removeMemberData();
	removeRoleData();
}

void LLGroupMgrGroupData::removeMemberData()
{
	for (auto& member : mMembers)
    {
		delete member.second;
	}
	mMembers.clear();
	mMemberDataComplete = false;
	mMemberVersion.generate();
}

void LLGroupMgrGroupData::removeRoleData()
{
	for (auto& member : mMembers)
    {
		LLGroupMemberData* data = member.second;
		if (data)
		{
			data->clearRoles();
		}
	}

	for (auto& role : mRoles)
    {
		LLGroupRoleData* data = role.second;
		delete data;
	}
	mRoles.clear();
	mReceivedRoleMemberPairs = 0;
	mRoleDataComplete = false;
	mRoleMemberDataComplete= false;
}

void LLGroupMgrGroupData::removeRoleMemberData()
{
	for (auto& member : mMembers)
    {
		LLGroupMemberData* data = member.second;
		if (data)
		{
			data->clearRoles();
		}
	}

	for (auto& role : mRoles)
    {
		LLGroupRoleData* data = role.second;
		if (data)
		{
			data->clearMembers();
		}
	}

	mReceivedRoleMemberPairs = 0;
	mRoleMemberDataComplete= false;
}

LLGroupMgrGroupData::~LLGroupMgrGroupData()
{
	removeData();
}

bool LLGroupMgrGroupData::changeRoleMember(const LLUUID& role_id, 
										   const LLUUID& member_id, 
										   LLRoleMemberChangeType rmc)
{
	role_list_t::iterator ri = mRoles.find(role_id);
	member_list_t::iterator mi = mMembers.find(member_id);

	if (ri == mRoles.end()
		|| mi == mMembers.end() )
	{
		if (ri == mRoles.end()) LL_WARNS() << "LLGroupMgrGroupData::changeRoleMember couldn't find role " << role_id << LL_ENDL;
		if (mi == mMembers.end()) LL_WARNS() << "LLGroupMgrGroupData::changeRoleMember couldn't find member " << member_id << LL_ENDL;
		return false;
	}

	LLGroupRoleData* grd = ri->second;
	LLGroupMemberData* gmd = mi->second;

	if (!grd || !gmd)
	{
		LL_WARNS() << "LLGroupMgrGroupData::changeRoleMember couldn't get member or role data." << LL_ENDL;
		return false;
	}

    if (LLRoleMemberChangeType::RMC_ADD == rmc)
	{
		LL_INFOS() << " adding member to role." << LL_ENDL;
		grd->addMember(member_id);
		gmd->addRole(role_id,grd);

		//TODO move this into addrole function
		//see if they added someone to the owner role and update isOwner
		gmd->mIsOwner = (role_id == mOwnerRole) ? TRUE : gmd->mIsOwner;
	}
    else if (LLRoleMemberChangeType::RMC_REMOVE == rmc)
	{
		LL_INFOS() << " removing member from role." << LL_ENDL;
		grd->removeMember(member_id);
		gmd->removeRole(role_id);

		//see if they removed someone from the owner role and update isOwner
		gmd->mIsOwner = (role_id == mOwnerRole) ? FALSE : gmd->mIsOwner;
	}

	lluuid_pair role_member;
	role_member.first = role_id;
	role_member.second = member_id;

	change_map_t::iterator it = mRoleMemberChanges.find(role_member);
	if (it != mRoleMemberChanges.end())
	{
		// There was already a role change for this role_member
		if (it->second.mChange == rmc)
		{
			// Already recorded this change?  Weird.
			LL_INFOS() << "Received duplicate change for "
					<< " role: " << role_id << " member " << member_id 
            << " change " << (rmc == LLRoleMemberChangeType::RMC_ADD ? "ADD" : "REMOVE") << LL_ENDL;
		}
		else
		{
			// The only two operations (add and remove) currently cancel each other out
			// If that changes this will need more logic
            if (rmc == LLRoleMemberChangeType::RMC_NONE)
			{
				LL_WARNS() << "changeRoleMember: existing entry with 'RMC_NONE' change! This shouldn't happen." << LL_ENDL;
				LLRoleMemberChange rc(role_id,member_id,rmc);
				mRoleMemberChanges[role_member] = rc;
			}
			else
			{
				mRoleMemberChanges.erase(it);
			}
		}
	}
	else
	{
		LLRoleMemberChange rc(role_id,member_id,rmc);
		mRoleMemberChanges[role_member] = rc;
	}

	recalcAgentPowers(member_id);

	mChanged = TRUE;
	return true;
}

void LLGroupMgrGroupData::recalcAllAgentPowers()
{
	LLGroupMemberData* gmd;

	for (auto& member : mMembers)
    {
		gmd = member.second;
		if (!gmd) continue;

		gmd->mAgentPowers = 0;
		for (LLGroupMemberData::role_list_t::iterator it = gmd->mRolesList.begin();
			 it != gmd->mRolesList.end(); ++it)
		{
			LLGroupRoleData* grd = (*it).second;
			if (!grd) continue;

			gmd->mAgentPowers |= grd->mRoleData.mRolePowers;
		}
	}
}

void LLGroupMgrGroupData::recalcAgentPowers(const LLUUID& agent_id)
{
	member_list_t::iterator mi = mMembers.find(agent_id);
	if (mi == mMembers.end()) return;

	LLGroupMemberData* gmd = mi->second;

	if (!gmd) return;

	gmd->mAgentPowers = 0;
	for (LLGroupMemberData::role_list_t::iterator it = gmd->mRolesList.begin();
		 it != gmd->mRolesList.end(); ++it)
	{
		LLGroupRoleData* grd = (*it).second;
		if (!grd) continue;

		gmd->mAgentPowers |= grd->mRoleData.mRolePowers;
	}
}

bool LLGroupMgrGroupData::isSingleMemberNotOwner()
{
	return mMembers.size() == 1 && !mMembers.begin()->second->isOwner();
}

bool packRoleUpdateMessageBlock(LLMessageSystem* msg, 
								const LLUUID& group_id,
								const LLUUID& role_id, 
								const LLRoleData& role_data, 
								bool start_message)
{
	if (start_message)
	{
		msg->newMessage("GroupRoleUpdate");
		msg->nextBlock("AgentData");
		msg->addUUID("AgentID",gAgent.getID());
		msg->addUUID("SessionID",gAgent.getSessionID());
		msg->addUUID("GroupID",group_id);
		start_message = false;
	}

	msg->nextBlock("RoleData");
	msg->addUUID("RoleID",role_id);
	msg->addString("Name", role_data.mRoleName);
	msg->addString("Description", role_data.mRoleDescription);
	msg->addString("Title", role_data.mRoleTitle);
	msg->addU64("Powers", role_data.mRolePowers);
	msg->addU8("UpdateType", (U8)role_data.mChangeType);

	if (msg->isSendFullFast())
	{
		gAgent.sendReliableMessage();
		start_message = true;
	}

	return start_message;
}

void LLGroupMgrGroupData::sendRoleChanges()
{
	// Commit changes locally
	LLGroupRoleData* grd;
	role_list_t::iterator role_it;
	LLMessageSystem* msg = gMessageSystem;
	bool start_message = true;

	bool need_role_cleanup = false;
	bool need_role_data = false;
	bool need_power_recalc = false;

	// Apply all changes
	for (role_data_map_t::iterator iter = mRoleChanges.begin();
		 iter != mRoleChanges.end(); )
	{
		role_data_map_t::iterator it = iter++; // safely incrament iter
		const LLUUID& role_id = (*it).first;
		const LLRoleData& role_data = (*it).second;

		// Commit to local data set
		role_it = mRoles.find((*it).first);
		if ( (mRoles.end() == role_it 
                && LLRoleChangeType::RC_CREATE != role_data.mChangeType)
			|| (mRoles.end() != role_it
                && LLRoleChangeType::RC_CREATE == role_data.mChangeType))
		{
			continue;
		}
	
		// NOTE: role_it is valid EXCEPT for the RC_CREATE case
		switch (role_data.mChangeType)
		{
            case LLRoleChangeType::RC_CREATE:
			{
				// NOTE: role_it is NOT valid in this case
				grd = new LLGroupRoleData(role_id, role_data, 0);
				mRoles[role_id] = grd;
				need_role_data = true;
				break;
			}
            case LLRoleChangeType::RC_DELETE:
			{
				LLGroupRoleData* group_role_data = (*role_it).second;
				delete group_role_data;
				mRoles.erase(role_it);
				need_role_cleanup = true;
				need_power_recalc = true;
				break;
			}
            case LLRoleChangeType::RC_UPDATE_ALL:
				// fall through
            case LLRoleChangeType::RC_UPDATE_POWERS:
				need_power_recalc = true;
				// fall through
            case LLRoleChangeType::RC_UPDATE_DATA:
				// fall through
			default: 
			{
				LLGroupRoleData* group_role_data = (*role_it).second;
				group_role_data->setRoleData(role_data); // NOTE! might modify mRoleChanges!
				break;
			}
		}

		// Update dataserver
		start_message = packRoleUpdateMessageBlock(msg,getID(),role_id,role_data,start_message);
	}

	if (!start_message)
	{
		gAgent.sendReliableMessage();
	}

	// If we delete a role then all the role-member pairs are invalid!
	if (need_role_cleanup)
	{
		removeRoleMemberData();
	}

	// If we create a new role, then we need to re-fetch all the role data.
	if (need_role_data)
	{
		LLGroupMgr::getInstance()->sendGroupRoleDataRequest(getID());
	}

	// Clean up change lists	
	mRoleChanges.clear();

	// Recalculate all the agent powers because role powers have now changed.
	if (need_power_recalc)
	{
		recalcAllAgentPowers();
	}
}

void LLGroupMgrGroupData::cancelRoleChanges()
{
	// Clear out all changes!
	mRoleChanges.clear();
}

void LLGroupMgrGroupData::createBanEntry(const LLUUID& ban_id, const LLGroupBanData& ban_data)
{ 
	mBanList[ban_id] = ban_data;
}

void LLGroupMgrGroupData::removeBanEntry(const LLUUID& ban_id)
{
	mBanList.erase(ban_id);
}

void LLGroupMgrGroupData::banMemberById(const LLUUID& participant_uuid)
{
	if (!mMemberDataComplete ||
		!mRoleDataComplete ||
		!(mRoleMemberDataComplete && !mMembers.empty()))
	{
		LL_WARNS() << "No Role-Member data yet, setting ban request to pending." << LL_ENDL;
		mPendingBanRequest = true;
		mPendingBanMemberID = participant_uuid;

		if (!mMemberDataComplete || mMembers.empty())
		{
			LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mID);
		}

		if (!mRoleDataComplete)
		{
			LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mID);
		}

		return;
	}
	
	LLGroupMgrGroupData::member_list_t::iterator mi = mMembers.find((participant_uuid));
	if (mi == mMembers.end())
	{
		if (!mPendingBanRequest)
		{
			mPendingBanRequest = true;
			mPendingBanMemberID = participant_uuid;
			LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mID); // member isn't in members list, request reloading
		}
		else
		{
			mPendingBanRequest = false;
		}

		return;
	}

	mPendingBanRequest = false;

	LLGroupMemberData* member_data = (*mi).second;
	if (member_data && member_data->isInRole(mOwnerRole))
	{
		return; // can't ban group owner
	}

	std::vector<LLUUID> ids;
	ids.push_back(participant_uuid);

	LLGroupBanData ban_data;
	createBanEntry(participant_uuid, ban_data);
	LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_POST, mID, LLGroupMgr::BAN_CREATE, ids);
	LLGroupMgr::getInstance()->sendGroupMemberEjects(mID, ids);
	LLGroupMgr::getInstance()->sendGroupMembersRequest(mID);
	LLSD args;
	LLAvatarName av_name;
	LLAvatarNameCache::get(participant_uuid, &av_name);
	args["AVATAR_NAME"] = av_name.getUserName();
	args["GROUP_NAME"] = mName;
	LLNotifications::instance().add(LLNotification::Params("EjectAvatarFromGroup").substitutions(args));
}

//
// LLGroupMgr
//

LLGroupMgr::LLGroupMgr():
    mMemberRequestInFlight(false)
{
}

LLGroupMgr::~LLGroupMgr()
{
	clearGroups();
}

void LLGroupMgr::clearGroups()
{
	std::for_each(mRoleActionSets.begin(), mRoleActionSets.end(), DeletePointer());
	mRoleActionSets.clear();
	std::for_each(mGroups.begin(), mGroups.end(), DeletePairedPointer());
	mGroups.clear();
	mObservers.clear();
}

void LLGroupMgr::clearGroupData(const LLUUID& group_id)
{
	group_map_t::iterator iter = mGroups.find(group_id);
	if (iter != mGroups.end())
	{
		delete (*iter).second;
		mGroups.erase(iter);
	}
}

void LLGroupMgr::addObserver(LLGroupMgrObserver* observer) 
{ 
	if( observer->getID() != LLUUID::null )
		mObservers.insert(std::pair<LLUUID, LLGroupMgrObserver*>(observer->getID(), observer));
}

void LLGroupMgr::addObserver(const LLUUID& group_id, LLParticularGroupObserver* observer)
{
	if(group_id.notNull() && observer)
	{
		mParticularObservers[group_id].insert(observer);
	}
}

void LLGroupMgr::removeObserver(LLGroupMgrObserver* observer)
{
	if (!observer)
	{
		return;
	}
	observer_multimap_t::iterator it;
	it = mObservers.find(observer->getID());
	while (it != mObservers.end())
	{
		if (it->second == observer)
		{
			mObservers.erase(it);
			break;
		}
		else
		{
			++it;
		}
	}
}

void LLGroupMgr::removeObserver(const LLUUID& group_id, LLParticularGroupObserver* observer)
{
	if(group_id.isNull() || !observer)
	{
		return;
	}

    observer_map_t::iterator obs_it = mParticularObservers.find(group_id);
    if(obs_it == mParticularObservers.end())
        return;

    obs_it->second.erase(observer);

    if (obs_it->second.empty())
    	mParticularObservers.erase(obs_it);
}

LLGroupMgrGroupData* LLGroupMgr::getGroupData(const LLUUID& id)
{
	group_map_t::iterator gi = mGroups.find(id);

	if (gi != mGroups.end())
	{
		return gi->second;
	}
	return nullptr;
}

// Helper function for LLGroupMgr::processGroupMembersReply
// This reformats date strings from MM/DD/YYYY to YYYY/MM/DD ( e.g. 1/27/2008 -> 2008/1/27 )
// so that the sorter can sort by year before month before day.
static void formatDateString(std::string &date_string)
{
	using namespace boost;
	cmatch result;
	const regex expression("([0-9]{1,2})/([0-9]{1,2})/([0-9]{4})");
	if (regex_match(date_string.c_str(), result, expression))
	{
		// convert matches to integers so that we can pad them with zeroes on Linux
		S32 year	= std::stoi(result[3]);
		S32 month	= std::stoi(result[1]);
		S32 day		= std::stoi(result[2]);

		// ISO 8601 date format
		date_string = fmt::format(FMT_STRING("{}/{}/{}"), year, month, day);
	}
}

// static
void LLGroupMgr::processGroupMembersReply(LLMessageSystem* msg, void** data)
{
	LL_DEBUGS() << "LLGroupMgr::processGroupMembersReply" << LL_ENDL;
	LLUUID agent_id;
	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id );
	if (gAgent.getID() != agent_id)
	{
		LL_WARNS() << "Got group members reply for another agent!" << LL_ENDL;
		return;
	}

	LLUUID group_id;
	msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id );

	LLUUID request_id;
	msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_RequestID, request_id);

	LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id);
	if (!group_datap || (group_datap->mMemberRequestID != request_id))
	{
		LL_WARNS() << "processGroupMembersReply: Received incorrect (stale?) group or request id" << LL_ENDL;
		return;
	}

	msg->getS32(_PREHASH_GroupData, "MemberCount", group_datap->mMemberCount );

	if (group_datap->mMemberCount > 0)
	{
		S32 contribution = 0;
		std::string online_status;
		std::string title;
		U64 agent_powers = 0;
		BOOL is_owner = FALSE;

		S32 num_members = msg->getNumberOfBlocksFast(_PREHASH_MemberData);
		for (S32 i = 0; i < num_members; i++)
		{
			LLUUID member_id;

			msg->getUUIDFast(_PREHASH_MemberData, _PREHASH_AgentID, member_id, i );
			msg->getS32(_PREHASH_MemberData, _PREHASH_Contribution, contribution, i);
			msg->getU64(_PREHASH_MemberData, "AgentPowers", agent_powers, i);
			msg->getStringFast(_PREHASH_MemberData, _PREHASH_OnlineStatus, online_status, i);
			msg->getString(_PREHASH_MemberData, "Title", title, i);
			msg->getBOOL(_PREHASH_MemberData,"IsOwner",is_owner,i);

			if (member_id.notNull())
			{
				if (online_status == "Online")
				{
					static std::string localized_online(LLTrans::getString("group_member_status_online"));
					online_status = localized_online;
				}
				else
				{
					formatDateString(online_status); // reformat for sorting, e.g. 12/25/2008 -> 2008/12/25
				}
				
				//LL_INFOS() << "Member " << member_id << " has powers " << std::hex << agent_powers << std::dec << LL_ENDL;
				LLGroupMemberData* newdata = new LLGroupMemberData(member_id, 
																	contribution, 
																	agent_powers, 
																	title,
																	online_status,
																	is_owner);
#if LL_DEBUG
				LLGroupMgrGroupData::member_list_t::iterator mit = group_datap->mMembers.find(member_id);
				if (mit != group_datap->mMembers.end())
				{
					LL_INFOS() << " *** Received duplicate member data for agent " << member_id << LL_ENDL;
				}
#endif
				group_datap->mMembers[member_id] = newdata;
			}
			else
			{
				LL_INFOS() << "Received null group member data." << LL_ENDL;
			}
		}

		//if group members are loaded while titles are missing, load the titles.
		if(group_datap->mTitles.empty())
		{
			LLGroupMgr::getInstance()->sendGroupTitlesRequest(group_id);
		}
	}

	group_datap->mMemberVersion.generate();

	if (group_datap->mMembers.size() ==  (U32)group_datap->mMemberCount)
	{
		group_datap->mMemberDataComplete = true;
		group_datap->mMemberRequestID.setNull();
		// We don't want to make role-member data requests until we have all the members
		if (group_datap->mPendingRoleMemberRequest)
		{
			group_datap->mPendingRoleMemberRequest = false;
			LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(group_datap->mID);
		}
	}

	group_datap->mChanged = TRUE;
	LLGroupMgr::getInstance()->notifyObservers(GC_MEMBER_DATA);
}

//static 
void LLGroupMgr::processGroupPropertiesReply(LLMessageSystem* msg, void** data)
{
	LL_DEBUGS() << "LLGroupMgr::processGroupPropertiesReply" << LL_ENDL;
	if (!msg)
	{
		LL_ERRS() << "Can't access the messaging system" << LL_ENDL;
		return;
	}
	LLUUID agent_id;
	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id );
	if (gAgent.getID() != agent_id)
	{
		LL_WARNS() << "Got group properties reply for another agent!" << LL_ENDL;
		return;
	}

	LLUUID group_id;
	std::string	name;
	std::string	charter;
	BOOL	show_in_list = FALSE;
	LLUUID	founder_id;
	U64		powers_mask = GP_NO_POWERS;
	S32		money = 0;
	std::string	member_title;
	LLUUID	insignia_id;
	LLUUID	owner_role;
	U32		membership_fee = 0;
	BOOL	open_enrollment = FALSE;
	S32		num_group_members = 0;
	S32		num_group_roles = 0;
	BOOL	allow_publish = FALSE;
	BOOL	mature = FALSE;

	msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id );
	msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_FounderID, founder_id);	
	msg->getStringFast(_PREHASH_GroupData, _PREHASH_Name, name );
	msg->getStringFast(_PREHASH_GroupData, _PREHASH_Charter, charter );
	msg->getBOOLFast(_PREHASH_GroupData, _PREHASH_ShowInList, show_in_list );
	msg->getStringFast(_PREHASH_GroupData, _PREHASH_MemberTitle, member_title );
	msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_InsigniaID, insignia_id );
	msg->getU64Fast(_PREHASH_GroupData, _PREHASH_PowersMask, powers_mask );
	msg->getU32Fast(_PREHASH_GroupData, _PREHASH_MembershipFee, membership_fee );
	msg->getBOOLFast(_PREHASH_GroupData, _PREHASH_OpenEnrollment, open_enrollment );
	msg->getS32Fast(_PREHASH_GroupData, _PREHASH_GroupMembershipCount, num_group_members);
	msg->getS32(_PREHASH_GroupData, "GroupRolesCount", num_group_roles);
	msg->getS32Fast(_PREHASH_GroupData, _PREHASH_Money, money);
	msg->getBOOL("GroupData", "AllowPublish", allow_publish);
	msg->getBOOL("GroupData", "MaturePublish", mature);
	msg->getUUID(_PREHASH_GroupData, "OwnerRole", owner_role);

	LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->createGroupData(group_id);

	group_datap->mName = name;
	group_datap->mCharter = charter;
	group_datap->mShowInList = show_in_list;
	group_datap->mInsigniaID = insignia_id;
	group_datap->mFounderID = founder_id;
	group_datap->mMembershipFee = membership_fee;
	group_datap->mOpenEnrollment = open_enrollment;
	group_datap->mAllowPublish = allow_publish;
	group_datap->mMaturePublish = mature;
	group_datap->mOwnerRole = owner_role;
	group_datap->mMemberCount = num_group_members;
	group_datap->mRoleCount = num_group_roles + 1; // Add the everyone role.
	
	group_datap->mGroupPropertiesDataComplete = true;
	group_datap->mChanged = TRUE;

	LLGroupMgr::getInstance()->notifyObservers(GC_PROPERTIES);
}

// static
void LLGroupMgr::processGroupRoleDataReply(LLMessageSystem* msg, void** data)
{
	LL_DEBUGS() << "LLGroupMgr::processGroupRoleDataReply" << LL_ENDL;
	LLUUID agent_id;
	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id );
	if (gAgent.getID() != agent_id)
	{
		LL_WARNS() << "Got group role data reply for another agent!" << LL_ENDL;
		return;
	}

	LLUUID group_id;
	msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id );

	LLUUID request_id;
	msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_RequestID, request_id);

	LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id);
	if (!group_datap || (group_datap->mRoleDataRequestID != request_id))
	{
		LL_WARNS() << "processGroupPropertiesReply: Received incorrect (stale?) group or request id" << LL_ENDL;
		return;
	}

	msg->getS32(_PREHASH_GroupData, "RoleCount", group_datap->mRoleCount );

	std::string	name;
	std::string	title;
	std::string	desc;
	U64		powers = 0;
	U32		member_count = 0;
	LLUUID role_id;

	U32 num_blocks = msg->getNumberOfBlocks("RoleData");
	U32 i = 0;
	for (i=0; i< num_blocks; ++i)
	{
		msg->getUUID("RoleData", "RoleID", role_id, i );
		
		msg->getString("RoleData","Name",name,i);
		msg->getString("RoleData","Title",title,i);
		msg->getString("RoleData","Description",desc,i);
		msg->getU64("RoleData","Powers",powers,i);
		msg->getU32("RoleData","Members",member_count,i);

		//there are 3 predifined roles - Owners, Officers, Everyone
		//there names are defined in lldatagroups.cpp
		//lets change names from server to localized strings
		if(name == "Everyone")
		{
			name = LLTrans::getString("group_role_everyone");
		}
		else if(name == "Officers")
		{
			name = LLTrans::getString("group_role_officers");
		}
		else if(name == "Owners")
		{
			name = LLTrans::getString("group_role_owners");
		}



		LL_DEBUGS() << "Adding role data: " << name << " {" << role_id << "}" << LL_ENDL;
		LLGroupRoleData* rd = new LLGroupRoleData(role_id,name,title,desc,powers,member_count);
		group_datap->mRoles[role_id] = rd;
	}

	if (group_datap->mRoles.size() == (U32)group_datap->mRoleCount)
	{
		group_datap->mRoleDataComplete = true;
		group_datap->mRoleDataRequestID.setNull();
		// We don't want to make role-member data requests until we have all the role data
		if (group_datap->mPendingRoleMemberRequest)
		{
			group_datap->mPendingRoleMemberRequest = false;
			LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(group_datap->mID);
		}
	}

	group_datap->mChanged = TRUE;
	LLGroupMgr::getInstance()->notifyObservers(GC_ROLE_DATA);
}

// static
void LLGroupMgr::processGroupRoleMembersReply(LLMessageSystem* msg, void** data)
{
	LL_DEBUGS() << "LLGroupMgr::processGroupRoleMembersReply" << LL_ENDL;
	LLUUID agent_id;
	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id );
	if (gAgent.getID() != agent_id)
	{
		LL_WARNS() << "Got group role members reply for another agent!" << LL_ENDL;
		return;
	}

	LLUUID request_id;
	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_RequestID, request_id);

	LLUUID group_id;
	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id );

	U32 total_pairs;
	msg->getU32(_PREHASH_AgentData, "TotalPairs", total_pairs);

	LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id);
	if (!group_datap || (group_datap->mRoleMembersRequestID != request_id))
	{
		LL_WARNS() << "processGroupRoleMembersReply: Received incorrect (stale?) group or request id" << LL_ENDL;
		return;
	}

	U32 num_blocks = msg->getNumberOfBlocks("MemberData");
	U32 i;
	LLUUID member_id;
	LLUUID role_id;
	LLGroupRoleData* rd = nullptr;
	LLGroupMemberData* md = nullptr;

	LLGroupMgrGroupData::role_list_t::iterator ri;
	LLGroupMgrGroupData::member_list_t::iterator mi;

	// If total_pairs == 0, there are no members in any custom roles.
	if (total_pairs > 0)
	{
		for (i = 0;i < num_blocks; ++i)
		{
			msg->getUUID("MemberData","RoleID",role_id,i);
			msg->getUUID("MemberData","MemberID",member_id,i);

			if (role_id.notNull() && member_id.notNull() )
			{
				rd = nullptr;
				ri = group_datap->mRoles.find(role_id);
				if (ri != group_datap->mRoles.end())
				{
					rd = ri->second;
				}

				md = nullptr;
				mi = group_datap->mMembers.find(member_id);
				if (mi != group_datap->mMembers.end())
				{
					md = mi->second;
				}

				if (rd && md)
				{
					LL_DEBUGS() << "Adding role-member pair: " << role_id << ", " << member_id << LL_ENDL;
					rd->addMember(member_id);
					md->addRole(role_id,rd);
				}
				else
				{
					if (!rd) LL_WARNS() << "Received role data for unknown role " << role_id << " in group " << group_id << LL_ENDL;
					if (!md) LL_WARNS() << "Received role data for unknown member " << member_id << " in group " << group_id << LL_ENDL;
				}
			}
		}

		group_datap->mReceivedRoleMemberPairs += num_blocks;
	}

	if (group_datap->mReceivedRoleMemberPairs == total_pairs)
	{
		// Add role data for the 'everyone' role to all members
		LLGroupRoleData* everyone = group_datap->mRoles[LLUUID::null];
		if (!everyone)
		{
			LL_WARNS() << "Everyone role not found!" << LL_ENDL;
		}
		else
		{
			for (auto& member : group_datap->mMembers)
            {
				LLGroupMemberData* data = member.second;
				if (data)
				{
					data->addRole(LLUUID::null,everyone);
				}
			}
		}
		
        group_datap->mRoleMemberDataComplete= true;
		group_datap->mRoleMembersRequestID.setNull();
	}

	group_datap->mChanged = TRUE;
	LLGroupMgr::getInstance()->notifyObservers(GC_ROLE_MEMBER_DATA);

	if (group_datap->mPendingBanRequest)
	{
		group_datap->banMemberById(group_datap->mPendingBanMemberID);
	}
}

// static
void LLGroupMgr::processGroupTitlesReply(LLMessageSystem* msg, void** data)
{
	LL_DEBUGS() << "LLGroupMgr::processGroupTitlesReply" << LL_ENDL;
	LLUUID agent_id;
	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id );
	if (gAgent.getID() != agent_id)
	{
		LL_WARNS() << "Got group properties reply for another agent!" << LL_ENDL;
		return;
	}

	LLUUID group_id;
	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id );
	LLUUID request_id;
	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_RequestID, request_id);
	
	LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id);
	if (!group_datap || (group_datap->mTitlesRequestID != request_id))
	{
		LL_WARNS() << "processGroupTitlesReply: Received incorrect (stale?) group" << LL_ENDL;
		return;
	}

	LLGroupTitle title;

	S32 i = 0;
	S32 blocks = msg->getNumberOfBlocksFast(_PREHASH_GroupData);
	for (i=0; i<blocks; ++i)
	{
		msg->getString("GroupData","Title",title.mTitle,i);
		msg->getUUID("GroupData","RoleID",title.mRoleID,i);
		msg->getBOOL("GroupData","Selected",title.mSelected,i);

		if (!title.mTitle.empty())
		{
			LL_DEBUGS() << "LLGroupMgr adding title: " << title.mTitle << ", " << title.mRoleID << ", " << (title.mSelected ? 'Y' : 'N') << LL_ENDL;
			group_datap->mTitles.push_back(title);
		}
	}

	group_datap->mChanged = TRUE;
	LLGroupMgr::getInstance()->notifyObservers(GC_TITLES);
}

// static
void LLGroupMgr::processEjectGroupMemberReply(LLMessageSystem* msg, void ** data)
{
	LL_DEBUGS() << "processEjectGroupMemberReply" << LL_ENDL;
	LLUUID group_id;
	msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id);
	BOOL success;
	msg->getBOOLFast(_PREHASH_EjectData, _PREHASH_Success, success);

	// If we had a failure, the group panel needs to be updated.
	if (!success)
	{
		LLGroupActions::refresh(group_id);
	}
}

// static
void LLGroupMgr::processJoinGroupReply(LLMessageSystem* msg, void ** data)
{
	LL_DEBUGS() << "processJoinGroupReply" << LL_ENDL;
	LLUUID group_id;
	BOOL success;
	msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id);
	msg->getBOOLFast(_PREHASH_GroupData, _PREHASH_Success, success);

	if (success)
	{
		// refresh all group information
		gAgent.sendAgentDataUpdateRequest();

		LLGroupMgr::getInstance()->clearGroupData(group_id);
		// refresh the floater for this group, if any.
		LLGroupActions::refresh(group_id);
	}
}

// static
void LLGroupMgr::processLeaveGroupReply(LLMessageSystem* msg, void ** data)
{
	LL_DEBUGS() << "processLeaveGroupReply" << LL_ENDL;
	LLUUID group_id;
	BOOL success;
	msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id);
	msg->getBOOLFast(_PREHASH_GroupData, _PREHASH_Success, success);

	if (success)
	{
		// refresh all group information
		gAgent.sendAgentDataUpdateRequest();

		LLGroupMgr::getInstance()->clearGroupData(group_id);
		// close the floater for this group, if any.
		LLGroupActions::closeGroup(group_id);
	}
}

// static
void LLGroupMgr::processCreateGroupReply(LLMessageSystem* msg, void ** data)
{
	LLUUID group_id;
	BOOL success;
	std::string message;

	msg->getUUIDFast(_PREHASH_ReplyData, _PREHASH_GroupID, group_id );

	msg->getBOOLFast(_PREHASH_ReplyData, _PREHASH_Success,	success );
	msg->getStringFast(_PREHASH_ReplyData, _PREHASH_Message, message );

	if (success)
	{
		// refresh all group information
		gAgent.sendAgentDataUpdateRequest();

		// HACK! We haven't gotten the agent group update yet, so ... um ... fake it.
		// This is so when we go to modify the group we will be able to do so.
		// This isn't actually too bad because real data will come down in 2 or 3 miliseconds and replace this.
		LLGroupData gd;
		gd.mAcceptNotices = TRUE;
		gd.mListInProfile = TRUE;
		gd.mContribution = 0;
		gd.mID = group_id;
		gd.mName = "new group";
		gd.mPowers = GP_ALL_POWERS;

		gAgent.mGroups.push_back(gd);

		LLPanelGroup::refreshCreatedGroup(group_id);
		//FIXME
		//LLFloaterGroupInfo::closeCreateGroup();
		//LLFloaterGroupInfo::showFromUUID(group_id,"roles_tab");
	}
	else
	{
		// *TODO: Translate
		LLSD args;
		args["MESSAGE"] = message;
		LLNotificationsUtil::add("UnableToCreateGroup", args);
	}
}

LLGroupMgrGroupData* LLGroupMgr::createGroupData(const LLUUID& id)
{
	LLGroupMgrGroupData* group_datap = nullptr;

	group_map_t::iterator existing_group = LLGroupMgr::getInstance()->mGroups.find(id);
	if (existing_group == LLGroupMgr::getInstance()->mGroups.end())
	{
		group_datap = new LLGroupMgrGroupData(id);
		LLGroupMgr::getInstance()->addGroup(group_datap);
	}
	else
	{
		group_datap = existing_group->second;
	}

	if (group_datap)
	{
		group_datap->setAccessed();
	}

	return group_datap;
}

void LLGroupMgr::notifyObservers(LLGroupChange gc)
{
	for (auto& group : mGroups)
    {
		LLUUID group_id = group.first;
		if (group.second->mChanged)
		{
			// notify LLGroupMgrObserver
			// Copy the map because observers may remove themselves on update
			observer_multimap_t observers = mObservers;

			// find all observers for this group id
			observer_multimap_t::iterator oi = observers.lower_bound(group_id);
			observer_multimap_t::iterator end = observers.upper_bound(group_id);
			for (; oi != end; ++oi)
			{
				oi->second->changed(gc);
			}
            group.second->mChanged = FALSE;


			// notify LLParticularGroupObserver
		    observer_map_t::iterator obs_it = mParticularObservers.find(group_id);
		    if(obs_it == mParticularObservers.end())
		        continue;

//		    observer_set_t& obs = obs_it->second;
// [RLVa:KB] - Checked: RLVa-2.2 (General bugfix)
			// Iterate over a *copy* of the observer list
		    observer_set_t obs = obs_it->second;
// [/RLVa:KB]
		    for (auto ob : obs)
            {
                ob->changed(group_id, gc);
		    }
		}
	}
}

void LLGroupMgr::addGroup(LLGroupMgrGroupData* group_datap)
{
	while (mGroups.size() >= MAX_CACHED_GROUPS)
	{
		// LRU: Remove the oldest un-observed group from cache until group size is small enough

		F32 oldest_access = LLFrameTimer::getTotalSeconds();
		group_map_t::iterator oldest_gi = mGroups.end();

		for (group_map_t::iterator gi = mGroups.begin(); gi != mGroups.end(); ++gi )
		{
			observer_multimap_t::iterator oi = mObservers.find(gi->first);
			if (oi == mObservers.end())
			{
				if (gi->second 
						&& (gi->second->getAccessTime() < oldest_access))
				{
					oldest_access = gi->second->getAccessTime();
					oldest_gi = gi;
				}
			}
		}
		
		if (oldest_gi != mGroups.end())
		{
			delete oldest_gi->second;
			mGroups.erase(oldest_gi);
		}
		else
		{
			// All groups must be currently open, none to remove.
			// Just add the new group anyway, but get out of this loop as it 
			// will never drop below max_cached_groups.
			break;
		}
	}

	mGroups[group_datap->getID()] = group_datap;
}


void LLGroupMgr::sendGroupPropertiesRequest(const LLUUID& group_id)
{
	LL_DEBUGS() << "LLGroupMgr::sendGroupPropertiesRequest" << LL_ENDL;
	// This will happen when we get the reply
	//LLGroupMgrGroupData* group_datap = createGroupData(group_id);
	
	LLMessageSystem* msg = gMessageSystem;
	msg->newMessage("GroupProfileRequest");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID",gAgent.getID());
	msg->addUUID("SessionID",gAgent.getSessionID());
	msg->nextBlock("GroupData");
	msg->addUUID("GroupID",group_id);
	gAgent.sendReliableMessage();
}

void LLGroupMgr::sendGroupMembersRequest(const LLUUID& group_id)
{
	LL_DEBUGS() << "LLGroupMgr::sendGroupMembersRequest" << LL_ENDL;
	LLGroupMgrGroupData* group_datap = createGroupData(group_id);
	if (group_datap->mMemberRequestID.isNull())
	{
		group_datap->removeMemberData();
		group_datap->mMemberRequestID.generate();

		LLMessageSystem* msg = gMessageSystem;
		msg->newMessage("GroupMembersRequest");
		msg->nextBlock("AgentData");
		msg->addUUID("AgentID",gAgent.getID());
		msg->addUUID("SessionID",gAgent.getSessionID());
		msg->nextBlock("GroupData");
		msg->addUUID("GroupID",group_id);
		msg->addUUID("RequestID",group_datap->mMemberRequestID);
		gAgent.sendReliableMessage();
	}
}


void LLGroupMgr::sendGroupRoleDataRequest(const LLUUID& group_id)
{
	LL_DEBUGS() << "LLGroupMgr::sendGroupRoleDataRequest" << LL_ENDL;
	LLGroupMgrGroupData* group_datap = createGroupData(group_id);
	if (group_datap->mRoleDataRequestID.isNull())
	{
		group_datap->removeRoleData();
		group_datap->mRoleDataRequestID.generate();

		LLMessageSystem* msg = gMessageSystem;
		msg->newMessage("GroupRoleDataRequest");
		msg->nextBlock("AgentData");
		msg->addUUID("AgentID",gAgent.getID());
		msg->addUUID("SessionID",gAgent.getSessionID());
		msg->nextBlock("GroupData");
		msg->addUUID("GroupID",group_id);
		msg->addUUID("RequestID",group_datap->mRoleDataRequestID);
		gAgent.sendReliableMessage();
	}
}

void LLGroupMgr::sendGroupRoleMembersRequest(const LLUUID& group_id)
{
	LL_DEBUGS() << "LLGroupMgr::sendGroupRoleMembersRequest" << LL_ENDL;
	LLGroupMgrGroupData* group_datap = createGroupData(group_id);
	
	if (group_datap->mRoleMembersRequestID.isNull())
	{
		// Don't send the request if we don't have all the member or role data
		if (!group_datap->isMemberDataComplete()
			|| !group_datap->isRoleDataComplete())
		{
			// *TODO: KLW FIXME: Should we start a member or role data request?
			LL_INFOS() << " Pending: " << (group_datap->mPendingRoleMemberRequest ? "Y" : "N")
				<< " MemberDataComplete: " << (group_datap->mMemberDataComplete ? "Y" : "N")
				<< " RoleDataComplete: " << (group_datap->mRoleDataComplete ? "Y" : "N") << LL_ENDL;
			group_datap->mPendingRoleMemberRequest = TRUE;
			return;
		}

		group_datap->removeRoleMemberData();
		group_datap->mRoleMembersRequestID.generate();

		LLMessageSystem* msg = gMessageSystem;
		msg->newMessage("GroupRoleMembersRequest");
		msg->nextBlock("AgentData");
		msg->addUUID("AgentID",gAgent.getID());
		msg->addUUID("SessionID",gAgent.getSessionID());
		msg->nextBlock("GroupData");
		msg->addUUID("GroupID",group_id);
		msg->addUUID("RequestID",group_datap->mRoleMembersRequestID);
		gAgent.sendReliableMessage();
	}
}

void LLGroupMgr::sendGroupTitlesRequest(const LLUUID& group_id)
{
	LL_DEBUGS() << "LLGroupMgr::sendGroupTitlesRequest" << LL_ENDL;
	LLGroupMgrGroupData* group_datap = createGroupData(group_id);
	
	group_datap->mTitles.clear();
	group_datap->mTitlesRequestID.generate();

	LLMessageSystem* msg = gMessageSystem;
	msg->newMessage("GroupTitlesRequest");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID",gAgent.getID());
	msg->addUUID("SessionID",gAgent.getSessionID());
	msg->addUUID("GroupID",group_id);
	msg->addUUID("RequestID",group_datap->mTitlesRequestID);

	gAgent.sendReliableMessage();
}

void LLGroupMgr::sendGroupTitleUpdate(const LLUUID& group_id, const LLUUID& title_role_id)
{
	LL_DEBUGS() << "LLGroupMgr::sendGroupTitleUpdate" << LL_ENDL;

	LLMessageSystem* msg = gMessageSystem;
	msg->newMessage("GroupTitleUpdate");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID",gAgent.getID());
	msg->addUUID("SessionID",gAgent.getSessionID());
	msg->addUUID("GroupID",group_id);
	msg->addUUID("TitleRoleID",title_role_id);

	gAgent.sendReliableMessage();

	// Save the change locally
	LLGroupMgrGroupData* group_datap = createGroupData(group_id);
	for (auto& title : group_datap->mTitles)
    {
		if (title.mRoleID == title_role_id)
		{
            title.mSelected = TRUE;
		}
		else if (title.mSelected)
		{
            title.mSelected = FALSE;
		}
	}
}

// static
void LLGroupMgr::sendCreateGroupRequest(const std::string& name,
										const std::string& charter,
										U8 show_in_list,
										const LLUUID& insignia,
										S32 membership_fee,
										BOOL open_enrollment,
										BOOL allow_publish,
										BOOL mature_publish)
{
	LLMessageSystem* msg = gMessageSystem;
	msg->newMessage("CreateGroupRequest");
	msg->nextBlock("AgentData");
	msg->addUUID("AgentID",gAgent.getID());
	msg->addUUID("SessionID",gAgent.getSessionID());

	msg->nextBlock("GroupData");
	msg->addString("Name",name);
	msg->addString("Charter",charter);
	msg->addBOOL("ShowInList",show_in_list);
	msg->addUUID("InsigniaID",insignia);
	msg->addS32("MembershipFee",membership_fee);
	msg->addBOOL("OpenEnrollment",open_enrollment);
	msg->addBOOL("AllowPublish",allow_publish);
	msg->addBOOL("MaturePublish",mature_publish);

	gAgent.sendReliableMessage();
}

void LLGroupMgr::sendUpdateGroupInfo(const LLUUID& group_id)
{
	LL_DEBUGS() << "LLGroupMgr::sendUpdateGroupInfo" << LL_ENDL;
	LLGroupMgrGroupData* group_datap = createGroupData(group_id);

	LLMessageSystem* msg = gMessageSystem;

	msg->newMessageFast(_PREHASH_UpdateGroupInfo);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID,gAgent.getID());
	msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID());

	msg->nextBlockFast(_PREHASH_GroupData);
	msg->addUUIDFast(_PREHASH_GroupID,group_datap->getID());
	msg->addStringFast(_PREHASH_Charter,group_datap->mCharter);
	msg->addBOOLFast(_PREHASH_ShowInList,group_datap->mShowInList);
	msg->addUUIDFast(_PREHASH_InsigniaID,group_datap->mInsigniaID);
	msg->addS32Fast(_PREHASH_MembershipFee,group_datap->mMembershipFee);
	msg->addBOOLFast(_PREHASH_OpenEnrollment,group_datap->mOpenEnrollment);
	msg->addBOOLFast(_PREHASH_AllowPublish,group_datap->mAllowPublish);
	msg->addBOOLFast(_PREHASH_MaturePublish,group_datap->mMaturePublish);

	gAgent.sendReliableMessage();

	// Not expecting a response, so let anyone else watching know the data has changed.
	group_datap->mChanged = TRUE;
	notifyObservers(GC_PROPERTIES);
}

void LLGroupMgr::sendGroupRoleMemberChanges(const LLUUID& group_id)
{
	LL_DEBUGS() << "LLGroupMgr::sendGroupRoleMemberChanges" << LL_ENDL;
	LLGroupMgrGroupData* group_datap = createGroupData(group_id);

	if (group_datap->mRoleMemberChanges.empty()) return;

	LLMessageSystem* msg = gMessageSystem;

	bool start_message = true;
	for (LLGroupMgrGroupData::change_map_t::const_iterator citer = group_datap->mRoleMemberChanges.begin();
		 citer != group_datap->mRoleMemberChanges.end(); ++citer)
	{
		if (start_message)
		{
			msg->newMessage("GroupRoleChanges");
			msg->nextBlockFast(_PREHASH_AgentData);
			msg->addUUIDFast(_PREHASH_AgentID,gAgent.getID());
			msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID());
			msg->addUUIDFast(_PREHASH_GroupID,group_id);
			start_message = false;
		}
		msg->nextBlock("RoleChange");
		msg->addUUID("RoleID",citer->second.mRole);
		msg->addUUID("MemberID",citer->second.mMember);
		msg->addU32("Change",(U32)citer->second.mChange);

		if (msg->isSendFullFast())
		{
			gAgent.sendReliableMessage();
			start_message = true;
		}
	}

	if (!start_message)
	{
		gAgent.sendReliableMessage();
	}

	group_datap->mRoleMemberChanges.clear();

	// Not expecting a response, so let anyone else watching know the data has changed.
	group_datap->mChanged = TRUE;
	notifyObservers(GC_ROLE_MEMBER_DATA);
}

//static
void LLGroupMgr::sendGroupMemberJoin(const LLUUID& group_id)
{
	LLMessageSystem *msg = gMessageSystem;

	msg->newMessageFast(_PREHASH_JoinGroupRequest);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	msg->nextBlockFast(_PREHASH_GroupData);
	msg->addUUIDFast(_PREHASH_GroupID, group_id);

	gAgent.sendReliableMessage();
}

// member_role_pairs is <member_id,role_id>
// static
void LLGroupMgr::sendGroupMemberInvites(const LLUUID& group_id, std::map<LLUUID,LLUUID>& member_role_pairs)
{
	bool start_message = true;
	LLMessageSystem* msg = gMessageSystem;

	for (auto& member_role_pair : member_role_pairs)
    {
		if (start_message)
		{
			msg->newMessage("InviteGroupRequest");
			msg->nextBlock("AgentData");
			msg->addUUID("AgentID",gAgent.getID());
			msg->addUUID("SessionID",gAgent.getSessionID());
			msg->nextBlock("GroupData");
			msg->addUUID("GroupID",group_id);
			start_message = false;
		}

		msg->nextBlock("InviteData");
		msg->addUUID("InviteeID", member_role_pair.first);
		msg->addUUID("RoleID", member_role_pair.second);

		if (msg->isSendFull())
		{
			gAgent.sendReliableMessage();
			start_message = true;
		}
	}

	if (!start_message)
	{
		gAgent.sendReliableMessage();
	}
}

//static
void LLGroupMgr::sendGroupMemberEjects(const LLUUID& group_id,
									   uuid_vec_t& member_ids)
{
	bool start_message = true;
	LLMessageSystem* msg = gMessageSystem;

	LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id);
	if (!group_datap) return;

	for (auto& ejected_member_id : member_ids)
    {
        // Can't use 'eject' to leave a group.
		if (ejected_member_id == gAgent.getID()) continue;

		// Make sure they are in the group, and we need the member data
		LLGroupMgrGroupData::member_list_t::iterator mit = group_datap->mMembers.find(ejected_member_id);
		if (mit != group_datap->mMembers.end())
		{
			// Add them to the message
			if (start_message)
			{
				msg->newMessage("EjectGroupMemberRequest");
				msg->nextBlock("AgentData");
				msg->addUUID("AgentID",gAgent.getID());
				msg->addUUID("SessionID",gAgent.getSessionID());
				msg->nextBlock("GroupData");
				msg->addUUID("GroupID",group_id);
				start_message = false;
			}
			
			msg->nextBlock("EjectData");
			msg->addUUID("EjecteeID",ejected_member_id);

			if (msg->isSendFull())
			{
				gAgent.sendReliableMessage();
				start_message = true;
			}

			LLGroupMemberData* member_data = (*mit).second;

			// Clean up groupmgr
			for (LLGroupMemberData::role_list_t::iterator rit = member_data->roleBegin();
				 rit != member_data->roleEnd(); ++rit)
			{
				if ((*rit).first.notNull() && (*rit).second!=nullptr)
				{
					(*rit).second->removeMember(ejected_member_id);
				}
			}
			
			group_datap->mMembers.erase(ejected_member_id);
			
			// member_data was introduced and is used here instead of (*mit).second to avoid crash because of invalid iterator
			// It becomes invalid after line with erase above. EXT-4778
			delete member_data;
		}
	}

	if (!start_message)
	{
		gAgent.sendReliableMessage();
	}

	group_datap->mMemberVersion.generate();
}

void LLGroupMgr::getGroupBanRequestCoro(std::string url, LLUUID groupId)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
	auto httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("groupMembersRequest", httpPolicy);
    auto httpRequest = std::make_shared<LLCore::HttpRequest>();

    std::string finalUrl = url + "?group_id=" + groupId.asString();

    LLSD result = httpAdapter->getAndSuspend(httpRequest, finalUrl);

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if (!status)
    {
        LL_WARNS("GrpMgr") << "Error receiving group member data " << LL_ENDL;
        return;
    }

    if (result.has("ban_list"))
    {
        result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
        // group ban data received
        processGroupBanRequest(result);
    }
}

void LLGroupMgr::postGroupBanRequestCoro(std::string url, LLUUID groupId,
    U32 action, uuid_vec_t banList, bool update)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
	auto httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("groupMembersRequest", httpPolicy);
    auto httpRequest = std::make_shared<LLCore::HttpRequest>();
    auto httpHeaders = std::make_shared<LLCore::HttpHeaders>();
    auto httpOptions = std::make_shared<LLCore::HttpOptions>();

    httpOptions->setFollowRedirects(false);

    httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML);


    std::string finalUrl = url + "?group_id=" + groupId.asString();

    LLSD postData = LLSD::emptyMap();
    postData["ban_action"] = (LLSD::Integer)action;
    // Add our list of potential banned residents to the list
    postData["ban_ids"] = LLSD::emptyArray();
    LLSD banEntry;

    uuid_vec_t::const_iterator it = banList.begin();
    for (; it != banList.end(); ++it)
    {
        banEntry = (*it);
        postData["ban_ids"].append(banEntry);
    }

    LL_WARNS() << "post: " << ll_pretty_print_sd(postData) << LL_ENDL;

    LLSD result = httpAdapter->postAndSuspend(httpRequest, finalUrl, postData, httpOptions, httpHeaders);

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if (!status)
    {
        LL_WARNS("GrpMgr") << "Error posting group member data " << LL_ENDL;
        return;
    }

    if (result.has("ban_list"))
    {
        result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
        // group ban data received
        processGroupBanRequest(result);
    }

    if (update)
    {
        getGroupBanRequestCoro(url, groupId);
    }
}

void LLGroupMgr::sendGroupBanRequest(	EBanRequestType request_type, 
										const LLUUID& group_id, 
										U32 ban_action, /* = BAN_NO_ACTION */
										const std::vector<LLUUID> &ban_list) /* = std::vector<LLUUID>() */
{
	LLViewerRegion* currentRegion = gAgent.getRegion();
	if(!currentRegion)
	{
		LL_WARNS("GrpMgr") << "Agent does not have a current region. Uh-oh!" << LL_ENDL;
		return;
	}

	// Check to make sure we have our capabilities
	if(!currentRegion->capabilitiesReceived())
	{
		LL_WARNS("GrpMgr") << " Capabilities not received!" << LL_ENDL;
		return;
	}

	// Get our capability
	std::string cap_url =  currentRegion->getCapability("GroupAPIv1");
	if(cap_url.empty())
	{
		return;
	}

    U32 action = ban_action & ~BAN_UPDATE;
    bool update = ((ban_action & BAN_UPDATE) == BAN_UPDATE);

    switch (request_type)
    {
    case REQUEST_GET:
        LLCoros::instance().launch("LLGroupMgr::getGroupBanRequestCoro",
            boost::bind(&LLGroupMgr::getGroupBanRequestCoro, this, cap_url, group_id));
        break;
    case REQUEST_POST:
        LLCoros::instance().launch("LLGroupMgr::postGroupBanRequestCoro",
            boost::bind(&LLGroupMgr::postGroupBanRequestCoro, this, cap_url, group_id, 
            action, ban_list, update));
        break;
    case REQUEST_PUT:
    case REQUEST_DEL:
        break;
    }
}

void LLGroupMgr::processGroupBanRequest(const LLSD& content)
{
	// Did we get anything in content?
	if(!content.size())
	{
		LL_WARNS("GrpMgr") << "No group member data received." << LL_ENDL;
		return;
	}

	LLUUID group_id = content["group_id"].asUUID();
	
	LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id);
	if (!gdatap)
		return;

	gdatap->clearBanList();
	LLSD::map_const_iterator i		= content["ban_list"].beginMap();
	LLSD::map_const_iterator iEnd	= content["ban_list"].endMap();
	for(;i != iEnd; ++i)
	{
		const LLUUID ban_id(i->first);
		LLSD ban_entry(i->second);
		
		LLGroupBanData ban_data;
		if(ban_entry.has("ban_date"))
		{
			ban_data.mBanDate = ban_entry["ban_date"].asDate();
			// TODO: Ban Reason
		}

		gdatap->createBanEntry(ban_id, ban_data);
	}

	gdatap->mChanged = TRUE;
	LLGroupMgr::getInstance()->notifyObservers(GC_BANLIST);
}

void LLGroupMgr::groupMembersRequestCoro(std::string url, LLUUID groupId)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
	auto httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("groupMembersRequest", httpPolicy);
    auto httpRequest = std::make_shared<LLCore::HttpRequest>();
    LLCore::HttpOptions::ptr_t httpOpts = std::make_shared<LLCore::HttpOptions>();

    mMemberRequestInFlight = true;

    LLSD postData = LLSD::emptyMap();
    postData["group_id"] = groupId;

    LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData, httpOpts);

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if (!status)
    {
        LL_WARNS("GrpMgr") << "Error receiving group member data " << LL_ENDL;
        mMemberRequestInFlight = false;
        return;
    }

    result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
    LLGroupMgr::processCapGroupMembersRequest(result);
    mMemberRequestInFlight = false;
}

void LLGroupMgr::sendCapGroupMembersRequest(const LLUUID& group_id)
{
    static U32 lastGroupMemberRequestFrame = 0;

	// Have we requested the information already this frame?
    // Todo: make this per group, we can invite to one group and simultaneously be checking another one
    if ((lastGroupMemberRequestFrame == gFrameCount) || (mMemberRequestInFlight))
		return;
	
	LLViewerRegion* currentRegion = gAgent.getRegion();
	// Thank you FS:Ansariel!
	if(!currentRegion)
	{
		LL_WARNS("GrpMgr") << "Agent does not have a current region. Uh-oh!" << LL_ENDL;
		return;
	}

	// Check to make sure we have our capabilities
	if(!currentRegion->capabilitiesReceived())
	{
		LL_WARNS("GrpMgr") << " Capabilities not received!" << LL_ENDL;
		return;
	}

	// Get our capability
	std::string cap_url =  currentRegion->getCapability("GroupMemberData");

	// Thank you FS:Ansariel!
	if(cap_url.empty())
	{
		LL_INFOS("GrpMgr") << "Region has no GroupMemberData capability.  Falling back to UDP fetch." << LL_ENDL;
		sendGroupMembersRequest(group_id);
		return;
	}

    LLGroupMgrGroupData* group_datap = createGroupData(group_id); //make sure group exists
    group_datap->mMemberRequestID.generate(); // mark as pending

    lastGroupMemberRequestFrame = gFrameCount;

    LLCoros::instance().launch("LLGroupMgr::groupMembersRequestCoro",
        boost::bind(&LLGroupMgr::groupMembersRequestCoro, this, cap_url, group_id));
}


void LLGroupMgr::processCapGroupMembersRequest(const LLSD& content)
{
	// Did we get anything in content?
	if(!content.size())
	{
		LL_DEBUGS("GrpMgr") << "No group member data received." << LL_ENDL;
		return;
	}

	LLUUID group_id = content["group_id"].asUUID();

	LLGroupMgrGroupData* group_datap = getGroupData(group_id);
	if(!group_datap)
	{
		LL_WARNS("GrpMgr") << "Received incorrect, possibly stale, group or request id" << LL_ENDL;
		return;
	}

	// If we have no members, there's no reason to do anything else
	S32	num_members	= content["member_count"];
	if (num_members < 1)
	{
		LL_INFOS("GrpMgr") << "Received empty group members list for group id: " << group_id.asString() << LL_ENDL;
		// Set mMemberDataComplete for correct handling of empty responses. See MAINT-5237
		group_datap->mMemberDataComplete = true;
		group_datap->mChanged = TRUE;
		LLGroupMgr::getInstance()->notifyObservers(GC_MEMBER_DATA);
		return;
	}
	
	group_datap->mMemberCount = num_members;

	LLSD	member_list	= content["members"];
	LLSD	titles		= content["titles"];
	LLSD	defaults	= content["defaults"];

	std::string online_status;
	std::string title;
	S32			contribution;
	U64			member_powers = 0;
	U64			default_powers = 0;
	// If this is changed to a bool, make sure to change the LLGroupMemberData constructor
	BOOL		is_owner;

	// Compute this once, rather than every time.
	if (defaults.has("default_powers") && !defaults["default_powers"].asString().empty())
	{
		try
		{
			default_powers = std::stoull(defaults["default_powers"].asString(), nullptr, 16);
		}
		catch(const std::invalid_argument& e)
		{
			LL_WARNS() << "Group member default powers returned exception: " << e.what() << LL_ENDL;
		}
	}

	LLSD::map_const_iterator member_iter_start	= member_list.beginMap();
	LLSD::map_const_iterator member_iter_end	= member_list.endMap();
	for( ; member_iter_start != member_iter_end; ++member_iter_start)
	{
		// Reset defaults
		online_status	= "unknown";
		title			= titles[0].asString();
		contribution	= 0;
		member_powers	= default_powers;
		is_owner		= false;

		const LLUUID member_id(member_iter_start->first);
		LLSD member_info = member_iter_start->second;
		
		if(member_info.has("last_login"))
		{
			online_status = member_info["last_login"].asString();
			if(online_status == "Online")
				online_status = LLTrans::getString("group_member_status_online");
			else
				formatDateString(online_status);
		}

		if(member_info.has("title"))
			title = titles[member_info["title"].asInteger()].asString();

		if (member_info.has("powers"))
		{
			try
			{
				member_powers = std::stoull(member_info["powers"].asString(), nullptr, 16);
			}
			catch (const std::invalid_argument& e)
			{
				LL_WARNS() << "Group member powers returned exception: " << e.what() << LL_ENDL;
			}
		}

		if(member_info.has("donated_square_meters"))
			contribution = member_info["donated_square_meters"];

		if(member_info.has("owner"))
			is_owner = true;

		LLGroupMemberData* data = new LLGroupMemberData(member_id, 
			contribution, 
			member_powers, 
			title,
			online_status,
			is_owner);

		LLGroupMemberData* member_old = group_datap->mMembers[member_id];
		if (member_old && group_datap->mRoleMemberDataComplete)
		{
			LLGroupMemberData::role_list_t::iterator rit = member_old->roleBegin();
			LLGroupMemberData::role_list_t::iterator end = member_old->roleEnd();

			for ( ; rit != end; ++rit)
			{
				data->addRole((*rit).first,(*rit).second);
			}
		}
		else
		{
			group_datap->mRoleMemberDataComplete = false;
		}

		group_datap->mMembers[member_id] = data;
	}

	group_datap->mMemberVersion.generate();

	// Technically, we have this data, but to prevent completely overhauling
	// this entire system (it would be nice, but I don't have the time), 
	// I'm going to be dumb and just call services I most likely don't need 
	// with the thought being that the system might need it to be done.
	// 
	// TODO:
	// Refactor to reduce multiple calls for data we already have.
	if(group_datap->mTitles.empty())
		sendGroupTitlesRequest(group_id);


	group_datap->mMemberDataComplete = true;
	group_datap->mMemberRequestID.setNull();
	// Make the role-member data request
	if (group_datap->mPendingRoleMemberRequest || !group_datap->mRoleMemberDataComplete)
	{
		group_datap->mPendingRoleMemberRequest = false;
		sendGroupRoleMembersRequest(group_id);
	}

	group_datap->mChanged = TRUE;
	notifyObservers(GC_MEMBER_DATA);

}


void LLGroupMgr::sendGroupRoleChanges(const LLUUID& group_id)
{
	LL_DEBUGS() << "LLGroupMgr::sendGroupRoleChanges" << LL_ENDL;
	LLGroupMgrGroupData* group_datap = getGroupData(group_id);

	if (group_datap && group_datap->pendingRoleChanges())
	{
		group_datap->sendRoleChanges();
	
		// Not expecting a response, so let anyone else watching know the data has changed.
		group_datap->mChanged = TRUE;
		notifyObservers(GC_ROLE_DATA);
	}
}

void LLGroupMgr::cancelGroupRoleChanges(const LLUUID& group_id)
{
	LL_DEBUGS() << "LLGroupMgr::cancelGroupRoleChanges" << LL_ENDL;
	LLGroupMgrGroupData* group_datap = getGroupData(group_id);

	if (group_datap) group_datap->cancelRoleChanges();
}

//static
bool LLGroupMgr::parseRoleActions(const std::string& xml_filename)
{
	LLXMLNodePtr root;

	BOOL success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root);	
	
	if (!success || !root || !root->hasName( "role_actions" ))
	{
		LL_ERRS() << "Problem reading UI role_actions file: " << xml_filename << LL_ENDL;
		return false;
	}

	LLXMLNodeList role_list;

	root->getChildren("action_set", role_list, false);
	
	for (auto& role_iter : role_list)
    {
		LLXMLNodePtr action_set = role_iter.second;

		LLRoleActionSet* role_action_set = new LLRoleActionSet();
		LLRoleAction* role_action_data = new LLRoleAction();
		
		// name=
		std::string action_set_name;
		if (action_set->getAttributeString("name", action_set_name))
		{
			LL_DEBUGS() << "Loading action set " << action_set_name << LL_ENDL;
			role_action_data->mName = action_set_name;
		}
		else
		{
			LL_WARNS() << "Unable to parse action set with no name" << LL_ENDL;
			delete role_action_set;
			delete role_action_data;
			continue;
		}
		// description=
		std::string set_description;
		if (action_set->getAttributeString("description", set_description))
		{
			role_action_data->mDescription = set_description;
		}
		// long description=
		std::string set_longdescription;
		if (action_set->getAttributeString("longdescription", set_longdescription))
		{
			role_action_data->mLongDescription = set_longdescription;
		}

		// power mask=
		U64 set_power_mask = 0;

		LLXMLNodeList action_list;
		LLXMLNodeList::iterator action_iter;

		action_set->getChildren("action", action_list, false);

		for (action_iter = action_list.begin(); action_iter != action_list.end(); ++action_iter)
		{
			LLXMLNodePtr action = action_iter->second;

			LLRoleAction* role_action = new LLRoleAction();

			// name=
			std::string action_name;
			if (action->getAttributeString("name", action_name))
			{
				LL_DEBUGS() << "Loading action " << action_name << LL_ENDL;
				role_action->mName = action_name;
			}
			else
			{
				LL_WARNS() << "Unable to parse action with no name" << LL_ENDL;
				delete role_action;
				continue;
			}
			// description=
			std::string description;
			if (action->getAttributeString("description", description))
			{
				role_action->mDescription = description;
			}
			// long description=
			std::string longdescription;
			if (action->getAttributeString("longdescription", longdescription))
			{
				role_action->mLongDescription = longdescription;
			}
			// description=
			S32 power_bit = 0;
			if (action->getAttributeS32("value", power_bit))
			{
				if (0 <= power_bit && power_bit < 64)
				{
					role_action->mPowerBit = 0x1LL << power_bit;
				}
			}

			set_power_mask |= role_action->mPowerBit;
	
			role_action_set->mActions.push_back(role_action);
		}

		role_action_data->mPowerBit = set_power_mask;
		role_action_set->mActionSetData = role_action_data;

		LLGroupMgr::getInstance()->mRoleActionSets.push_back(role_action_set);
	}
	return true;
}

// static
void LLGroupMgr::debugClearAllGroups(void*)
{
	LLGroupMgr::getInstance()->clearGroups();
	LLGroupMgr::parseRoleActions("role_actions.xml");
}


