/*==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 "plSDLModifier.h"

#include "pnNetCommon/plSynchedObject.h"
#include "pnDispatch/plDispatch.h"
#include "pnSceneObject/plSceneObject.h"
#include "pnMessage/plSDLModifierMsg.h"

#include "plNetMessage/plNetMessage.h"
#include "plSDL/plSDL.h"
#include "plNetClient/plNetClientMgr.h"
#include "plNetClient/plNetObjectDebugger.h"

plSDLModifier::plSDLModifier() : fStateCache(nil), fSentOrRecvdState(false)
{
}

plSDLModifier::~plSDLModifier()
{
    delete fStateCache;
}

plKey plSDLModifier::GetStateOwnerKey() const
{ 
    return GetTarget() ? GetTarget()->GetKey() : nil; 
}

void plSDLModifier::AddTarget(plSceneObject* so) 
{
    if (so)
        plSingleModifier::AddTarget(so);
    if (!fStateCache)
        fStateCache = TRACKED_NEW plStateDataRecord(GetSDLName());
}

UInt32 plSDLModifier::IApplyModFlags(UInt32 sendFlags)
{
    return sendFlags;
}

//
// write to net msg and send to server
//
void plSDLModifier::ISendNetMsg(plStateDataRecord*& state, plKey senderKey, UInt32 sendFlags)
{
    hsAssert(senderKey, "nil senderKey?");

    plSynchedObject* sobj = plSynchedObject::ConvertNoRef(senderKey->ObjectIsLoaded());
    if (sobj && (sobj->IsInSDLVolatileList(GetSDLName())))
        state->SetFlags(state->GetFlags() | plStateDataRecord::kVolatile);

    bool dirtyOnly = (sendFlags & plSynchedObject::kForceFullSend) == 0;
    bool broadcast = (sendFlags & plSynchedObject::kBCastToClients) != 0;
    int writeOptions=0;
//  if (dirtyOnly)
        writeOptions |= plSDL::kDirtyOnly;
    if (broadcast)
        writeOptions |= plSDL::kBroadcast;
        
    writeOptions |= plSDL::kTimeStampOnRead;
    
    plNetClientMgr::GetInstance()->StoreSDLState(state, senderKey->GetUoid(), sendFlags, writeOptions);
        
    fSentOrRecvdState = true;
}

//
// Process SDL msgs to send and recv state
//
hsBool plSDLModifier::MsgReceive(plMessage* msg)
{
    plSDLModifierMsg* sdlMsg = plSDLModifierMsg::ConvertNoRef(msg);
    if (sdlMsg && !stricmp(sdlMsg->GetSDLName(),GetSDLName()))
    {       
        UInt32 sendFlags = IApplyModFlags(sdlMsg->GetFlags());

        if (!fSentOrRecvdState)
            sendFlags |= plSynchedObject::kNewState;
        
        if (sdlMsg->GetAction()==plSDLModifierMsg::kSendToServer)
        {
            // local player is changing the state and sending it out
            plStateChangeNotifier::SetCurrentPlayerID(plNetClientApp::GetInstance()->GetPlayerID());    

            SendState(sendFlags);
        }
        else
        if (sdlMsg->GetAction()==plSDLModifierMsg::kSendToServerAndClients)
        {
            // local player is changing the state and sending it out
            plStateChangeNotifier::SetCurrentPlayerID(plNetClientApp::GetInstance()->GetPlayerID());

            SendState(sendFlags | plSynchedObject::kBCastToClients);
        }
        else
        if (sdlMsg->GetAction()==plSDLModifierMsg::kRecv)
        {
            plStateDataRecord* sdRec=sdlMsg->GetState();
            plStateChangeNotifier::SetCurrentPlayerID(sdlMsg->GetPlayerID());   // remote player changed the state
            ReceiveState(sdRec);
        }

        return true;    // consumed
    }

    return plSingleModifier::MsgReceive(msg);
}

//
// send a state update
//
bool gMooseDump=false;
void plSDLModifier::SendState(UInt32 sendFlags)
{
    hsAssert(fStateCache, "nil stateCache");

    bool debugObject = (plNetObjectDebugger::GetInstance() && 
            plNetObjectDebugger::GetInstance()->IsDebugObject(GetStateOwnerKey()->ObjectIsLoaded()));
    
    bool force = (sendFlags & plSynchedObject::kForceFullSend) != 0;
    bool broadcast = (sendFlags & plSynchedObject::kBCastToClients) != 0;

    // record current state
    plStateDataRecord* curState = TRACKED_NEW plStateDataRecord(GetSDLName());
    IPutCurrentStateIn(curState);   // return sdl record which reflects current state of sceneObj, dirties curState
    if (!force)
    {
        curState->FlagDifferentState(*fStateCache); // flag items which are different from localCopy as dirty
    }

    if (curState->IsDirty())
    {
        // send current state
        bool dirtyOnly = force ? false : true;
        ISendNetMsg(curState, GetStateOwnerKey(), sendFlags);           // send the state

        if (debugObject)
        {
            gMooseDump=true;
            plNetObjectDebugger::GetInstance()->SetDebugging(true);
            curState->DumpToObjectDebugger(xtl::format("Object %s SENDS SDL state", 
                GetStateOwnerKey()->GetName(), dirtyOnly).c_str());
            gMooseDump=false;
        }

        // cache current state, send notifications if necessary
        fStateCache->UpdateFrom(*curState, dirtyOnly);  // update local copy of state
        
        ISentState(curState);
    }
    delete curState;

    if (plNetObjectDebugger::GetInstance())
        plNetObjectDebugger::GetInstance()->SetDebugging(false);
}

void plSDLModifier::ReceiveState(const plStateDataRecord* srcState)
{
    hsAssert(fStateCache, "nil stateCache");
    
    if (plNetObjectDebugger::GetInstance() && 
        plNetObjectDebugger::GetInstance()->IsDebugObject(GetStateOwnerKey()->ObjectIsLoaded()))
    {
        gMooseDump=true;
        plNetObjectDebugger::GetInstance()->SetDebugging(true);
        srcState->DumpToObjectDebugger(xtl::format("Object %s RECVS SDL state", 
            GetStateOwnerKey()->GetName()).c_str());
        gMooseDump=false;
    }

    if (srcState->IsUsed())
    {
        plSynchEnabler ps(false);   // disable dirty tracking while we are receiving/applying state

        // apply incoming state
        ISetCurrentStateFrom(srcState);         // apply incoming state to sceneObj
        
        // cache state, send notifications if necessary 
        fStateCache->UpdateFrom(*srcState, false);  // update local copy of state
        fSentOrRecvdState = true;
    }
    else
    {
        plNetClientApp::GetInstance()->DebugMsg("\tReceiving and ignoring unused SDL state msg: type %s, object %s", 
            GetSDLName(), GetStateOwnerKey()->GetName());
    }
    
    if (plNetObjectDebugger::GetInstance())
        plNetObjectDebugger::GetInstance()->SetDebugging(false);
}

void plSDLModifier::AddNotifyForVar(plKey key, const char* varName, float tolerance) const
{
    // create a SDL notifier object
    plStateChangeNotifier notifier(tolerance, key);
    // set the notification
    plStateDataRecord* rec = GetStateCache();
    if (rec)
    {
        plSimpleStateVariable* var = rec->FindVar(varName);
        // was the variable found?
        if (var)
            var->AddStateChangeNotification(notifier);
    }
}