/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Base scene-graph class.
 * @remarks NOTE: Many methods are virtual.
 *          For example, when the top-level SceneGraph::DetachNode() is called,
 *          BaseSceneGraph cannot use ASSERT( not IfNodeAttached() ) because, temporarily,
 *          the node could still be in ShadowedSceneGraph before SceneGraph::DetachNode() completes.
 *          Therefore, some final assertions/checks belong in the top-level SceneGraph.
 *//*
 * LEGAL:   COPYRIGHT (C) 2009 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define GRAPH_BASE_SCENE_GRAPH_CC 1
#include <utility>
#include "base/module.hh"
#include "base/conf.hh"
using namespace base;
#include "gfx/module.hh"
#include "gfx/osg.hh"
#include "gfx/osg_funcs.hh"
using namespace gfx;
#include "object/module.hh"
#include "object/events.hh"
using namespace object;
#include "view/module.hh"
#include "view/node_masks.hh"
using namespace view;
#include "shader/module.hh"
#include "shader/shader_factory.hh"
using namespace shader;
#include "graph/module.hh"
#include "graph/osg.hh"
#include "graph/conf.hh"
#include "graph/base_scene_graph.hh"

#include <osg/BlendFunc>
#include <osgUtil/Optimizer>

namespace graph {

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  BaseSceneGraph  //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

NodeSort BaseSceneGraph::msBranchNodeSort;  // default

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
BaseSceneGraph::BaseSceneGraph( RefPtr<GroupNode> rootNode )
:   mBaseRootNode(rootNode),
    mBranchNode(msBranchNodeSort.CreateGroupNode()),  // create group with default state
    mNodeSortMap(),
    mObjects()
{
    SET_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    // Initialize root node.
    SetRootNode( rootNode );

    // Unlisten() isn't necessary for singletons.
    EVENT_DESTROY_OBJECT.Listen( new DestroyObjectListener( *this ) );
}

BaseSceneGraph::~BaseSceneGraph()
{
    INVALIDATE_TYPESIG(this,TYPESIG_SCENE_GRAPH);
}

/*****************************************************************************
 * (private) Arrange nodes.  Also resets default state of root node.
 *****************************************************************************/
void
BaseSceneGraph::ArrangeNodes( void )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    // Attach branch node (unless attached).
    if ( not mBaseRootNode->containsNode( mBranchNode.get() ) )
        mBaseRootNode->addChild( mBranchNode.get() );

    // Set default state on root node.
    SetDefaultState( mBaseRootNode.get() );
}

/*****************************************************************************
 * (protected) This can be called on the same root node in order to reset its state.
 *****************************************************************************/
void
BaseSceneGraph::SetRootNode( RefPtr<GroupNode> rootNode )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    // Switch root node.
    RefPtr<GroupNode> prevRootNode = mBaseRootNode;
    mBaseRootNode = rootNode;

    // Arrange nodes.
    ArrangeNodes();

    // Detach children from outgoing root node (unless same root node).
    if ( (prevRootNode.get() != NULL)
     and (prevRootNode.get() != mBaseRootNode.get()) )
    {
        OsgNodeRemoveChildren( prevRootNode );
    }
}

/*****************************************************************************
 * (protected)
 * Apply default state to a node.
 *****************************************************************************/
void
BaseSceneGraph::SetDefaultState( RefPtr<Node> node )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    RefPtr<osg::StateSet> stateSet = node->getOrCreateStateSet();
    const osg::StateAttribute::OverrideValue override = osg::StateAttribute::OVERRIDE;

    // Disable hw lighting.
    stateSet->setMode( GL_LIGHTING, osg::StateAttribute::OFF | override );

    // Set default shader.
    GET_SHADER_FACTORY().AttachShader( ShaderName(), node.get() );

    // Set default blend function (don't enable blending).
    RefPtr<osg::BlendFunc> blendFunc = new osg::BlendFunc;
    blendFunc->setFunction( osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE_MINUS_SRC_ALPHA );
    stateSet->setAttributeAndModes( blendFunc.get(), osg::StateAttribute::ON );

    // Enable back-face culling.
    stateSet->setMode( GL_CULL_FACE, osg::StateAttribute::ON | override );
}

/*****************************************************************************
 * (private subroutine)
 * NOP if node is already attached.
 *****************************************************************************/
void
BaseSceneGraph::AttachNode( RefPtr<Node> node, RefPtr<GroupNode> parent )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    // NOP if node is already attached.
    if ( EX( node->getNumParents() == 0 ) )
    {
        // Attach node.
        parent->addChild( node.get() );
    }

ASSERT( node->getNumParents() == 1 );  // node should be in one graph only
ASSERT( IfNodeAttached( *node ) );  // virtual call (ok)
}

/*****************************************************************************
 * Attach node to scene-graph under branch node with special graphical state.
 * NOP if node is already attached.
 *****************************************************************************/
void
BaseSceneGraph::AttachNode( RefPtr<Node> node, const NodeSort& nodeSort )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);
CHECK_TYPESIG(&nodeSort,TYPESIG_NODE_SORT);

    // Optimize this node's subgraph.
    Optimize( node, nodeSort );

    // Try to map NodeSort to a cached group node.
    NodeSortMap::iterator iter = mNodeSortMap.find( nodeSort );
    if ( iter != mNodeSortMap.end() )
    {
        // Found cached group node.
        RefPtr<GroupNode> groupNode = iter->second;
        AttachNode( node, groupNode );
    }
    else
    {
        // Create group node.
        RefPtr<GroupNode> groupNode = nodeSort.CreateGroupNode();

        // Cache group node.
        mNodeSortMap.insert( std::make_pair<>( nodeSort, groupNode ) );

        // Attach node to branch.
        AttachNode( node, groupNode );

        // Attach branch to root.
        AttachNode( groupNode.get(), mBaseRootNode.get() );
    }

ASSERT( IfNodeAttached( *node ) );  // virtual call (ok)
}

/*****************************************************************************
 * Create a branch of the scene-graph at group node having special graphical-state.
 * The regular AttachNode() places nodes under a group with default graphical state.
 *****************************************************************************/
void
BaseSceneGraph::AttachBranchNode( RefPtr<GroupNode> groupNode )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    AttachNode( groupNode.get(), mBaseRootNode.get() );

ASSERT( IfNodeAttached( *groupNode ) );  // virtual call (ok)
}

/*****************************************************************************
 * Similar to AttachObject() but overrides Object::GetNodeSort().
 *****************************************************************************/
void
BaseSceneGraph::AttachObject( shptr<Object> object, const NodeSort& nodeSort )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);
CHECK_TYPESIG(&nodeSort,TYPESIG_NODE_SORT);

    // Attach Object's node.
    AttachNode( object->GetRootNode(), nodeSort );

    // AttachObject() implies that SceneGraph holds a reference to Object.
    // The C++ core needs a container of Objects (so this is it).
    mObjects.insert( object );

ASSERT( IfObjectAttached( object ) );  // virtual call (ok)
}

/*****************************************************************************
 * Detach node.
 * Ok to call if node is already detached.
 *****************************************************************************/
void
BaseSceneGraph::DetachNode( RefPtr<Node> node )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    // A node should be attached to 0 or 1 group nodes.
    const uint parentCount = node->getNumParents();
    if ( parentCount == 1 )
    {
        // Has one parent which should be an osg::Group.
        RefPtr<osg::Group> parent = node->getParent( 0 );  // idx=0
    #if DEBUG
        ASSERT( parent->asGroup() != NULL );            // should be a Group or derivative
        ASSERT( parent->removeChild( node.get() ) );    // remove() should return true
    #else
        parent->removeChild( node.get() );
    #endif
    }
    else // parentCount != 1
    {
        // Multiple parents shouldn't occur and isn't handled.
        // Objects may have shared nodes for 3D models but
        // those should be underneath unique group nodes (osg::MatrixTransform).
        ASSERT( parentCount == 0 );
    }

// WRONG: Node might still be attached to ShadowedSceneGraph.  A case where assertions belong in top-level SceneGraph.
//ASSERT( node->getNumParents() == 0 );  // should have zero parents
//ASSERT( not IfNodeAttached( *node ) );  // virtual call
}

/*****************************************************************************
 * Detach user-defined branch node.
 * Ok to call if node is already detached.
 *****************************************************************************/
void
BaseSceneGraph::DetachBranchNode( RefPtr<GroupNode> branchNode )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    DetachNode( branchNode.get() );
}

/*****************************************************************************
 * Detach Object.
 * Ok to call if Object is already detached.
 *****************************************************************************/
void
BaseSceneGraph::DetachObject( shptr<Object> object )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);
CHECK_TYPESIG(object,TYPESIG_OBJECT);

    // Even though erasing indirectly calls call ~Object() which calls DetachNode(),
    // call DetachNode() here in case something else is holding a reference to Object.
    DetachNode( object->GetRootNode() );
    mObjects.erase( object );

// WRONG: Node might still be attached to ShadowedSceneGraph.  A case where assertions belong in top-level SceneGraph.
//ASSERT( not IfObjectAttached( object ) );  // virtual call
}

/*****************************************************************************
 * @return True if Node is attached to SceneGraph.
 *****************************************************************************/
bool
BaseSceneGraph::IfNodeAttached( Node& node )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    // Checking if a node has a parent is adequate despite being inexact.
    // This really could be an interior node of a subgraph
    // but trying to attach it remains wrong anyway.
    return node.getNumParents() > 0;
}

/*****************************************************************************
 * @return True if Object is attached to SceneGraph.
 *****************************************************************************/
bool
BaseSceneGraph::IfObjectAttached( shptr<Object> object )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);
CHECK_TYPESIG(object,TYPESIG_OBJECT);

    // This use of find thru a container is why arg has to be a shptr instead of Object&.
    return mObjects.find(object) != mObjects.end();
}

/*****************************************************************************
 * Flush scene-graph (for reloading).
 *****************************************************************************/
void
BaseSceneGraph::Flush( void )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    // Flush data members.
    OsgNodeRemoveChildren( mBaseRootNode );
    OsgNodeRemoveChildren( mBranchNode );
    mNodeSortMap.clear();
    mObjects.clear();

    // Flushing caused removing of child nodes so recompose nodes.
    // This method also resets the default shader which might've changed.
    ArrangeNodes();
}

/*****************************************************************************
 * Run OSG optimizer on a node and its children.
 *****************************************************************************/
void
BaseSceneGraph::Optimize( RefPtr<Node> node, const NodeSort& nodeSort )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);
CHECK_TYPESIG(&nodeSort,TYPESIG_NODE_SORT);

    if ( EX( GRAPH_CONF.mEnableOsgOptimizer ) )
    {
        // Optimize node.
        osgUtil::Optimizer optimizer;
        optimizer.optimize( node.get(), nodeSort.GetOptimizations().mVal );
    }

    if ( UX( GRAPH_CONF.mEnableDumpFull ) )
    {
        // To dump the scene-graph with textures,
        // run Optimizer::TextureVisitor to disable UnrefImageAfterApply.
        // -- THIS CAN CONSUME LOTS OF CPU-SIDE MEMORY --
        osgUtil::Optimizer optimizer;  // above case with optimizer is conditional
        osgUtil::Optimizer::TextureVisitor textureVisitor( true, false,   // disable unref image 
                                                           false, false,  // client storage
                                                           false, 1.0f,   // anisotropic filtering
                                                           &optimizer );
        node->accept( textureVisitor );
    }
}

/*****************************************************************************
 * Listen for the DestroyObject event.
 *****************************************************************************/
void
BaseSceneGraph::DestroyObjectListener::operator()( shptr<Object> object )
{
CHECK_TYPESIG(&mBaseSceneGraph,TYPESIG_SCENE_GRAPH);
CHECK_TYPESIG(object,TYPESIG_OBJECT);

    mBaseSceneGraph.DetachObject( object );
}

} // namespace graph
