/*
 * groupmanager.cpp by Anders Reggestad <andersr@pvv.org>
 *
 * Copyright (C) 2001 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) 
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation (version 2 of the License)
 * This program 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 General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
#include <psconfig.h>
#include <physicallayer/entity.h>
#include <propclass/mesh.h>

#include "client.h"
#include "clients.h"
#include "invitemanager.h"
#include "playergroup.h"
#include "groupmanager.h"
#include "util/log.h"
#include "util/serverconsole.h"
#include "util/psxmlparser.h"
#include "util/eventmanager.h"
#include "gem.h"
#include "psserver.h"
#include "globals.h"
#include "chatmanager.h"


/** A structure to hold the clients that are pending on joining a group.
 */
class PendingGroupInvite : public PendingInvite
{
public:
    int groupID;

    PendingGroupInvite(Client *inviter,
                       Client *invitee,
                       const char *question,
                       PlayerGroup *g)
        : PendingInvite( inviter, invitee, true, 
                        question,"Accept","Decline",
                        "You have invited %s to join your group.",
                        "%s has invited you to group.",
                        "%s has joined your group.",
                        "You have joined the group.",
                        "%s has declined your invitation.",
                        "You have declined %s's invitation.",
                        psQuestionMessage::generalConfirm)
    {
        groupID   = g->id;
    }

    virtual ~PendingGroupInvite() {}

    void HandleAnswer(const csString & answer);
};




PlayerGroup::PlayerGroup()
    :id(next_id++)
{
}

PlayerGroup::PlayerGroup(GroupManager * mgr, gemActor * leader)
    :manager(mgr), leader(leader),id(next_id++)
{
    members.Push(leader);
    leader->SetGroup(this);
}

PlayerGroup::~PlayerGroup()
{
}

void PlayerGroup::Add(gemActor *new_member)
{
    members.Push(new_member);
    new_member->SetGroup(this);
}

void PlayerGroup::Remove(gemActor *member)
{
    psserver->SendSystemInfo(leader->GetClientID(),"%s has left the group",member->GetName());

    // Delete member from group
    member->SetGroup(NULL);
    members.Delete(member);    

    // Check if we need to find a new leader for group
    if (member == leader)
    {
        if ( members.Length() )
        {
            leader = members[0];
            psserver->SendSystemInfo(leader->GetClientID(), "You are now group leader.");
        }
        else
        {
            leader = 0;
            manager->Remove(this);
            return;    // return immediately because our instance was deleted
        }
        
    }

    // If only one member left in the group then disband it.
    if ( members.Length() == 1 )
    {   
        Disband();
        manager->Remove(this);        
        return;
    }
    
    if (!IsEmpty())
    {
        BroadcastMemberList();
    }
}

void PlayerGroup::ListMembers(gemActor * client)
{
    int clientnum = client->GetClientID();
    csString str;
    
    psserver->SendSystemInfo(clientnum, "-------Group members-------");
    for (size_t n = 0; n < members.Length(); n++)
    {
        str =  "-";
        str += members[n]->GetName();
        psserver->SendSystemInfo(clientnum, str);
    }
}

void PlayerGroup::Broadcast(MsgEntry *me)
{
    // Copy message to send out to everyone
    MsgEntry *newmsg = new MsgEntry(me);
    if (newmsg->overrun)
    {
        Bug1("Could not copy MsgEntry for PlayerGroup::Broadcast.\n");
        newmsg->DecRef();
        return;
    }

    newmsg->msgid = (uintptr_t) newmsg;
    
    for (size_t n = 0; n < members.Length(); n++)
    {
        newmsg->clientnum = members[n]->GetClientID();
        psserver->GetEventManager()->SendMessage(newmsg);
    }
    
    CHECK_FINAL_DECREF(newmsg,"GroupMsg");
    newmsg->DecRef();
}


bool PlayerGroup::IsLeader(gemActor * client)
{
    return (leader == client);
}

void PlayerGroup::Disband()
{
    psGUIGroupMessage msg(0,psGUIGroupMessage::LEAVE,"Group Disbanded By Leader");
    if (!msg.valid)
    {
        Bug1("Could not create valid psGUIGroupMessage for group.\n");
        return;
    }


    for (size_t n = 0; n < members.Length(); n++)
    {
        msg.msg->clientnum = members[n]->GetClientID();
        psserver->GetEventManager()->SendMessage(msg.msg);
        members[n]->SetGroup(NULL);
    }
    members.DeleteAll();

    leader = 0;
}

bool PlayerGroup::IsEmpty()
{
    return (members.Length() == 0);
}

float fixZero(float f)
{
    return f == 0 ? 1 : f;
}

void PlayerGroup::BroadcastMemberList()
{
    csString list;
    list.Append("<L>");
    for (size_t n = 0; n < members.Length(); n++)
    {
        csString buff;
        psCharacter *charData = members[n]->GetCharacterData();
        
    csString escpxml = EscpXML(members[n]->GetCharacterData()->GetCharName());
        buff.Format("<M N=\"%s\" H=\"%.2f\" M=\"%.2f\" PS=\"%.2f\" MS=\"%.2f\"/>",
                escpxml.GetData(),
                charData->AdjustHitPoints(0.0) / fixZero(charData->AdjustHitPointsMax(0.0)) * 100,
                charData->AdjustMana(0.0) / fixZero(charData->AdjustManaMax(0.0)) * 100,
                charData->GetStamina(true) / fixZero(charData->GetStaminaMax(true)) * 100,
                charData->GetStamina(false) / fixZero(charData->GetStaminaMax(false)) * 100);
        
        list.Append(buff);
    }
    list.Append("</L>");

    psGUIGroupMessage msg(0,psGUIGroupMessage::MEMBERS,list.GetData());
    if (msg.valid)
        Broadcast(msg.msg);
    else
    {
        Bug1("Could not create valid psGUIGroupMessage for broadcast.\n");
    }

}

bool PlayerGroup::HasMember(gemActor *member)
{
    for (size_t i = 0; i < members.Length(); i++)
        if (members[i] == member)
            return true;
    return false;
}


//---------------------------------------------------------------------------

int PlayerGroup::next_id = 1;

GroupManager::GroupManager(ClientConnectionSet *cs, ChatManager *chat)
{
    clients    = cs;
    chatserver = chat;  // Needed to GROUPSAY things.

    psserver->GetEventManager()->Subscribe(this,MSGTYPE_GROUPCMD,REQUIRE_READY_CLIENT|REQUIRE_ALIVE);
}

GroupManager::~GroupManager()
{
    if (psserver->GetEventManager())
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_GROUPCMD);
}

void GroupManager::HandleMessage(MsgEntry *me, Client *client)
{
    psGroupCmdMessage msg(me);
    if (!msg.valid)
    {
        Debug2(LOG_NET,me->clientnum,"Failed to parse psGroupCmdMessage from client %u.\n",me->clientnum);
        return;
    }

    if (!msg.valid)
    {
        Error1("Command not supported");
        return;
    }

    if (msg.command == "/invite")
    {
        Invite(msg,client);
    }
    else if (msg.command == "/groupremove")
    {
        RemovePlayerFromGroup(msg,client->GetActor());
    }
    else if (msg.command == "/disband")
    {
        Disband(msg,client->GetActor());
    }
    else if (msg.command == "/leavegroup")
    {
        Leave(msg,client->GetActor());
    }
    else if (msg.command == "/groupmembers")
    {
        ListMembers(msg,client->GetActor());
    }
    else
    {
        Error1("Command not supported by server.");
    }
}

void GroupManager::Invite(psGroupCmdMessage& msg,Client *inviter)
{
    csRef<PlayerGroup> group = inviter->GetActor()->GetGroup();

    csString playerName = msg.player;
    //Check for empty player name. If we don't do this, the server crashes
    if (playerName=="")
    {
        psserver->SendSystemError(inviter->GetClientNum(), "Please specify the player name to invite to your group.");
        return;
    }

    playerName = NormalizeCharacterName(playerName);
    
    // Check to see if the player is trying to invite themself.
    if ( playerName == inviter->GetName() )
    {
        psserver->SendSystemError(inviter->GetClientNum(), "Cannot invite yourself to a group.");
        return;
    }

    if (group && !group->IsLeader(inviter->GetActor()))
    {
        psserver->SendSystemError(inviter->GetClientNum(),"Only group leaders can invite new members.");
        return;
    }

    // invited player must be online when invited
    Client *invitee = clients->Find(playerName);

    if (!invitee)
    {
        psserver->SendSystemError(inviter->GetClientNum(),"%s is not online right now.",
                                  (const char*) playerName);
        return;
    }

    if (invitee->GetActor()->InGroup())
    {
        psserver->SendSystemError(inviter->GetClientNum(),"That player is already a member of a group.");
        return;
    }
    
    if (!group)
    {
        // Create a new group with client as leader
        group = NewGroup(inviter->GetActor());        
        psserver->SendSystemInfo(inviter->GetClientNum(),"You are group leader for the new group.");
    }
    

    /**
     * Notify the inviter that the invitation has been sent.
     * Notify the invitee that he/she has been invited and ask them to confirm.
     */
    psserver->SendSystemInfo(inviter->GetClientNum(),"Confirming group invitation with %s now...",invitee->GetName() );

    csString invitation;
    invitation.Format("%s has invited you to join a group.  Would you like to join it?",inviter->GetName() );
    PendingGroupInvite *pnew = new PendingGroupInvite(inviter,
                                                      invitee,
                                                      invitation,
                                                      group);
    
    // Track who is invited where, to verify confirmations
    psserver->questionmanager->SendQuestion(pnew);
}

void PendingGroupInvite::HandleAnswer(const csString & answer)
{
    Client * client = psserver->GetConnections()->Find(clientnum);
    if (!client  ||  client->GetActor()->InGroup())
        return;

            
    PendingInvite::HandleAnswer(answer);
    
    if (answer == "yes")
        psserver->groupmanager->HandleJoinGroup(this);                        
}


void GroupManager::Disband(psGroupCmdMessage& msg,gemActor *client)
{
    csRef<PlayerGroup> group = client->GetGroup();

    if (!group)
    {
        psserver->SendSystemInfo(client->GetClientID(),"You are not in a group.");
        return;
    }
    
    if (!group->IsLeader(client))
    {
        psserver->SendSystemInfo(client->GetClientID(),"Only group leader can disband group.");
        return;
    }

    group->Disband();
    Remove(group);
    
    psserver->SendSystemInfo(client->GetClientID(),"You have disbanded the group.");
}

PlayerGroup * GroupManager::FindGroup(int id)
{
    // TODO: Change to hashmap
    for (size_t grpNum=0; grpNum < groups.Length(); grpNum++)
        if (groups[grpNum]->id == id)
            return groups[grpNum];
    return NULL;
}

void GroupManager::HandleJoinGroup(PendingGroupInvite *invite)
{
    PlayerGroup * group = FindGroup(invite->groupID);
    if (group == NULL)
        return;

    Client * inviteeClient = clients->Find(invite->clientnum);
    Client * inviterClient = clients->Find(invite->inviterClientNum);
    
    if (!inviteeClient   ||   !AddPlayerToGroup(group,inviteeClient->GetActor()))
    {
        Error1("Error joining group!\n" );
        return;
    }

    if (inviterClient != NULL)
        GroupChat(inviterClient->GetActor(),"Player %s has joined the group!",invite->inviteeName.GetData() );
}

void GroupManager::Leave(psGroupCmdMessage& msg,gemActor *client)
{
    csRef<PlayerGroup> group = client->GetGroup();
    if (!group)
    {
        psserver->SendSystemInfo(client->GetClientID(), "You are not in a group.");
        return;
    }
    
    SendLeave(client);
    psserver->SendSystemInfo(client->GetClientID(),"You have left the group.");

    // If this leave the group empty it will be deleted when going out of this scope.
    group->Remove(client);
}

void GroupManager::ListMembers(psGroupCmdMessage& msg,gemActor *client)
{
    csRef<PlayerGroup> group = client->GetGroup();
    if (group)
    {
        group->ListMembers(client);
    }
    else
    {
        psserver->SendSystemInfo(client->GetClientID(),"You are not in a group" );
    }
}

void GroupManager::SendGroup(gemActor * client)
{
    csRef<PlayerGroup> group = client->GetGroup();
    csString buff;
    buff.Format("<GROUP ID=\"%d\" />",group->id);
    psGUIGroupMessage msg(client->GetClientID(),psGUIGroupMessage::GROUP,buff);
    msg.SendMessage();
}

void GroupManager::SendLeave(gemActor * client)
{
    psGUIGroupMessage msg(client->GetClientID(),psGUIGroupMessage::LEAVE,"");
    msg.SendMessage();
}

csPtr<PlayerGroup> GroupManager::NewGroup(gemActor * leader)
{
    csRef<PlayerGroup> group = csPtr<PlayerGroup>(new PlayerGroup(this,leader));

    groups.Push(group);

    SendGroup(leader);

    group->BroadcastMemberList();

    return csPtr<PlayerGroup>(group);
}

bool GroupManager::AddPlayerToGroup(PlayerGroup * group, gemActor *client)
{
    group->Add(client);

    SendGroup(client);

    group->BroadcastMemberList();
    
    return true;
}

void GroupManager::RemovePlayerFromGroup(psGroupCmdMessage& msg,gemActor *client)
{
    Client* targetClient;
    gemActor *targetClientActor;
    csRef<PlayerGroup> group;
 
    group = client->GetGroup();
    if (!group)
    {
        psserver->SendSystemInfo(client->GetClientID(), "You are not in a group.");
        return;
    }
 
    if (!msg.player || msg.player == "")
    {
        psserver->SendSystemInfo(client->GetClientID(), "You must specify the name of the member you want to remove.");
        return;
    }
 
    targetClient = clients->Find(NormalizeCharacterName(msg.player));
    if (!targetClient)
    {
        psserver->SendSystemInfo(client->GetClientID(), "Cound not find a player named %s.", msg.player.GetData());
        return;
    }
 
    if (!group->IsLeader(client))
    {
        psserver->SendSystemInfo(client->GetClientID(), "You must be the group leader to remove members.", msg.player.GetData());
        return;
    }
 
    targetClientActor = targetClient->GetActor();
    csRef<PlayerGroup> targetGroup = targetClientActor->GetGroup();
    if (!targetGroup || targetGroup->id != group->id)
    {
        psserver->SendSystemInfo(client->GetClientID(), "Player %s is not a member of your group.", msg.player.GetData());
        return;
    }
 
    SendLeave(targetClientActor);
    psserver->SendSystemInfo(targetClientActor->GetClientID(),"You were removed from the group.");
    group->Remove(targetClientActor);
}

void GroupManager::Remove(PlayerGroup * group)
{
    groups.Delete(group);
}


void GroupManager::GroupChat(gemActor * client, const char *fmt, ...)
{
    csString text;
    va_list args;

    va_start(args, fmt);
    text.FormatV(fmt,args);
    va_end(args);

    psChatMessage groupmsg(client->GetClientID(),"System",text,CHAT_GROUP, false);
    if (groupmsg.valid)
    {
        groupmsg.msg->Reset();
        Client *c = psserver->GetConnections()->Find(client->GetClientID());
        chatserver->HandleMessage(groupmsg.msg,c);
    }
    else
    {
        Bug2("Could not create a valid psChatMessage for client %u.\n",client->GetClientID());
    }
}

