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

#define PROGRAM_MODULE_CC 1
#include "base/module.hh"
#include "base/misc.hh"
#include "base/stream.hh"
using namespace base;
#include "math/module.hh"
using namespace math;
#include "graph/module.hh"
#include "graph/model_cache.hh"
#include "graph/scene_graph.hh"
using namespace graph;
#include "object/module.hh"
#include "object/aircraft.hh"
#if DEBUG
#include "object/zombies.hh"
#endif
using namespace object;
#include "world/module.hh"
#include "world/conf.hh"
using namespace world;
#include "sky/module.hh"
#include "sky/conf.hh"
using namespace sky;
#include "fx/module.hh"
#include "fx/fx.hh"
using namespace fx;
#include "sound/module.hh"
using namespace sound;
#include "init/module.hh"
using namespace init;
#include "lua_bind/module.hh"
#include "lua_bind/interpreter.hh"
using namespace lua::interpreter;
#include "view/module.hh"
#include "view/conf.hh"
#include "view/window.hh"
using namespace view;
#include "program/module.hh"
#include "program/conf.hh"
#include "program/splash.hh"
#include "program/aircraft_models.hh"

#if COMPILE_SDL
// SDL header defines a main() macro, undefine it.
#undef main
#endif

//==============================================================================

/*****************************************************************************
 * main.
 *****************************************************************************/
int main( int argc, char** argv )
{
    try
    {
        // Start module and enter main loop.
        GET_PROGRAM().Run( argc, argv );
    }
    catch ( const std::exception& e )
    {
        CERROR << "EXCEPTION! " << std::endl << e.what() << std::endl;
        #if DEBUG
        CERROR << "Memory used: " << FormatMegabyte(MemoryUsed()) << " MB" << std::endl;
        #endif
        EXIT( 1 );
    }
    catch ( ... )
    {
        CERROR << "EXCEPTION! " << std::endl;
        #if DEBUG
        CERROR << "Memory used: " << FormatMegabyte(MemoryUsed()) << " MB" << std::endl;
        #endif
        EXIT( 1 );
    }
    EXIT( 0 );
    return 0;
}

//==============================================================================

namespace program {

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////  Program  ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *
 * As always, beware of NULLs during initialization.
 *****************************************************************************/
Program::Program( void )
:   mPause(false),
    mCommandQueue()
{
ASSERT_RUN_ONCE;
}

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

/*****************************************************************************
 * Run!
 *****************************************************************************/
void
Program::Run( int argc, char** argv )
{
    // Splash.
    CLOG << "===========================================" << std::endl
         << "          Palomino Flight Simulator        " << std::endl
         << defs::PROGRAM_COPYRIGHT_DATE << " Jim E. Brooks - www.palomino3d.org" << std::endl
         << "===========================================" << std::endl
#if PROFILE == 1
         << "[profNoOpt "
#elif PROFILE == 2
         << "[profOpt "
#elif DEBUG
         << "[dbg "
#else
         << "[rel "
#endif
         << "OSG " << OPENSCENEGRAPH_MAJOR_VERSION << '.'
                   << OPENSCENEGRAPH_MINOR_VERSION << '.'
                   << OPENSCENEGRAPH_PATCH_VERSION << ']' << std::endl;
#if COMPILE_GUI
    CLOG << "Press F1 to start GUI." << std::endl;
#endif

    // Initialize and start modules.
    const base::InitArgs initArgs( argc, argv, defs::PROGRAM_NAME_SHORT );
    init::Init( initArgs );
    program::global::gConf.Init( initArgs, defs::CONF_TXT_FILENAME );
    init::Start();

    // Start Lua interpreter.
    // Lua scripts will create scene-graph.
    GET_LUA().ExportCmdLine( argc, argv );
    GET_LUA().RunScript( "startup.lua" );

#if DEBUG
    SelfChecks();
#endif

    // Register timer-tick handler.
    // Pausing must not stop this tick handler else the Lua keyboard handler won't run.
    GET_TIMER().Register( new TickFunctor, PROGRAM_CONF.mTimerTickFreq, base::TickFunctor::CANNOT_DISABLE );

    // Splash (graphical).
    GET_PROGRAM_SPLASH().Start();

    // Enter the graphics main loop.
    GET_WINDOW().EnterMainLoop();

    // Bye!
    EXIT( 0 );
}

/*****************************************************************************
 * Reset program (Backspace pressed).
 *****************************************************************************/
void
Program::Reset( void )
{
    // Reset window.
    GET_WINDOW().Reset();

    // Stop explosions.
    GET_FX().Reset();

    // Stop playing sounds.
    GET_SOUND().Stop();

#if DEBUG
    CDEBUG << "objects = " << Object::GetObjectCount() << std::endl;
#endif
}

/*****************************************************************************
 * Flush the scene-graph.  Then reload essential parts.
 *****************************************************************************/
void
Program::FlushSceneGraph( void )
{
    // Flush.  Flushing removes almost all nodes.
    GET_SCENE_GRAPH().Flush();

    // Reload parts that C++ loads.
    GET_WORLD().Make();
    if ( SKY_CONF.mSkyEnabled )
        GET_SKY().Make();  // don't call instance if not enabled
}

/*****************************************************************************
 * The pulse of the module.
 *****************************************************************************/
void
Program::TickFunctor::Tick( const Milliseconds millisecElapsed )
{
    GET_PROGRAM().Tick( millisecElapsed );
}

void
Program::Tick( const Milliseconds millisecElapsed )
{
    // Run a Lua script every tick.
    GET_LUA().RunScript( "tick.lua" );
}

/*****************************************************************************
 * Pause simulation.
 *****************************************************************************/
void
Program::SetPause( bool pause_ )  // pause() is a syscall
{
    mPause = pause_;

    // Turn off sound while paused.
    GET_SOUND().Enable( not pause_ );

    // To pause, disable most timer-tick handlers.
    // The tick that propagates to Lua (which handles keyboard) stays enabled regardless.
    GET_TIMER().Enable( not pause_ );
}

bool
Program::IfPaused( void )
{
    return mPause;
}

/*****************************************************************************
 * Command queue is a queue of strings which are processed by Lua scripts.
 *****************************************************************************/
void
Program::EnqueueCommand( const string& cmd )
{
    mCommandQueue.push_back( cmd );
}

/*****************************************************************************
 * @return Next command or empty string.
 *****************************************************************************/
string
Program::DequeueCommand( void )
{
    if ( mCommandQueue.empty() )
    {
        return string();
    }
    else
    {
        const string cmd = mCommandQueue.front();
        mCommandQueue.pop_front();
        return cmd;
    }
}

/*****************************************************************************
 * Debug
 *****************************************************************************/
#if DEBUG
void
Program::SelfChecks()
{
    // Test that World or something else doesn't keep a reference
    // to an Object after it is detached.
if ( not VIEW_CONF.mManipulatorEnabled )  // exclude special mode
 {
    const int objectCount = Object::GetObjectCount();
   {
    shptr<Object> obj = Object::MakeDummyObject();
    GET_SCENE_GRAPH().AttachObject( obj );
    GET_SCENE_GRAPH().DetachObject( obj );
   }
    ASSERT( Object::GetObjectCount() <= objectCount );  // in case other zombies were destroyed
 }

    // Test a complex Object (Aircraft) to ensure that
    // it has no references to itself (the refCount-cycle bug).
    // Failing to free an Object loaded with a 3D model file
    // will waste lots of memory.
 {
    const int objectCount = Object::GetObjectCount();
   {
    shptr<Aircraft> aircraft = MakeAircraft( eAircraftType_P51, WorldVertex(0,0,0), ModelCache::LOAD_SHARED );
    ASSERT( objectCount + 1 == Object::GetObjectCount() );  // should count new Aircraft
    GET_SCENE_GRAPH().AttachObject( aircraft.PTR() );
    GET_SCENE_GRAPH().DetachObject( aircraft.PTR() );
    aircraft->Zombify();
   }
    GET_ZOMBIE_OBJECTS().DestroyZombies();
    ASSERT( Object::GetObjectCount() <= objectCount );  // in case other zombies were destroyed
 }
}
#endif // DEBUG

} // namespace program
