/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

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, either version 3 of the License, or
(at your option) any later version.

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, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "plMsgForwarder.h"
#include "hsResMgr.h"
#include "hsTypes.h"
#include "pnMessage/plMessage.h"
#include "pnKeyedObject/plKey.h"
#include "pnNetCommon/plNetApp.h"
#include "pnNetCommon/plSynchedObject.h"

#include "pnMessage/plSelfDestructMsg.h"
#include "pnMessage/plMessageWithCallbacks.h"


class plForwardCallback
{
public:
    hsTArray<plKey> fOrigReceivers;
    int fNumCallbacks;
    hsBool fNetPropogate;
};

plMsgForwarder::plMsgForwarder()
{
}

plMsgForwarder::~plMsgForwarder()
{
    CallbackMap::iterator i = fCallbacks.begin();

    for (; i != fCallbacks.end(); i++)
        delete (*i).second;
}

void plMsgForwarder::Read(hsStream* s, hsResMgr* mgr)
{
    hsKeyedObject::Read(s, mgr);

    int numKeys = s->ReadSwap32();
    fForwardKeys.Reset();
    fForwardKeys.Expand(numKeys);
    fForwardKeys.SetCount(numKeys);
    for (int i = 0; i < numKeys; i++)
    {
        plKey key = mgr->ReadKey(s);
        fForwardKeys[i] = key;
    }
}

void plMsgForwarder::Write(hsStream* s, hsResMgr* mgr)
{
    hsKeyedObject::Write(s, mgr);

    int numKeys = fForwardKeys.Count();
    s->WriteSwap32(numKeys);
    for (int i = 0; i < numKeys; i++)
        mgr->WriteKey(s, fForwardKeys[i]);
}

hsBool plMsgForwarder::MsgReceive(plMessage* msg)
{
    // Self destruct messages are for us only
    plSelfDestructMsg *selfMsg = plSelfDestructMsg::ConvertNoRef(msg);
    if (selfMsg)
        return hsKeyedObject::MsgReceive(msg);

    // If this is a callback message, it needs to be handled differently
    if (IForwardCallbackMsg(msg))
        return true;

    // All other messages are forwarded
    IForwardMsg(msg);
    return true;
}

hsBool plMsgForwarder::IForwardCallbackMsg(plMessage *msg)
{
    // Only process as a callback message if it is one, AND it has callbacks
    plMessageWithCallbacks *callbackMsg = plMessageWithCallbacks::ConvertNoRef(msg);
    if (callbackMsg && callbackMsg->GetNumCallbacks() > 0)
    {
        for (int i = 0; i < callbackMsg->GetNumCallbacks(); i++)
        {
            plEventCallbackMsg *event = callbackMsg->GetEventCallback(i);
            hsAssert(event, "Message forwarder only supports event callback messages");
            if (event)
            {
                plForwardCallback *fc = TRACKED_NEW plForwardCallback;
                fc->fNumCallbacks = fForwardKeys.Count();

                // Turn off net propagate the callbacks to us will all be local.  Only the
                // callback we send will go over the net
                fc->fNetPropogate = (event->HasBCastFlag(plMessage::kNetPropagate) != 0);
                event->SetBCastFlag(plMessage::kNetPropagate, false);

                for (int j = 0; j < event->GetNumReceivers(); j++)
                    fc->fOrigReceivers.Append((plKey)event->GetReceiver(j));

                event->ClearReceivers();
                event->AddReceiver(GetKey());

                fCallbacks[event] = fc;

#if 0
                hsStatusMessageF("Adding CBMsg, eventSender=%s, eventRemoteMsg=%d\n",                   
                    event->GetSender() ? event->GetSender()->GetName() : "nil", fc->fNetPropogate);
#endif
            }
        }
#if 0
        hsStatusMessageF("Fwding CBMsg, sender=%s, remoteMsg=%d",
            msg->GetSender() ? msg->GetSender()->GetName() : "nil", msg->HasBCastFlag(plMessage::kNetNonLocal));
#endif
        IForwardMsg(callbackMsg);
        
        return true;
    }

    // Callback from one of our forward keys.  Don't send the final callback to the original
    // requester until all forward keys have reported in.
    plEventCallbackMsg *eventMsg = plEventCallbackMsg::ConvertNoRef(msg);
    if (eventMsg)
    {
        CallbackMap::const_iterator it = fCallbacks.find(eventMsg);
        if (it != fCallbacks.end())
        {
            plForwardCallback *fc = it->second;
            if (--fc->fNumCallbacks == 0)
            {
                hsStatusMessageF("plEventCallbackMsg received, erasing, sender=%s, remoteMsg=%d\n",
                    msg->GetSender() ? msg->GetSender()->GetName() : "nil", msg->HasBCastFlag(plMessage::kNetNonLocal));

                fCallbacks.erase(eventMsg);

                plUoid uoid = GetKey()->GetUoid();
                hsBool locallyOwned = (plNetClientApp::GetInstance()->IsLocallyOwned(uoid) != plSynchedObject::kNo);

                // If the callback was originally net propagated, and we own this forwarder, net propagate the callback
                if (fc->fNetPropogate && locallyOwned)
                    eventMsg->SetBCastFlag(plMessage::kNetPropagate);

                eventMsg->ClearReceivers();
                eventMsg->AddReceivers(fc->fOrigReceivers);
                eventMsg->SetSender(GetKey());
                hsRefCnt_SafeRef(eventMsg);
                eventMsg->Send();

                delete fc;
            }
        }
        else
        {
            hsStatusMessageF("! Unknown plEventCallbackMsg received, sender=%s, remoteMsg=%d\n",
                msg->GetSender() ? msg->GetSender()->GetName() : "nil", msg->HasBCastFlag(plMessage::kNetNonLocal));
            hsAssert(0, "Unknown plEventCallbackMsg received");
        }
        return true;
    }

    return false;
}

void plMsgForwarder::IForwardMsg(plMessage *msg)
{
    // Back up the message's original receivers
    hsTArray<plKey> oldKeys;
    for (int i = 0; i < msg->GetNumReceivers(); i++)
        oldKeys.Append((plKey)msg->GetReceiver(i));

    // Set to our receivers and send
    hsRefCnt_SafeRef(msg);
    msg->ClearReceivers();
    msg->AddReceivers(fForwardKeys);
    msg->Send();

    // Reset back to the original receivers.  This is so we don't screw up objects
    // who reuse their messages
    msg->ClearReceivers();
    msg->AddReceivers(oldKeys);
}

void plMsgForwarder::AddForwardKey(plKey key)
{
    if (fForwardKeys.Find(key) == fForwardKeys.kMissingIndex)
        fForwardKeys.Append(key);
}