/*
 * chatmanager.cpp
 *
 * Copyright (C) 2003 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 <string.h>
#include <memory.h>

#include "util/serverconsole.h"
#include "cachemanager.h"
#include "chatmanager.h"
#include "clients.h"
#include "playergroup.h"
#include "gem.h"
#include "util/log.h"
#include "util/pserror.h"
#include "net/msghandler.h"
#include "bulkobjects/psnpcdialog.h"
#include "bulkobjects/dictionary.h"
#include "bulkobjects/psguildinfo.h"
#include "globals.h"
#include "psserver.h"
#include "npcmanager.h"
#include "util/eventmanager.h"
#include "util/strutil.h"




ChatManager::ChatManager()
{
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_CHAT,REQUIRE_ALIVE);
}

ChatManager::~ChatManager()
{
    psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_CHAT);
}

bool ChatManager::FloodControl(csString& newmessage, Client *client)
{
    size_t count=0;

    //Shovel 1 back
    client->ShovelFlood(newmessage);

    //Delete everything older than 10 (or if other defined) secs
    client->CheckBuffer();

    for (int i = 0; i <= (int)(client->GetFloodMax()-1); i++)
    {
        if (client->GetFlood(i).str==newmessage)
            count++;
    }

    //Now when the loops is finished, check what count is
    if (count==client->GetFloodWarn())
    {
        psserver->SendSystemInfo(client->GetClientNum(),"Flood warning. Stop or you will be muted.");
    }

    if (count==client->GetFloodMax())
    {
        client->SetMute(true);
        client->ClearFlood();
        psserver->SendSystemInfo(client->GetClientNum(),"BAM! Muted.");
        return false;
    }

    return true;
}

void ChatManager::HandleMessage(MsgEntry *me,Client *client)
{
    psChatMessage msg(me);

    // Dont
    if (!msg.valid)
    {
        Debug2(LOG_NET,me->clientnum,"Received unparsable psChatMessage from client %u.\n",me->clientnum);
        return;
    }

    char *pType;
    msg.GetTypeText(pType);

    if (msg.iChatType != CHAT_TELL)
    {
        Notify4(LOG_CHAT,
            "%s %s: %s\n", client->GetName(),
        pType, (const char *) msg.sText);
    }
    else
        Notify5(LOG_CHAT,"%s %s %s: %s\n", client->GetName(),
        pType, (const char *)msg.sPerson,(const char *)msg.sText);

    bool saveFlood = true;

    if (!client->IsMute())
    {
      // Send Chat to other players
      switch (msg.iChatType)
      {
          case CHAT_GUILD:
          {
              SendGuild(client, msg);
              break;
          }
          case CHAT_GROUP:
          {
              SendGroup(client, msg);
              break;
           }
          case CHAT_AUCTION:
          case CHAT_SHOUT:
          {
              SendShout(client, msg);
              break;
          }
          case CHAT_MY:
          case CHAT_ME:
          case CHAT_SAY:
          {
              // Send to all if there's no NPC response or the response is public
              SendSay(client, msg, client->GetName());
              break;
          }
          case CHAT_NPC:
          {
              // Only the speaker sees his successful chatting with an npc.
              // This helps quests stay secret.
              psChatMessage newMsg(client->GetClientNum(), client->GetName(),
                  msg.sText, msg.iChatType, msg.translate);
              newMsg.SendMessage();
              saveFlood = false;

              gemObject *target = client->GetTargetObject();
              gemNPC *targetnpc = dynamic_cast<gemNPC*>(target);
              NpcResponse *resp = CheckNPCResponse(msg,client,targetnpc);
              if (resp)
                  resp->ExecuteScript(client, targetnpc);

              break;
          }
          case CHAT_TELL:
          {
              Client *target=NULL;
              if ( (target = FindPlayerClient(msg.sPerson, client->GetClientNum())))
              {
                  SendTell(msg, client->GetName(), client, target);

                  // Save to chat history
                  client->GetActor()->LogMessage(client->GetActor()->GetName(), msg);
                  target->GetActor()->LogMessage(client->GetActor()->GetName(), msg);
              }

              break;
          }
          case CHAT_REPORT:
          {
              // First thing to extract the name of the player to log
              csString targetName;
              int index = (int)msg.sText.FindFirst(' ', 0);
              if ( index == -1 )
                 targetName = msg.sText;
              else
                 targetName = msg.sText.Slice(0, index);
              targetName = NormalizeCharacterName(targetName);

              if ( msg.sText.Length() == 0 )
              {
                  psserver->SendSystemError(client->GetClientNum(), "You must specify name of player.");
                  break;
              }

              Client * target = psserver->GetConnections()->Find(targetName);
              if ( !target )
              {
                  psserver->SendSystemError(client->GetClientNum(), "%s is not online.", targetName.GetData());
                  break;
              }

              if (!client->GetActor()->IsLoggingChat())
              {
                  psserver->SendSystemError(client->GetClientNum(), "%s will be logged for five minutes now.", targetName.GetData());
                  psserver->SendSystemError(target->GetClientNum(), "Your last 5 minutes of chat has been reported to the GMs, logging will now continue.");
              }
              else
              {
                  if (target->GetClientNum() != client->GetActor()->GetReportTargetId())
                  {
                      psserver->SendSystemError(client->GetClientNum(), "Previous logging is still active.");
                      break;
                  }
                  psserver->SendSystemError(client->GetClientNum(), "Logging for another five minutes.");
              }
              client->GetActor()->AddChatReport(target->GetActor());
              psserver->GetEventManager()->Push(new psEndChatLoggingEvent(client->GetClientNum(), 300000));
              break;
         }
         case CHAT_ADVISOR:
         case CHAT_ADVICE:
          {
             break;
         }

         default:
         {
              Error2("Unknown Chat Type: %d\n",msg.iChatType);
              break;
         }
       }
    }
    else
    {
        //User is muted but tries to chat anyway. Remind the user that he/she/it is muted
        psserver->SendSystemInfo(client->GetClientNum(),"You can't send messages because you are muted.");
    }

    if (saveFlood)
        FloodControl(msg.sText,client);
}

/// TODO: This function is guaranteed not to work atm.-Keith
void ChatManager::SendNotice(psChatMessage& msg)
{
    SendSay(NULL, msg, "Server");
}


void ChatManager::SendShout(Client *c, psChatMessage& msg)
{
    psChatMessage newMsg(c->GetClientNum(), c->GetName(), msg.sText, msg.iChatType, msg.translate);

    if (c->GetActor()->GetCharacterData()->GetTotalOnlineTime() > 3600)
    {
        csArray<PublishDestination>& clients = c->GetActor()->GetMulticastClients();
        newMsg.Multicast(clients, 0, PROX_LIST_ANY_RANGE );

        // The message is saved to the chat history of all the clients around
        for (size_t i = 0; i < clients.Length(); i++)
        {
            Client *target = psserver->GetConnections()->Find(clients[i].client);
            if (target && target->IsReady())
                target->GetActor()->LogMessage(c->GetActor()->GetName(), newMsg);
        }
    }
    else
    {
        psserver->SendSystemError(c->GetClientNum(), "You are not allowed to shout or auction until you have been in-game for at least 1 hour.");
        psserver->SendSystemInfo(c->GetClientNum(), "Please use the Help tab or /advisor if you need help.");
    }
}

void ChatManager::SendSay(Client *p, psChatMessage& msg,const char* who)
{
    if (p)
    {
        psChatMessage newMsg(p->GetClientNum(), who, msg.sText, msg.iChatType, msg.translate);
        csArray<PublishDestination>& clients = p->GetActor()->GetMulticastClients();
        newMsg.Multicast(clients, 0, CHAT_SAY_RANGE );

        // The message is saved to the chat history of all the clients around
        for (size_t i = 0; i < clients.Length(); i++)
        {
            Client *target = psserver->GetConnections()->Find(clients[i].client);
            if (target && target->IsReady() && clients[i].dist < CHAT_SAY_RANGE)
                target->GetActor()->LogMessage(p->GetActor()->GetName(), newMsg);
        }
    }
}

void ChatManager::SendGuild(Client *client, psChatMessage& msg)
{
    psGuildInfo * guild;
    psGuildLevel * level;

    guild = client->GetCharacterData()->GetGuild();
    if (guild == NULL)
    {
        psserver->SendSystemInfo(client->GetClientNum(), "You are not in a guild.");
        return;
    }

    level = client->GetCharacterData()->GetGuildLevel();
    if (level!=NULL  &&  level->HasRights(RIGHTS_CHAT)==false)
    {
        psserver->SendSystemInfo(client->GetClientNum(), "You are not allowed to use chat channel of your guild.");
        return;
    }

    SendGuild(client->GetName(), guild, msg);
}

void ChatManager::SendGuild(const csString & sender, psGuildInfo * guild, psChatMessage& msg)
{
    ClientIterator iter(*psserver->GetConnections() );
    Client * client;
    psGuildLevel * level;

    for (client = iter.First(); client != NULL; client = iter.Next())
    {
        if (client->GetGuildID() == guild->id)
        {
            level = client->GetCharacterData()->GetGuildLevel();
            if (level!=NULL  &&  level->HasRights(RIGHTS_VIEW_CHAT))
            {
                psChatMessage newMsg(client->GetClientNum(), sender, msg.sText, msg.iChatType, msg.translate);
                newMsg.SendMessage();
            }
        }
    }
}

void ChatManager::SendGroup(Client * client, psChatMessage& msg)
{
    csRef<PlayerGroup> group = client->GetActor()->GetGroup();
    if (group)
    {
        psChatMessage newMsg(0, client->GetName(), msg.sText, msg.iChatType, msg.translate);
        group->Broadcast(newMsg.msg);
    }
    else
    {
        psserver->SendSystemInfo(client->GetClientNum(), "You are not part of any group.");
    }
}


void ChatManager::SendTell(psChatMessage& msg, const char* who,Client *client,Client *p)
{
    Debug1(LOG_CHAT,client->GetClientNum(),"SendTell!\n");

    // Sanity check that we are sending to correct clientnum!
    csString targetName = msg.sPerson;
    NormalizeCharacterName(targetName);
    CS_ASSERT(strcasecmp(p->GetName(), targetName) == 0);

      // Create a new message and send it to that person if found
    psChatMessage cmsg(p->GetClientNum(), who, msg.sText, msg.iChatType, msg.translate);
    cmsg.SendMessage();

    // Echo the message back to the speaker also
    psChatMessage cmsg2(client->GetClientNum(), msg.sPerson, msg.sText, CHAT_TELLSELF, msg.translate);
    cmsg2.SendMessage();
}

#define MAX_NPC_DIALOG_DIST 5

NpcResponse *ChatManager::CheckNPCEvent(Client *client,csString& triggerText,gemNPC * &target)
{
    gemNPC *npc = target;

    if (npc && npc->IsAlive())
    {
        csString trigger(triggerText);
        trigger.Downcase();

        psNPCDialog *npcdlg = npc->GetNPCDialogPtr();

        if (npcdlg)  // if NULL, then NPC never speaks
        {
            float dist = npc->RangeTo( client->GetActor() );

            if (dist > MAX_NPC_DIALOG_DIST)
                return NULL;

            Notify3(LOG_NPC, "%s checking trigger %s.\n",target->GetName(),trigger.GetData() );
            return npcdlg->Respond(trigger,client);
        }
        else
            Notify2(LOG_NPC,"NPC %s cannot speak.\n",npc->GetName() );  // can comment this out later
    }
    return NULL;
}

NpcResponse *ChatManager::CheckNPCResponse(psChatMessage& msg,Client *client,gemNPC * &target)
{
    return CheckNPCEvent(client,msg.sText,target);  // <L MONEY="0,0,0,3"></L>
}



psEndChatLoggingEvent::psEndChatLoggingEvent(uint32_t _clientnum, const int delayticks=5000)
  : psGameEvent(0,delayticks,"psEndChatLoggingEvent")
{
#ifdef _psEndChatLoggingEvent_DEBUG_
CPrintf(CON_DEBUG, "EndOfChatLoggingEvent created for clientnum %i!", _clientnum);
#endif

    clientnum = _clientnum;
}

void psEndChatLoggingEvent::Trigger()
{
#ifdef _psEndChatLoggingEvent_DEBUG_
CPrintf(CON_DEBUG, "EndOfChatLoggingEvent is about to happen on clientnum %i!", clientnum);
#endif

    Client *client = NULL;

    client = psserver->GetConnections()->Find(clientnum);
    if (!client)
    {
#ifdef _psEndChatLoggingEvent_DEBUG_
CPrintf(CON_DEBUG, "EndOfChatLoggingEvent on unknown client!");
#endif
        return;
    }
    client->GetActor()->RemoveChatReport();
}

