/**
 * @file llgroupactions.cpp
 * @brief Group-related actions (join, leave, new, delete, etc)
 *
 * $LicenseInfo:firstyear=2009&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 "llgroupactions.h"

#include "message.h"

#include "llagent.h"
#include "llcommandhandler.h"
#include "llfloaterreg.h"
#include "llfloatersidepanelcontainer.h"
#include "llgroupmgr.h"
#include "llfloaterimcontainer.h"
#include "llimview.h" // for gIMMgr
#include "llnotificationsutil.h"
#include "llstartup.h"
#include "llstatusbar.h"    // can_afford_transaction()
#include "groupchatlistener.h"

//
// Globals
//
static LLGroupChatListener sGroupChatListener;

class LLGroupHandler : public LLCommandHandler
{
public:
    // requires trusted browser to trigger
    LLGroupHandler() : LLCommandHandler("group", UNTRUSTED_THROTTLE) { }

    virtual bool canHandleUntrusted(
        const LLSD& params,
        const LLSD& query_map,
        LLMediaCtrl* web,
        const std::string& nav_type)
    {
        if (params.size() < 1)
        {
            return true; // don't block, will fail later
        }

        if (nav_type == NAV_TYPE_CLICKED
            || nav_type == NAV_TYPE_EXTERNAL)
        {
            return true;
        }

        const std::string verb = params[0].asString();
        if (verb == "create")
        {
            return false;
        }
        return true;
    }

    bool handle(const LLSD& tokens,
                const LLSD& query_map,
                const std::string& grid,
                LLMediaCtrl* web)
    {
        if (LLStartUp::getStartupState() < STATE_STARTED)
        {
            return true;
        }

        if (tokens.size() < 1)
        {
            return false;
        }

        if (tokens[0].asString() == "create")
        {
            LLGroupActions::createGroup();
            return true;
        }

        if (tokens.size() < 2)
        {
            return false;
        }

        if (tokens[0].asString() == "list")
        {
            if (tokens[1].asString() == "show")
            {
                LLSD params;
                params["people_panel_tab_name"] = "groups_panel";
                LLFloaterSidePanelContainer::showPanel("people", "panel_people", params);
                return true;
            }
            return false;
        }

        LLUUID group_id;
        if (!group_id.set(tokens[0], false))
        {
            return false;
        }

        if (tokens[1].asString() == "about")
        {
            if (group_id.isNull())
                return true;

            LLGroupActions::show(group_id);

            return true;
        }
        if (tokens[1].asString() == "inspect")
        {
            if (group_id.isNull())
                return true;
            LLGroupActions::inspect(group_id);
            return true;
        }
        return false;
    }
};
LLGroupHandler gGroupHandler;

// This object represents a pending request for specified group member information
// which is needed to check whether avatar can leave group
class LLFetchGroupMemberData : public LLGroupMgrObserver
{
public:
    LLFetchGroupMemberData(const LLUUID& group_id) :
        mGroupId(group_id),
        mRequestProcessed(false),
        LLGroupMgrObserver(group_id)
    {
        LL_INFOS() << "Sending new group member request for group_id: "<< group_id << LL_ENDL;
        LLGroupMgr* mgr = LLGroupMgr::getInstance();
        // register ourselves as an observer
        mgr->addObserver(this);
        // send a request
        mgr->sendGroupPropertiesRequest(group_id);
        mgr->sendCapGroupMembersRequest(group_id);
    }

    ~LLFetchGroupMemberData()
    {
        if (!mRequestProcessed)
        {
            // Request is pending
            LL_WARNS() << "Destroying pending group member request for group_id: "
                << mGroupId << LL_ENDL;
        }
        // Remove ourselves as an observer
        LLGroupMgr::getInstance()->removeObserver(this);
    }

    void changed(LLGroupChange gc)
    {
        if (gc == GC_PROPERTIES && !mRequestProcessed)
        {
            LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupId);
            if (!gdatap)
            {
                LL_WARNS() << "LLGroupMgr::getInstance()->getGroupData() was NULL" << LL_ENDL;
            }
            else if (!gdatap->isMemberDataComplete())
            {
                LL_WARNS() << "LLGroupMgr::getInstance()->getGroupData()->isMemberDataComplete() was false" << LL_ENDL;
                processGroupData();
                mRequestProcessed = true;
            }
        }
    }

    LLUUID getGroupId() { return mGroupId; }
    virtual void processGroupData() = 0;
protected:
    LLUUID mGroupId;
    bool mRequestProcessed;
};

class LLFetchLeaveGroupData: public LLFetchGroupMemberData
{
public:
     LLFetchLeaveGroupData(const LLUUID& group_id)
         : LLFetchGroupMemberData(group_id)
     {}
     void processGroupData()
     {
         LLGroupActions::processLeaveGroupDataResponse(mGroupId);
     }
     void changed(LLGroupChange gc)
     {
         if (gc == GC_PROPERTIES && !mRequestProcessed)
         {
             LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupId);
             if (!gdatap)
             {
                 LL_WARNS() << "GroupData was NULL" << LL_ENDL;
             }
             else
             {
                 processGroupData();
                 mRequestProcessed = true;
             }
         }
     }
};

LLFetchLeaveGroupData* gFetchLeaveGroupData = NULL;

// static
void LLGroupActions::search()
{
    LLFloaterReg::showInstance("search", LLSD().with("collection", "groups"));
}

// static
void LLGroupActions::startCall(const LLUUID& group_id)
{
    // create a new group voice session
    LLGroupData gdata;

    if (!gAgent.getGroupData(group_id, gdata))
    {
        LL_WARNS() << "Error getting group data" << LL_ENDL;
        return;
    }

    LLUUID session_id = gIMMgr->addSession(gdata.mName, IM_SESSION_GROUP_START, group_id, LLSD());
    if (session_id == LLUUID::null)
    {
        LL_WARNS() << "Error adding session" << LL_ENDL;
        return;
    }

    // start the call
    gIMMgr->autoStartCallOnStartup(session_id);

    make_ui_sound("UISndStartIM");
}

// static
void LLGroupActions::join(const LLUUID& group_id)
{
    if (!gAgent.canJoinGroups())
    {
        LLNotificationsUtil::add("JoinedTooManyGroups");
        return;
    }

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

    if (gdatap)
    {
        S32 cost = gdatap->mMembershipFee;
        LLSD args;
        args["COST"] = llformat("%d", cost);
        args["NAME"] = gdatap->mName;
        LLSD payload;
        payload["group_id"] = group_id;

        if (can_afford_transaction(cost))
        {
            if(cost > 0)
                LLNotificationsUtil::add("JoinGroupCanAfford", args, payload, onJoinGroup);
            else
                LLNotificationsUtil::add("JoinGroupNoCost", args, payload, onJoinGroup);

        }
        else
        {
            LLNotificationsUtil::add("JoinGroupCannotAfford", args, payload);
        }
    }
    else
    {
        LL_WARNS() << "LLGroupMgr::getInstance()->getGroupData(" << group_id
            << ") was NULL" << LL_ENDL;
    }
}

// static
bool LLGroupActions::onJoinGroup(const LLSD& notification, const LLSD& response)
{
    S32 option = LLNotificationsUtil::getSelectedOption(notification, response);

    if (option == 1)
    {
        // user clicked cancel
        return false;
    }

    LLGroupMgr::getInstance()->
        sendGroupMemberJoin(notification["payload"]["group_id"].asUUID());
    return false;
}

// static
void LLGroupActions::leave(const LLUUID& group_id)
{
    if (group_id.isNull())
    {
        return;
    }

    LLGroupData group_data;
    if (gAgent.getGroupData(group_id, group_data))
    {
        LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id);
        if (!gdatap || !gdatap->isMemberDataComplete())
        {
            if (gFetchLeaveGroupData != NULL)
            {
                delete gFetchLeaveGroupData;
                gFetchLeaveGroupData = NULL;
            }
            gFetchLeaveGroupData = new LLFetchLeaveGroupData(group_id);
        }
        else
        {
            processLeaveGroupDataResponse(group_id);
        }
    }
}

//static
void LLGroupActions::processLeaveGroupDataResponse(const LLUUID group_id)
{
    LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id);
    LLUUID agent_id = gAgent.getID();
    LLGroupMgrGroupData::member_list_t::iterator mit = gdatap->mMembers.find(agent_id);
    //get the member data for the group
    if ( mit != gdatap->mMembers.end() )
    {
        LLGroupMemberData* member_data = (*mit).second;

        if ( member_data && member_data->isOwner() && gdatap->mMemberCount == 1)
        {
            LLNotificationsUtil::add("OwnerCannotLeaveGroup");
            return;
        }
    }
    LLSD args;
    args["GROUP"] = gdatap->mName;
    LLSD payload;
    payload["group_id"] = group_id;
    if (gdatap->mMembershipFee > 0)
    {
        args["COST"] = gdatap->mMembershipFee;
    LLNotificationsUtil::add("GroupLeaveConfirmMember", args, payload, onLeaveGroup);
}
    else
    {
        LLNotificationsUtil::add("GroupLeaveConfirmMemberNoFee", args, payload, onLeaveGroup);
    }

}

// static
void LLGroupActions::activate(const LLUUID& group_id)
{
    LLMessageSystem* msg = gMessageSystem;
    msg->newMessageFast(_PREHASH_ActivateGroup);
    msg->nextBlockFast(_PREHASH_AgentData);
    msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
    msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
    msg->addUUIDFast(_PREHASH_GroupID, group_id);
    gAgent.sendReliableMessage();
}

static bool isGroupUIVisible()
{
    static LLPanel* panel = 0;
    if(!panel)
        panel = LLFloaterSidePanelContainer::getPanel("people", "panel_group_info_sidetray");
    if(!panel)
        return false;
    return panel->isInVisibleChain();
}

// static
void LLGroupActions::inspect(const LLUUID& group_id)
{
    LLFloaterReg::showInstance("inspect_group", LLSD().with("group_id", group_id));
}

// static
void LLGroupActions::show(const LLUUID &group_id, bool expand_notices_tab)
{
    if (group_id.isNull())
        return;

    LLSD params;
    params["group_id"] = group_id;
    params["open_tab_name"] = "panel_group_info_sidetray";
    if (expand_notices_tab)
    {
        params["action"] = "show_notices";
    }

    LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params);
    LLFloater *floater = LLFloaterReg::getTypedInstance<LLFloaterSidePanelContainer>("people");
    if (!floater->isFrontmost())
    {
        floater->setVisibleAndFrontmost(true, params);
    }
}

void LLGroupActions::refresh_notices()
{
    if(!isGroupUIVisible())
        return;

    LLSD params;
    params["group_id"] = LLUUID::null;
    params["open_tab_name"] = "panel_group_info_sidetray";
    params["action"] = "refresh_notices";

    LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params);
}

//static
void LLGroupActions::refresh(const LLUUID& group_id)
{
    if(!isGroupUIVisible())
        return;

    LLSD params;
    params["group_id"] = group_id;
    params["open_tab_name"] = "panel_group_info_sidetray";
    params["action"] = "refresh";

    LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params);
}

//static
void LLGroupActions::createGroup()
{
    LLSD params;
    params["group_id"] = LLUUID::null;
    params["open_tab_name"] = "panel_group_creation_sidetray";
    params["action"] = "create";

    LLFloaterSidePanelContainer::showPanel("people", "panel_group_creation_sidetray", params);

}
//static
void LLGroupActions::closeGroup(const LLUUID& group_id)
{
    if(!isGroupUIVisible())
        return;

    LLSD params;
    params["group_id"] = group_id;
    params["open_tab_name"] = "panel_group_info_sidetray";
    params["action"] = "close";

    LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params);
}


// static
LLUUID LLGroupActions::startIM(const LLUUID& group_id)
{
    if (group_id.isNull()) return LLUUID::null;

    LLGroupData group_data;
    if (gAgent.getGroupData(group_id, group_data))
    {
        LLUUID session_id = gIMMgr->addSession(
            group_data.mName,
            IM_SESSION_GROUP_START,
            group_id);
        if (session_id != LLUUID::null)
        {
            LLFloaterIMContainer::getInstance()->showConversation(session_id);
        }
        make_ui_sound("UISndStartIM");
        return session_id;
    }
    else
    {
        // this should never happen, as starting a group IM session
        // relies on you belonging to the group and hence having the group data
        make_ui_sound("UISndInvalidOp");
        return LLUUID::null;
    }
}

// static
void LLGroupActions::endIM(const LLUUID& group_id)
{
    if (group_id.isNull())
        return;

    LLUUID session_id = gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id);
    if (session_id != LLUUID::null)
    {
        gIMMgr->leaveSession(session_id);
    }
}

// static
bool LLGroupActions::isInGroup(const LLUUID& group_id)
{
    // *TODO: Move all the LLAgent group stuff into another class, such as
    // this one.
    return gAgent.isInGroup(group_id);
}

// static
bool LLGroupActions::isAvatarMemberOfGroup(const LLUUID& group_id, const LLUUID& avatar_id)
{
    if(group_id.isNull() || avatar_id.isNull())
    {
        return false;
    }

    LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(group_id);
    if(!group_data)
    {
        return false;
    }

    if(group_data->mMembers.end() == group_data->mMembers.find(avatar_id))
    {
        return false;
    }

    return true;
}

//-- Private methods ----------------------------------------------------------

// static
bool LLGroupActions::onLeaveGroup(const LLSD& notification, const LLSD& response)
{
    S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
    LLUUID group_id = notification["payload"]["group_id"].asUUID();
    if(option == 0)
    {
        LLMessageSystem* msg = gMessageSystem;
        msg->newMessageFast(_PREHASH_LeaveGroupRequest);
        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();
    }
    return false;
}
