/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Big particle-system.
 *//*
 * LEGAL:   COPYRIGHT (C) 2008 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define FX_PARTSYS_BIG_CC 1
#include "base/module.hh"
#include "base/stream.hh"
using namespace base;
#include "object/module.hh"
using namespace object;
#include "graph/module.hh"
#include "graph/scene_graph.hh"
using namespace graph;
#include "fx/module.hh"
#include "fx/defs.hh"
#include "fx/partsys_big.hh"

namespace fx {

#if DEBUG
INTERN int sBigParticleCount = 0;  // in all particle-systems
#endif

// Detach particle while iterating thru particle container.
// STL idiom for associative (not sequential) containers.
#define DETACH_BIG_PARTICLE_INC_ITER(ITER)                  \
{{                                                          \
    BigParticles::iterator iter2 = ITER;                    \
    ++iter2;                                                \
    DetachParticle( ITER );                                 \
    ITER = iter2;                                           \
}}

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////  BigParticle  //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 * BigParticle is based on Sprite.
 * Passing Sprite::Args struct is for speed/terseness.
 *****************************************************************************/
BigParticle::BigParticle( const Sprite::Args& spriteArgs,
                          const Milliseconds lifetime )
:   Sprite(spriteArgs),
    mTimeOfDeath(GET_TIMER().GetElapsedTime() + lifetime)
{
ASSERT( lifetime > Milliseconds(0) );
    SET_TYPESIG(this,TYPESIG_BIG_PARTICLE);

#if DEBUG
    // Limit amount of particles in all particle-systems.
    ++sBigParticleCount;
    ASSERT( uint(sBigParticleCount) < fx::defs::MAX_PARTICLES_ALL_SYSTEMS );
    ASSERT( sBigParticleCount > 0 );
#endif

#if DEBUG_PARTSYS
    CDEBUG << "new BigParticle lifetime=" << uint(lifetime) << " total=" << sBigParticleCount << std::endl;
#endif
}

BigParticle::~BigParticle()
{
#if DEBUG
    --sBigParticleCount;
    ASSERT( sBigParticleCount >= 0 );  // catch double-deletion
#endif

#if DEBUG_PARTSYS
    CDEBUG << "delete BigParticle total=" << sBigParticleCount << std::endl;
#endif

    INVALIDATE_TYPESIG(this,TYPESIG_BIG_PARTICLE);
}

/*****************************************************************************
 * Schedule destruction of particle.
 *****************************************************************************/
void
BigParticle::SelfDestruct( void )
{
CHECK_TYPESIG(this,TYPESIG_BIG_PARTICLE);

    // So it will be past time-of-death.
    mTimeOfDeath = Milliseconds(0);
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////  BigParticleSystem::Args  ///////////////////////////.
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor.
 * @param   lifetime
 *          Lifetime of entire particle-system
 * @param   timerEnabled
 *          Pass false for particles that don't need to be updated (clouds).
 * @param   nodeSort
 *          Graphical state.
 *****************************************************************************/
BigParticleSystem::Args::Args( const Milliseconds lifetime,
                               const bool timerEnabled,
                               const NodeSort& nodeSort )
:   ParticleSystem::Args(lifetime,timerEnabled),
    mNodeSort(nodeSort)
{
CHECK_TYPESIG(&nodeSort,TYPESIG_NODE_SORT);
    // NOP
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////  BigParticleSystem  //////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
BigParticleSystem::BigParticleSystem( const Args& args )
:   ParticleSystem(args),
    mBigParticles(),
    mNodeSort(args.mNodeSort),
    mGroupNode(args.mNodeSort.CreateGroupNode())
{
    SET_TYPESIG(this,TYPESIG_BIG_PARTICLE_SYSTEM);

    // Attach to scene-graph as separate branch.
    GET_SCENE_GRAPH().AttachBranchNode( mGroupNode );
}

BigParticleSystem::~BigParticleSystem()
{
#if DEBUG_PARTSYS
    CDEBUG << "~BigParticleSystem()" << std::endl;
#endif

    // Detach all remaining BigParticles.
    GET_SCENE_GRAPH().DetachBranchNode( mGroupNode );

    INVALIDATE_TYPESIG(this,TYPESIG_BIG_PARTICLE_SYSTEM);
}

/*****************************************************************************
 * Attach particle.
 *****************************************************************************/
void
BigParticleSystem::AttachParticle( shptr<BigParticle> bigParticle )
{
CHECK_TYPESIG(this,TYPESIG_BIG_PARTICLE_SYSTEM);
CHECK_TYPESIG(bigParticle,TYPESIG_BIG_PARTICLE);
#if DEBUG_PARTSYS
    CDEBUG << "BigParticleSystem::AttachParticle()" << std::endl;
#endif

    // Add BigParticle to Object container.
    mBigParticles.push_back( bigParticle );  // so that the oldest are first (queue)

    // Attach BigParticle to group node (which was attached to scene-graph).
    mGroupNode->addChild( bigParticle->GetRootNode().get() );

ASSERT( mBigParticles.size() <= fx::defs::MAX_PARTICLES_ONE_SYSTEM );
}

/*****************************************************************************
 * Remove oldest particles.
 *****************************************************************************/
void
BigParticleSystem::DetachOldestParticles( const uint count )
{
CHECK_TYPESIG(this,TYPESIG_BIG_PARTICLE_SYSTEM);
ASSERT( count < fx::defs::MAX_PARTICLES_ONE_SYSTEM );

    // The oldest particle is the head of the STL list.
    // Quickest way is to use one loop.
    // list::erase(first,last) and distance() might cause two loops.
    BigParticles::iterator iter = mBigParticles.begin();
    for ( uint i = 0; i < count; ++i )
    {
        if ( EX( iter != mBigParticles.end() ) )
        {
            DETACH_BIG_PARTICLE_INC_ITER( iter );
        #if DEBUG_PARTSYS
            CDEBUG << "detaching oldest particle" << std::endl;
        #endif
        }
        else
        {
            // Oops, amount to detach > size of container.
            ASSERT(false);
            break;
        }
    }
}

/*****************************************************************************
 * (private) Detach particle.
 * NOTE: Invalidates iter!
 *****************************************************************************/
void
BigParticleSystem::DetachParticle( BigParticles::iterator iter )
{
CHECK_TYPESIG(this,TYPESIG_BIG_PARTICLE_SYSTEM);

    shptr<BigParticle> bigParticle = *iter;         // keep holding a reference to it
    CHECK_TYPESIG(bigParticle,TYPESIG_BIG_PARTICLE);
    mGroupNode->removeChild( bigParticle->GetRootNode().get() );    // detach in terms of a node
    mBigParticles.erase( iter );                                    // detach in terms of an Object
}

/*****************************************************************************
 * Tick.
 *****************************************************************************/
void
BigParticleSystem::Tick( const Milliseconds millisecElapsed )
{
CHECK_TYPESIG(this,TYPESIG_BIG_PARTICLE_SYSTEM);
#if DEBUG_PARTSYS
    CDEBUG << "BigParticleSystem::Tick() mBigParticles.size() " << mBigParticles.size() << std::endl;
#endif

    // Chain back to base method.
    Parent::Tick( millisecElapsed );

    // Age and animate the particles.
    for ( BigParticles::iterator iter = mBigParticles.begin();
          iter != mBigParticles.end();
          /* NOP */ )
    {
        CHECK_TYPESIG(*iter,TYPESIG_BIG_PARTICLE);          // *iter = shptr<BigParticle>
        if ( millisecElapsed > (*iter)->mTimeOfDeath )      // past lifetime?
        {
            // Bury this particle.
            DETACH_BIG_PARTICLE_INC_ITER( iter );
        }
        else
        {
            // Animate this living particle.
            (*iter)->Tick( millisecElapsed );
            ++iter;
        }
    }
}

} // namespace fx
