/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Base class for projectiles (missiles, bullets, etc).
 *//*
 * 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 GAME_PROJECTILE_CC 1
#include <utility>
#include "base/module.hh"
#include "base/timer.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs_vector.hh"
#include "math/matrix.hh"
using namespace math;
#include "graph/module.hh"
#include "graph/scene_graph.hh"
using namespace graph;
#include "object/module.hh"
#include "object/dyna.hh"
using namespace object;
#include "collision/module.hh"
#include "collision/events.hh"
using namespace collision;
#include "world/module.hh"
using namespace world;
#include "physics/module.hh"
using namespace physics;
#include "game/module.hh"
#include "game/projectile.hh"
#include "game/defs.hh"

namespace game {

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////  Projectile  //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 * Projectile will be made temporarily indestructible (NON_COLLIDABLE)
 * else it would collide immediately into the launcher.
 *****************************************************************************/
Projectile::Projectile( shptr<Graph> graph,
                        Object&      launcher,
                        const Speed  speed,
                        const Meter  maxDistanceGeo,
                        const Meter  safeDistanceGeo,
                        const bool   malfunction )
:   Dyna(graph,launcher.GetPosition(),NON_COLLIDABLE),
    mHit(false),
    mSpeedKPH(physics::conv::Speed2KPH(speed)),
    mMaxDistance(world::conv::Meters2Sim(maxDistanceGeo)),
    mSafeDistance(world::conv::Meters2Sim(safeDistanceGeo)),
    mInitialPosition(launcher.GetPosition()),
    mPrevTick(0),
    mCollidable(false),
    mMalfunction(malfunction)
{
CHECK_TYPESIG(graph,TYPESIG_GRAPH);
CHECK_TYPESIG(&launcher,TYPESIG_OBJECT);
ASSERT( maxDistanceGeo > Meter(0) );
ASSERT( safeDistanceGeo > Meter(0) );

    SET_TYPESIG(this,TYPESIG_PROJECTILE);

    // Projectile inherits matrix from its launcher.
    SetMatrix( launcher.GetMatrix() );

    // Attach projectile to scene-graph.
    GET_SCENE_GRAPH().AttachObject( this );  // holds a reference
}

Projectile::~Projectile()
{
    // Don't re-detach from World (illogical as World would hold
    // a reference so this dtor would never be called).

    INVALIDATE_TYPESIG(this,TYPESIG_PROJECTILE);
}

/*****************************************************************************
 * Animate the projectile.
 *****************************************************************************/
void
Projectile::Tick( const Milliseconds millisecElapsed )
{
CHECK_TYPESIG(this,TYPESIG_PROJECTILE);

    // Call base method.
    Parent::Tick( millisecElapsed );

    // Has projectile moved too far?
    const fp distanceFromInitialPosition = Distance( GetPosition(), mInitialPosition );
    if ( distanceFromInitialPosition > mMaxDistance )
    {
        Zombify();  // ok to call repeatedly
        return;
    }

    // Wait until start/end of time segment is known.
    if ( mPrevTick > Milliseconds(0) )
    {
        // Compute step (distance in world space).
        const Milliseconds timeDelta = millisecElapsed - mPrevTick;
        const fp step1 = -physics::conv::KPH2Step( timeDelta, mSpeedKPH );  // 1D and negative

        // Once projectile is safely away from launcher, enable collision-detection.
        // Another use is to let projectile fly straight for a moment before guidance maneuvers it.
        const bool safelyAway = Distance( GetPosition(), mInitialPosition ) > mSafeDistance;
        if ( safelyAway and (not mMalfunction) )
        {
            // The guidance method recomputes matrix and returns a step vector.
            const WorldVertex step3 = Guide( step1 );  // 3D
            const WorldVertex pos = GetPosition() + step3;
            SetPosition( pos );
        }
        else
        {
            // Fly straight aimlessly.
            Translate( ZZ, step1 );
        }

        // Make projectile collidable after it has flown away from launcher.
        if ( (not mCollidable) and safelyAway )
        {
            mCollidable = true;
            SetCollidable( true );
        }
    }

    // Remember tick to measure time-segment.
    mPrevTick = millisecElapsed;
}

/*****************************************************************************
 * Handle collision (Template Method).
 * Dyna::Tick() --> Dyna::DetectCollision() --> Projectile::HandleCollision()
 *****************************************************************************/
void
Projectile::HandleCollision( shptr<Object> collider )
{
CHECK_TYPESIG(this,TYPESIG_PROJECTILE);
CHECK_TYPESIG(collider,TYPESIG_OBJECT);

    // Collision has happened.

    // Do once.
    if ( mHit )
        return;
    mHit = true;

    // Projectile is a Dyna.
    // But what it collided into could be a non-Dyna such as terrain
    // which should not be destroyed.
    SetCollision( Object::COLLISION_FATAL );
    if ( collider->IfDyna() )
        collider->SetCollision( Object::COLLISION_FATAL );

    // Broadcast collision event (even though this isn't part of the collision module).
    EVENT_COLLISION.Broadcast( std::make_pair< shptr<Object>, shptr<Object> >( this, collider.PTR() ) );

    // Schedule deletion of Projectile.
    Zombify();

    // Make explosion.
    Explode();  // pure virtual
}

} // namespace game
