/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Game-related HUD widgets.
 *//*
 * 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_HUD_CC 1
#include <utility>
#include "base/module.hh"
#include "base/timer.hh"
#include "base/misc.hh"
#include "base/stream.hh"
using namespace base;
#include "object/module.hh"
using namespace object;
#include "hud/module.hh"
using namespace hud;
#include "game/module.hh"
#include "game/hud.hh"
#include "game/defs.hh"

namespace game {

INTERN const int  TARGET_BOX_WIDTH          = 60;  // uint causes sign bug!
INTERN const int  TARGET_BOX_HEIGHT         = 60;
INTERN const fp   TARGET_BOX_FONT_SIZE      = 20.0f;
INTERN const int  TARGET_BOX_TEXT_OFFSET_X  = -TARGET_BOX_WIDTH / 2 - 2;   // offset to avoid
INTERN const int  TARGET_BOX_TEXT_OFFSET_Y  = -TARGET_BOX_WIDTH / 2 - 8;  // obscuring 3D object
INTERN const RGBA TARGET_BOX_COLOR_DESELECTED( 0.8f, 0.8f, 0.1f, 1.0f );  // yellow
INTERN const RGBA TARGET_BOX_COLOR_SELECTED(   0xff, 0x99, 0x00, 0xff );  // orange
INTERN const RGBA TARGET_BOX_COLOR_RADAR_LOCK( 1.0f, 0.4f, 0.4f, 1.0f );  // red

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////  game::Hud  //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

Hud* Hud::msInstance;

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
Hud::Hud( void )
:   mDummyTarget(Object::MakeDummyObject()),  // -- ORDER-DEPENDENT --
    mSelectedTarget(mDummyTarget),            // -- ORDER-DEPENDENT --
    mTargetIndicatorMap(),
    mTickFunctor(new TickFunctor),
    mDestroyObjectListener(new DestroyObjectListener(*this))
{
ASSERT_RUN_ONCE;

    GET_TIMER().Register( mTickFunctor, game::defs::GAME_TICK_FREQ );
    EVENT_DESTROY_OBJECT.Listen( mDestroyObjectListener );
}

Hud::~Hud()
{
    GET_TIMER().Unregister( mTickFunctor );
    EVENT_DESTROY_OBJECT.Unlisten( mDestroyObjectListener );
}

/*****************************************************************************
 * Updates the target boxes.
 *****************************************************************************/
void
Hud::Tick( const Milliseconds millisecElapsed )
{
    // Update HUD with positions of targets.
    for ( TargetIndicatorMap::iterator iter = mTargetIndicatorMap.begin();
          iter != mTargetIndicatorMap.end();
          ++iter )
    {
        Object&  object  = *iter->first;           CHECK_TYPESIG(&object,TYPESIG_OBJECT);
        HudBox&  hudBox  = *iter->second.mHudBox;  CHECK_TYPESIG(&hudBox,TYPESIG_HUD_BOX);
        HudText& hudText = *iter->second.mHudText; CHECK_TYPESIG(&hudText,TYPESIG_HUD_TEXT);
        hudBox.Set3D( object.GetPosition(), TARGET_BOX_WIDTH, TARGET_BOX_HEIGHT );
        hudText.SetPosition3D( object.GetPosition() );
    }
}

// Reflect.
void
Hud::TickFunctor::Tick( const Milliseconds millisecElapsed )
{
    GET_GAME_HUD().Tick( millisecElapsed );
}

/*****************************************************************************
 * Set attributes of TargetIndicator (graphical object).
 * @param   object
 *          Object mapped to target-indicator box.
 * @param   color
 * @param   drawX
 *          Draw an X over box.
 *****************************************************************************/
void
Hud::SetTargetIndicator( shptr<Object> object, const RGBA color, const bool drawX )
{
CHECK_TYPESIG(object,TYPESIG_OBJECT);

    // Map Object to TargetIndicator.
    TargetIndicatorMap::iterator iter = mTargetIndicatorMap.find( object );
    if ( EX( iter != mTargetIndicatorMap.end() ) )
    {
        // Change attributes of OSG nodes.
        HudBox& hudBox   = *iter->second.mHudBox;  CHECK_TYPESIG(&hudBox,TYPESIG_HUD_BOX);
        HudText& hudText = *iter->second.mHudText; CHECK_TYPESIG(&hudText,TYPESIG_HUD_TEXT);
        hudBox.SetDrawX( drawX );  // draw an X
        hudBox.SetColor( color );
        hudText.SetColor( color );
    }
#if DEBUG
    else
    {
        CDEBUG << "MALFUNCTION: game::Hud::SetTargetIndicator() object " << object->GetName() << " isn't attached." << std::endl;
    }
#endif
}

/*****************************************************************************
 * Attach target.
 * Represent this in the HUD as a "target indicator box".
 *****************************************************************************/
void
Hud::AttachTarget( shptr<Object> target, const string& text )
{
CHECK_TYPESIG(target,TYPESIG_OBJECT);

    // Does this target have a target-indicator box in HUD?
    if ( mTargetIndicatorMap.find( target ) == mTargetIndicatorMap.end() )
    {
        // Compose TargetIndicator with a HudBox and HudText.
        shptr<HudBox> hudBox = new HudBox;
        hudBox->SetColor( TARGET_BOX_COLOR_DESELECTED );

        HudText::Args hudTextArgs;
        hudTextArgs.mText       = text;
        UpperCase( hudTextArgs.mText );
        hudTextArgs.mColor      = TARGET_BOX_COLOR_DESELECTED;
        hudTextArgs.mFont.SetSize( TARGET_BOX_FONT_SIZE );
        hudTextArgs.mOffset[XX] = TARGET_BOX_TEXT_OFFSET_X;  // offset to avoid
        hudTextArgs.mOffset[YY] = TARGET_BOX_TEXT_OFFSET_Y;  // obscuring 3D object
        shptr<HudText> hudText = new HudText( hudTextArgs );

        // Map TargetIndicator.
        mTargetIndicatorMap.insert( std::make_pair<>( target, TargetIndicator(hudBox,hudText) ) );

        // Attach TargetIndicator to underlying HUD.
        hud::Hud::GetInstance().Attach( hudBox );
        hud::Hud::GetInstance().Attach( hudText->GetNode() );
    }
}

/*****************************************************************************
 * Detach target.
 * NOP if target isn't contained in this HUD.
 *****************************************************************************/
void
Hud::DetachTarget( shptr<Object> target )
{
CHECK_TYPESIG(target,TYPESIG_OBJECT);
CHECK_TYPESIG(mSelectedTarget,TYPESIG_OBJECT);

    // Don't detach dummy target
    // NOP if target isn't contained in this HUD.
    TargetIndicatorMap::iterator iter = mTargetIndicatorMap.find( target );
    if ( iter != mTargetIndicatorMap.end()
     and target != mDummyTarget )
    {
        // Is this the selected target?
        if ( mSelectedTarget == target )
            mSelectedTarget = mDummyTarget;

        // Detach from HUD.
        TargetIndicator& targetIndicator = iter->second;
        hud::Hud::GetInstance().Detach( targetIndicator.mHudBox );
        hud::Hud::GetInstance().Detach( targetIndicator.mHudText->GetNode() );

        // Erase from STL container.
        mTargetIndicatorMap.erase( target );
    }
}

/*****************************************************************************
 * Select a target.  Represented in HUD as a "target indicator box".
 *****************************************************************************/
void
Hud::SelectTarget( shptr<Object> target, const bool radarLock )
{
CHECK_TYPESIG(target,TYPESIG_OBJECT);
CHECK_TYPESIG(mSelectedTarget,TYPESIG_OBJECT);

    // Do unconditionally regardless of mSelectedTarget=target (radarLock could change).

    // Deselect previous target.
    DeselectTarget();

    // Select next target.
    mSelectedTarget = target;
    SetTargetIndicator( target,
                        radarLock ? TARGET_BOX_COLOR_RADAR_LOCK : TARGET_BOX_COLOR_SELECTED,
                        radarLock );
}

/*****************************************************************************
 * Deselect all targets.
 *****************************************************************************/
void
Hud::DeselectTarget( void )
{
    // Unless dummy target is already selected.
    if ( mSelectedTarget != mDummyTarget )
    {
        // Of the deselected target, change its TargetIndicator (graphical object).
        SetTargetIndicator( mSelectedTarget, TARGET_BOX_COLOR_DESELECTED, false );

        // Then select dummy target (non-graphical operation).
        mSelectedTarget = mDummyTarget;
    }
}

/*****************************************************************************
 * Remove radar lock indicator.
 *****************************************************************************/
void
Hud::RemoveRadarLock( void )
{
    // Minor imperfection: red color will remain but X will be removed.
    // Though, this tell user that radar-lock was lost.
    for ( TargetIndicatorMap::iterator iter = mTargetIndicatorMap.begin();
          iter != mTargetIndicatorMap.end();
          ++iter )
    {
        HudBox& hudBox = *iter->second.mHudBox; CHECK_TYPESIG(&hudBox,TYPESIG_HUD_BOX);
        hudBox.SetDrawX( false );
    }
}

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

    // This will be a NOP if target isn't contained in this HUD.
    mHud.DetachTarget( object );
}

} // namespace game
