/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   SceneGraph class.
 * @remarks - Shadows are disabled per-node by SceneGraph::AttachNode(),
 *            or entirely disabled by if SceneGraph internally substitutes
 *            a BaseSceneGraph object for a ShadowSceneGraph object.
 *          - IMPORTANT: ShadowSceneGraph must not be attached at all
 *            if shadows are disabled (because of an obsolete OpenGL implementation),
 *            else OSG 2.9 will fail to render anything (white window).
 *//*
 * 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_SCENE_GRAPH_CC 1
#include "base/module.hh"
#include "base/conf.hh"
#include "base/stream.hh"
using namespace base;
#include "gfx/module.hh"
#include "gfx/rgba.hh"
#include "gfx/osg.hh"
#include "gfx/osg_funcs.hh"
using namespace gfx;
#include "object/module.hh"
using namespace object;
#include "shader/module.hh"
#include "shader/shader_factory.hh"  // Flush()
using namespace shader;
#include "graph/module.hh"
#include "graph/osg.hh"
#include "graph/conf.hh"
#include "graph/base_scene_graph.hh"
#include "graph/scene_graph.hh"
#include "graph/shadow_scene_graph.hh"
#include "graph/model_cache.hh"  // Flush()

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

namespace graph {

/// False if shadows are entirely disabled.
INLINE bool IF_SHADOWS_ENABLED( void )
{
    return GRAPH_CONF.mShadowMode != graph::defs::SHADOW_MODE_DISABLED;
}

/// True if node casts/receives shadows.
/// This effectively disables shadows by causing them to not be attached to the ShadowSceneGraph.
INLINE bool IF_SHADOW_NODE_SORT( const NodeSort& nodeSort )
{
    return IF_SHADOWS_ENABLED()
      and ((nodeSort.GetAttribs().mVal) & (NodeSort::Attribs::SHADOW_CASTER | NodeSort::Attribs::SHADOW_RECEIVER));
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////  SceneGraph  ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

SceneGraph* SceneGraph::msInstance;

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
SceneGraph::SceneGraph( void )
:   BaseSceneGraph(),
    mClearNode(new osg::ClearNode),  // root node
    //-------------------------------------------------------------------------------------
    // -- IMPORTANT --
    // If shadows are disabled then do not use ShadowSceneGraph at all.
    // OSG 2.9 will seriously malfunction if OpenGL implementation doesn't support shadows.
    //-------------------------------------------------------------------------------------
    mShadowSceneGraph(IF_SHADOWS_ENABLED() ? new ShadowSceneGraph : new BaseSceneGraph),
    mEnableWireframe(false),
    mPolygonMode(new osg::PolygonMode)
{
    // Arrange nodes.
    ArrangeNodes();

    // Set background color.
    SetBackgroundColor( GRAPH_CONF.mBackgroundColor );

    // Enable/disable wireframe mode per cmdline.
    EnableWireframeMode( GRAPH_CONF.mEnableWireframe );
}

// ctor subroutine.
void
SceneGraph::ArrangeNodes( void )
{
    // Set root node to an osg::ClearNode to change background color.
    BaseSceneGraph::SetRootNode( mClearNode.get() );

    // Attach the shadowed scene-graph to this scene-graph.
    // AttachNode() is a NOP if already attached.
    BaseSceneGraph::AttachNode( mShadowSceneGraph->GetRootNode().get(), NodeSort() );
}

SceneGraph::~SceneGraph()
{
    // NOP
}

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

    // Attach to either regular or shadowed scene-graph.
    if ( UX( IF_SHADOW_NODE_SORT(nodeSort) ) )
        mShadowSceneGraph->AttachNode( node, nodeSort );
    else
        BaseSceneGraph::AttachNode( node, nodeSort );

ASSERT( IfNodeAttached( *node ) );
ASSERT( node->getNumParents() == 1 );  // a node should be in one graph and have one parent after attaching
}

/*****************************************************************************
 * Attach node with default graphical state.
 *****************************************************************************/
void
SceneGraph::AttachNode( RefPtr<Node> node )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    AttachNode( node, NodeSort() );

ASSERT( IfNodeAttached( *node ) );
ASSERT( node->getNumParents() == 1 );  // a node should be in one graph and have one parent after attaching
}

/*****************************************************************************
 * Attach the node of an Object.
 * Implies that SceneGraph holds a reference to Object.
 * NodeSort is a hint for state-sorting.
 *****************************************************************************/
void
SceneGraph::AttachObject( shptr<Object> object, const NodeSort& nodeSort )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);
CHECK_TYPESIG(object,TYPESIG_OBJECT);
CHECK_TYPESIG(&nodeSort,TYPESIG_NODE_SORT);

    // Attach to either regular or shadowed scene-graph.
    if ( UX( IF_SHADOW_NODE_SORT(nodeSort) ) )
        mShadowSceneGraph->AttachObject( object, nodeSort );
    else
        BaseSceneGraph::AttachObject( object, nodeSort );

ASSERT( IfObjectAttached( object ) );
}

/*****************************************************************************
 * Attach object with default graphical state.
 *****************************************************************************/
void
SceneGraph::AttachObject( shptr<Object> object )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);
CHECK_TYPESIG(object,TYPESIG_OBJECT);

    AttachObject( object, object->GetNodeSort() );

ASSERT( IfObjectAttached( object ) );
}

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

    BaseSceneGraph::DetachNode( node );
    mShadowSceneGraph->DetachNode( node );

ASSERT( not IfNodeAttached( *node ) );  // shouldn't be attached
ASSERT( node->getNumParents() == 0 );  // should have zero parents after detaching
}

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

    BaseSceneGraph::DetachObject( object );
    mShadowSceneGraph->DetachObject( object );

#if DEBUG
    if ( UX( IfObjectAttached( object ) ) )
    {
        CERROR << "ERROR[SceneGraph::DetachObject]: " << object->GetName() << " is still attached." << std::endl;
        ASSERT( false );
    }
#endif
}

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

    return BaseSceneGraph::IfNodeAttached(node)
        or mShadowSceneGraph->IfNodeAttached(node);
}

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

    return BaseSceneGraph::IfObjectAttached(object)
        or mShadowSceneGraph->IfObjectAttached(object);
}

/*****************************************************************************
 * Top-level method to flush scene-graph and its auxillary objects.
 *****************************************************************************/
void
SceneGraph::Flush( void )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    // First flush shaders.
    GET_SHADER_FACTORY().Flush();

    // Flush this scene-graph (reloads default shaders).
    BaseSceneGraph::Flush();

    // Flush ShadowSceneGraph.
    mShadowSceneGraph->Flush();

    // Redo arrange nodes (flushing caused the removing of child nodes).
    ArrangeNodes();

    // Flush ModelCache.
    GET_MODEL_CACHE().Flush();
}

/*****************************************************************************
 * Optimize entire scene-graph.
 *****************************************************************************/
void
SceneGraph::Optimize( void )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

#if 0
    // Optimizing the whole scene-graph is wrong (and partly redundant).
    // Some 3D models won't render correctly if optimized.
    // Rather, optimizing must be done selectively on individual nodes.
    // Despite being a NOP, SceneGraph::Optimize() remains part of the interface.
    BaseSceneGraph::Optimize( GetRootNode(), NodeSort() );
#endif
}

/*****************************************************************************
 * Set the background color.
 *****************************************************************************/
void
SceneGraph::SetBackgroundColor( const RGBA color )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    mClearNode->setClearColor( RGBA2VEC4F(color) );
}

/*****************************************************************************
 * Enable/disable wireframe mode.
 *****************************************************************************/
void
SceneGraph::EnableWireframeMode( const bool enable )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    // Remember mode.
    mEnableWireframe = enable;

    // Enable wireframe mode, else enable default filled mode.
    mPolygonMode->setMode( osg::PolygonMode::FRONT_AND_BACK,
                           enable ? osg::PolygonMode::LINE     // wireframe mode
                                  : osg::PolygonMode::FILL );  // regular filled mode
    GetRootNode()->getOrCreateStateSet()->setAttributeAndModes( mPolygonMode.get(), osg::StateAttribute::ON );
}

/*****************************************************************************
 * Toggle wireframe mode.
 *****************************************************************************/
void
SceneGraph::ToggleWireframeMode( void )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    mEnableWireframe = not mEnableWireframe;

    EnableWireframeMode( mEnableWireframe );
}

/*****************************************************************************
 * Dump scene graph to file.
 * @param   filename_
 * @param   useCompression
 *          Use compression if supported.
 *          Will add a suffix such as .gz if not present in passed filename.
 *****************************************************************************/
void
SceneGraph::SaveToFile( const string& filename_, bool useCompression )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    COUT << "-------------------------------------------------------------" << std::endl
         << "When dumping scene-graph:"                                     << std::endl
         << "- Pass -dump-full to dump as much as possible."                << std::endl
         << "  This will enable dumping textures."                          << std::endl
         << "  Note that this will greatly increase memory usage!"          << std::endl
         << "- Shaders will be dumped too.  Some shaders might apply"       << std::endl
         << "  apply too much fog which can obscure textures."              << std::endl
         << "  Pass -no-shaders to avoid such problems."                    << std::endl
         << "- If procedural textures are in the scene, OSG will omit them" << std::endl
         << "  unless the app names and writes them to image files."        << std::endl
         << "-------------------------------------------------------------" << std::endl;

    string filename = filename_;
#if OPENSCENEGRAPH_MAJOR_VERSION * 100 + OPENSCENEGRAPH_MINOR_VERSION * 10 + OPENSCENEGRAPH_PATCH_VERSION >= 290
    // Append compression suffix (if not present).
    if ( useCompression and (filename.find(".gz") == string::npos) )
        filename += string(".gz");
#endif

    COUT << "Saving scene-graph to file " << filename << "...";
    if ( OsgNodeSaveToFile( *GetRootNode(), filename ) )
        COUT << "ok." << std::endl;
    else
        CERROR << "ERROR" << std::endl;
}

} // namespace graph
