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

#include "SceneWatcher.h"

#include "MaxMain/plMaxNode.h"
#include "MaxComponent/plComponent.h"
#include "MaxMain/plPlasmaRefMsgs.h"

#include "pnKeyedObject/plKey.h"

SceneWatcher::SceneWatcher() : fDirty(false)
{
    RegisterNotification(INotify, this, NOTIFY_NODE_CREATED);
    IAddNodeRecur((plMaxNode*)GetCOREInterface()->GetRootNode());
}

SceneWatcher::~SceneWatcher()
{
    DeleteAllRefsFromMe();
    UnRegisterNotification(INotify, this, NOTIFY_NODE_CREATED);
}

int SceneWatcher::NumRefs()
{
    return fNodes.size();
}

RefTargetHandle SceneWatcher::GetReference(int i)
{
    if (i < fNodes.size())
        return fNodes[i];

    hsAssert(0, "Index out of range");
    return nil;
}

void SceneWatcher::SetReference(int i, RefTargetHandle rtarg)
{
    if (i < fNodes.size())
        fNodes[i] = (plMaxNode*)rtarg;
    else
        hsAssert(0, "Index out of range");
}

//
// Something in the scene has changed.
//
RefResult SceneWatcher::NotifyRefChanged(Interval changeInt, RefTargetHandle hTarget, 
                           PartID& partID, RefMessage message)
{
    plMaxNode *node = (plMaxNode *)hTarget;

#ifdef HS_DEBUGGING
    char *tmp = node->GetName();
#endif

    if (message == REFMSG_CHANGE)
    {
        // If the message is from a component, and was not generated locally (ie.
        // it came from a ref parameter), ignore it.  There is no way to tell if
        // one of these messages is an actual change to the component or just a
        // change to the referenced object.  We'll catch the real changes with
        // REFMSG_USER_COMP_REF_CHANGED below.
        if (node->IsComponent())
        {
            plComponentBase *comp = node->ConvertToComponent();
            if (!comp->IsCurMsgLocal())
                return REF_SUCCEED;
        }

        // If this is a static light, ignore it
        Object *obj = node->GetObjectRef();
        if (obj && obj->SuperClassID() == LIGHT_CLASS_ID && !node->GetRunTimeLight())
            return REF_SUCCEED;

        node->SetDirty(plMaxNode::kGeomDirty, true);
        ISetDirty();
    }
    else if (message == REFMSG_TARGET_DELETED)
    {
        // If the deleted node was a component, dirty everyone it was attached to
        if (node->IsComponent())
        {
            plComponentBase *comp = node->ConvertToComponent();
            for (UInt32 i = 0; i < comp->NumTargets(); i++)
            {
                if (comp->GetTarget(i))
                    comp->GetTarget(i)->SetDirty(plMaxNode::kGeomDirty, true);
            }
        }

        fDeleted.push_back(node->GetKey());

        IRemoveRef(node);
        ISetDirty();
    }
    else if (message == REFMSG_NODE_MATERIAL_CHANGED ||
            message == REFMSG_USER_MAT)
    {
        if (!node->IsComponent())
        {
            node->SetDirty(plMaxNode::kMatDirty, true);
            ISetDirty();
        }
    }
    // A node was added to the components target list
    else if (message == REFMSG_USER_TARGET_ADD)
    {
        plMaxNode *target = (plMaxNode*)partID;
        target->SetDirty(plMaxNode::kGeomDirty, true);
        ISetDirty();
    }
    // A node was deleted from the components target list
    else if (message == REFMSG_USER_TARGET_DELETE)
    {
        plMaxNode *target = (plMaxNode*)partID;
        target->SetDirty(plMaxNode::kGeomDirty, true);
        ISetDirty();
    }
    // A ref maintained by a component PB changed (not just a propagated message
    // from the ref)
    else if (message == REFMSG_USER_COMP_REF_CHANGED)
    {
        node->SetDirty(plMaxNode::kGeomDirty, true);
        ISetDirty();
    }

    return REF_SUCCEED;
}

bool SceneWatcher::AnyDeleted()
{
    return !fDeleted.empty();
}

SceneWatcher::KeyList& SceneWatcher::GetDeleted()
{
    return fDeleted;
}

const SceneWatcher::NodeList& SceneWatcher::GetWatchNodes()
{
    return fNodes;
}

bool SceneWatcher::AnyDirty()
{
    return fDirty;
}

void SceneWatcher::GetDirty(NodeSet& dirtyNodes)
{
    int size = fNodes.size();
    for (int i = 0; i < size; i++)
    {
        plMaxNode *node = fNodes[i];

#ifdef HS_DEBUGGING
        const char *tmp = node ? node->GetName() : nil;
#endif
        // If any dirty flags are set, add to dirty list
        if (node && node->GetDirty(plMaxNode::kAllDirty))
            IGetDependents(node, dirtyNodes);
    }

    fDirty = false;
}

void SceneWatcher::IAddRef(plMaxNode *node)
{
    // Ensure that we don't already ref this node
    if (FindRef(node) != -1)
        return;

    // Make a ref
    int size = fNodes.size();
    fNodes.resize(size+1);
    MakeRefByID(FOREVER, size, node);
}

void SceneWatcher::IRemoveRef(plMaxNode *node)
{
    // Delete the reference if it's in our list
    int i = FindRef(node);

    if (i != -1)
    {
        // Clear the node data, in case it is undeleted later
        // (when it will be invalid)
        node->ClearData(nil, nil);
        node->SetDirty(plMaxNode::kAllDirty, true);

        // We can never really delete this reference because the user may "undo"
        // and cause it to come back, which just sets the reference again.
        fNodes[i] = nil;
    }
}

void SceneWatcher::IAddNodeRecur(plMaxNode *node)
{
    IAddRef(node);

    // If a node is dirty, make sure to set the dirty flag (since nodes may have
    // been dirtied in a previous run but not reconverted yet).
    if (node->GetDirty(plMaxNode::kAllDirty))
        fDirty = true;

    for (int i = 0; i < node->NumberOfChildren(); ++i)
    {
        plMaxNode *childNode = (plMaxNode*)node->GetChildNode(i);
        IAddNodeRecur(childNode);
    }
}

void SceneWatcher::INotify(void *param, NotifyInfo *info)
{
    SceneWatcher *inst = (SceneWatcher*)param;

    int code = info->intcode;

    // New node was added to the scene, add it to our refs
    if (code == NOTIFY_NODE_CREATED)
    {
        plMaxNode *node = (plMaxNode*)info->callParam;

        // Add a ref to the node and set it to dirty
        inst->IAddRef(node);
        node->SetDirty(plMaxNodeBase::kAllDirty, true);
        inst->ISetDirty();
    }
}

void SceneWatcher::ISetDirty()
{
    fDirty = true;
}

#include "../MaxComponent/plMiscComponents.h"

#include "../MaxExport/plExportErrorMsg.h"
#include "../MaxExport/plExportProgressBar.h"
#include "../MaxComponent/plResponderComponent.h"   // Just need the CID

void SceneWatcher::IGetLogicDependents(plMaxNode *node, NodeSet& nodes)
{
    int attached = node->NumAttachedComponents();
    for (int i = 0; i < attached; i++)
    {
        plComponentBase *comp = node->GetAttachedComponent(i);
/*
        if (comp->ClassID() == ACTIVATOR_CID)
        {
            plMaxNodeBase *activatorNode = comp->GetINode();
            int numResponders = activatorNode->NumAttachedComponents(true);
            for (int i = 0; i < numResponders; i++)
            {
                plComponentBase *responderComp = activatorNode->GetAttachedComponent(i, true);
                if (responderComp->ClassID() == RESPONDER_CID)
                {
                    for (int j = 0; j < responderComp->NumTargets(); j++)
                    {
                        plMaxNode *targ = (plMaxNode*)responderComp->GetTarget(j);
                        if (targ && nodes.find(targ) == nodes.end())
                        {
                            nodes.insert(targ);
                            IGetLogicDependents(targ, nodes);
                        }
                    }
                }
            }
        }
        else
*/
        if (comp->ClassID() == RESPONDER_CID)
        {
            int activatorCnt = ResponderGetActivatorCount(comp);
            for (int i = 0; i < activatorCnt; i++)
            {
                plComponentBase *activator = ResponderGetActivator(comp, i);

                for (int j = 0; j < activator->NumTargets(); j++)
                {
                    plMaxNode *targ = (plMaxNode*)activator->GetTarget(j);
                    if (targ && nodes.find(targ) == nodes.end())
                    {
                        nodes.insert(targ);
                        IGetLogicDependents(targ, nodes);
                    }
                }
            }
        }
    }
}

void SceneWatcher::IGetDependents(plMaxNode *node, NodeSet& nodes)
{
    NodeSet dependents;

    if (node->IsComponent())
    {
        plComponentBase *comp = node->ConvertToComponent();

        if (comp->ClassID() == ROOM_CID || comp->ClassID() == PAGEINFO_CID)
            return;

        for (int i = 0; i < comp->NumTargets(); i++)
        {
            plMaxNode *targ = (plMaxNode*)comp->GetTarget(i);
            if (targ)
            {
                dependents.insert(targ);
                IGetLogicDependents(targ, dependents);
            }
        }
    }
    else
    {
        dependents.insert(node);

        IGetLogicDependents(node, dependents);
    }

//  Bug in VC++?
//  nodes.insert(dependents.begin(), dependents.end());
    for (NodeSet::iterator i = dependents.begin(); i != dependents.end(); i++)
        nodes.insert(*i);
}