/*****************************************************************************
 * $Id: eng_init.cc,v 1.70 2006/11/25 17:22:08 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	3D engine initialization, exit.
 * @remarks @verbatim
 * Notes:   Initialization is order-dependent and tricky.
 *          OpenGL is partially initialized very early because some Object ctors
 *          create Texture objects which depend on OpenGL being initialized.
 *          Afterwards OpenGL is fully initialized and given control.
 * @endverbatim
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define ENG_INIT_CC 1
#include "eng.hh"
#include "eng_init.hh"
#ifndef NO_PYTHON
#include "eng_python_ifc.hh"
#endif
#include "gfx_stream.hh"
#include "gfx_random.hh"

void EngineAtExit( void );

/*****************************************************************************
 * Initialize 3D engine.
 *****************************************************************************/
int
Engine::Init( int argc, char** argv, int width, int height )
{
    // Register C function to ensure proper cleanup/exit.
    atexit( EngineAtExit );
	DEBUG_CODE( GFX_SET_ASSERT_HANDLER( EngineAssertHandler ); )

	mCmdarg.mArgv0 = argv[0];
	gEngineEvent.mGfxsysReady.Listen( Listener_Engine_gfxsysReady );

	// Some source files need to initialize themselves at run-time.
	init_eng_polygon();

	// Process command-line args.  A chain of *Cmdarg() functions are called.
	InitPreCmdarg();
	Cmdarg( argc, argv );
	InitPostCmdarg();

	mMilsecTimerFreq = mCmdarg.mTimerMilsec ? mCmdarg.mTimerMilsec : DEFAULT_TIMER_MILSEC;
	mMilsecFrame = mMilsecTotal = mMilsecTimerFreq;		// initially as if one frame has elapsed

	// Start the gfxsys but don't transfer execution to it yet.
	static GfxsysParms gfxsysParms;
	gfxsysParms.mValid			= true;
	gfxsysParms.mTimerMilsec    = mMilsecTimerFreq;
	gfxsysParms.mWindowWidth    = width;
	gfxsysParms.mWindowHeight	= height;
	GfxsysInit( &argc, argv, &gfxsysParms );

	// Random seed.
	if ( mTestFrameRate || mCmdarg.mSameSeed )
		mSeed = 0x2b3e27bd;
	else if ( mCmdarg.mSeedArg )
		mSeed = mCmdarg.mSeedVal;
	else
		mSeed = random_true() & 0x7fffffff; // 32-bit positive seed

#ifndef NO_PYTHON
	// Initialize Python interpreter.  Create Python C modules.
	if (    ! gPython.Init()
         || ! PythonModuleEngine()
         || ! PythonModuleWorld() )
	{
		*gStreamError << "ERROR[engine]: initializing Python.\n";
	}
#endif

	// Texture objects are needed during Object construction (see implementation.html).
	InitTextures( GFXSYS_IF_READY() );

	// Make the simulated world.
	// Ordering initialization and reloading is little tricky.
	randomize( mSeed );
	gWorld.Make();
	gWorld.mLocus.Update();

	// Resize caches with estimates from density of objects per quadrant.
	// Cache should hold enough objects in the locus without evictions.
	mCacheSize = gWorld.CacheSize();
#if DEBUG
	if ( mCmdarg.mCacheSize ) mCacheSize = mCmdarg.mCacheSize;
	if ( ! mQuiet ) *gStreamDebug << "cache size = " << mCacheSize << endl;
#endif
	mCacheWorldVertex.Resize( mCacheSize );
	mCacheNormals.Resize(     mCacheSize );
	if ( mSpeed >= eSpeed_FASTER ) gWorld.PreloadCaches();

#if DEBUG == 2 && defined(__GNUC__)
	*gStreamDebug << "DEBUG==2 links with -lmcheck which is known to cause \n"
                  << "a core-dump at program exit on Linux Nvidia-proprietary driver. \n";
#endif

	// Set the frustum according to the world.
    GfxsysSetFrustum( gWorld.GetFrustumFar() );

    // Engine is ready.
    mReady = true;
	gEngineEvent.mGfxsysReady.Broadcast();

    // Go.
	GfxsysRun();

    gEng.Exit(0);  // this point may or may not be reached (see EngineAtExit())
	return 0;
}
// Call when the gfxsys is ready, so in turn, the engine is ready.
static void
Listener_Engine_gfxsysReady( void )
{
	GFXSYS_SET_READY();
	gEngineEvent.mEngineReady.Broadcast();
}

/*****************************************************************************
 * Process command-line args.
 *****************************************************************************/
void
Engine::Cmdarg( int argc, char** argv )
{
	// Parse args (pass 1).
	for ( int i = 1; i < argc; ++i )
	{
		if ( STREQ( argv[i], "-v" ) )	// verbose
		{
			mVerbose = 1;
			mQuiet   = 0;
			CmdargDone( i );
		}
		else if ( STREQ( argv[i], "-vv" ) )	// very verbose
		{
			mVerbose = 2;
			mQuiet   = 0;
			CmdargDone( i );
		}
		else if ( STREQ( argv[i], "-q" ) )	// quiet
		{
			mVerbose = 0;
			mQuiet   = 1;
			CmdargDone( i );
		}
		else if ( STREQ( argv[i], "-qq" ) )	// very quiet
		{
			mVerbose = 0;
			mQuiet   = 2;
			CmdargDone( i );
		}
#if DEBUG
		// Test frame-rate?
		else if ( STREQ( argv[i], "-fps" ) )
		{
			mTestFrameRate = true;
			CmdargDone( i );
		}
#endif

	}

	// -------------------------------------------------------------------------

	if ( ! GFX_SELF_CHECK() || ! SelfCheck() )
	{
		*gStreamError << "ERROR: SelfCheck() failed." << endl;
		Exit(1);
	}

	// -------------------------------------------------------------------------

	// Parse args (pass 2).
	for ( int i = 1; i < argc; ++i )
	{
		// Speed vs. memory.
		// fast   : larger cache sizes
		// faster : larger cache sizes + preloaded caches + less LOD
		// fastest: larger cache sizes + preloaded caches + even less LOD
		if ( STREQ( argv[i], "-fast" ) )
		{
			// -fast isn't supposed to reduce LOD at all.
			mSpeed = eSpeed_FAST;
			CmdargDone( i );
		}
		else if ( STREQ( argv[i], "-faster" ) )
		{
			mSpeed = eSpeed_FASTER;
			mLodUser.Set( eLodUser_HIGH );
			CmdargDone( i );
		}
		else if ( STREQ( argv[i], "-fastest" ) )
		{
			mSpeed = eSpeed_FASTEST;
			mLodUser.Set( eLodUser_MID );
			CmdargDone( i );
		}
		else if ( STREQ( argv[i], "-memory" ) )
		{
			mSpeed = eSpeed_NORMAL;
			CmdargDone( i );
		}
		// Set LOD.
		// -lod can be passed after -faster/est to render quicker but without sacrificing LOD.
		else if (    STREQ( argv[i], "-lod"         )
                  || STREQ( argv[i], "-lod=default" )
                  || STREQ( argv[i], "-lod=normal"  ) )
		{
			mCmdarg.mLodUser = true;
			mLodUser.Reset();
			CmdargDone( i );
		}
		else if ( STREQ( argv[i], "-lod=max"  ) )
		{
			mCmdarg.mLodUser = true;
			mLodUser.Set( eLodUser_MAX );
			CmdargDone( i );
		}
		else if (    STREQ( argv[i], "-lod=high" )
                  || STREQ( argv[i], "-lod=hi"   ) )
		{
			mCmdarg.mLodUser = true;
			mLodUser.Set( eLodUser_HIGH );
			CmdargDone( i );
		}
		else if (    STREQ( argv[i], "-lod=medium" )
             || STREQ( argv[i], "-lod=med"    )
             || STREQ( argv[i], "-lod=mid"    ) )
		{
			mCmdarg.mLodUser = true;
			mLodUser.Set( eLodUser_MID );
			CmdargDone( i );
		}
		else if (    STREQ( argv[i], "-lod=low" )
                  || STREQ( argv[i], "-lod=lo"  ) )
		{
			mCmdarg.mLodUser = true;
			mLodUser.Set( eLodUser_LOW );
			CmdargDone( i );
		}
		else if ( STREQ( argv[i], "-lod=min" ) )
		{
			mCmdarg.mLodUser = true;
			mLodUser.Set( eLodUser_MIN );
			CmdargDone( i );
		}
		// Set timer frequency in milliseconds.
		else if ( STREQ( argv[i], "-freq" ) || STREQ( argv[i], "-timer" ) )
		{
			mCmdarg.mTimerMilsec = Argi(i+1,argc,argv);
#if 0  // allow "-timer 1" for max FPS
			if ( mCmdarg.mTimerMilsec < 5 || mCmdarg.mTimerMilsec > 200 )
				mCmdarg.mTimerMilsec = DEFAULT_TIMER_MILSEC;
#endif
			ENGINE_STATS_CODE( *gStreamStats << "Set frequency of rendering timer to " << mCmdarg.mTimerMilsec << " milliseconds. \n"; )
			CmdargDone( i, 2 );			// note the order: CmdargDone() before incrementing i
			i += 1;
		}
		else if ( STREQ( argv[i], "-seed" ) )
		{
			mCmdarg.mSeedArg  = true;
			mCmdarg.mSeedVal = Argi(i+1,argc,argv);
			CmdargDone( i, 2 );
			i += 1;
		}
		// For testing memory consumption.
		else if ( STREQ( argv[i], "-sameseed" ) || STREQ( argv[i], "-testmem" ) )
		{
			mCmdarg.mSameSeed = true;
			CmdargDone( i );
		}
		// Anti-aliasing.
		else if ( STREQ( argv[i], "-antialias=off" ) || STREQ( argv[i], "-no-antialias" ) )
		{
			GFX_ENABLE_ANTIALIAS( false ); // safe to call before gfxsys is ready
			CmdargDone( i );
		}
		else if ( STREQ( argv[i], "-antialias=on" ) || STREQ( argv[i], "-antialias" ) )
		{
			GFX_ENABLE_ANTIALIAS( true ); // safe to call before gfxsys is ready
			CmdargDone( i );
		}
		else if ( STREQ( argv[i], "-no-fsaa" ) )
		{
			// This causes Craft objects to be rendered by PolygonList::Draw()
			// which sw antialiasing.  For older video cards.
			mFSAA = false;
			CmdargDone( i );
		}
		else if ( STREQ( argv[i], "-fsaa" ) )
		{
			mFSAA = true;
			CmdargDone( i );
		}
#if DEBUG
		else if ( STREQ( argv[i], "-cache" ) )
		{
			mCmdarg.mCacheSize = Argi(i+1,argc,argv);
			CmdargDone( i, 2 );
			i += 1;
		}
#endif // DEBUG
    	else if ( STREQ( argv[i], "-near" ) )
		{
            SetPerspective( ePerspective_NEAR );
			CmdargDone( i );
        }
    	else if ( STREQ( argv[i], "-far" ) )
		{
            SetPerspective( ePerspective_FAR );
			CmdargDone( i );
        }
	}
}

/*****************************************************************************
 * Self-check, verify assumptions.
 *****************************************************************************/
bool
Engine::SelfCheck( void )
{
	bool error = false;
	if ( ! PolygonAttribs::SelfCheck() ) error = true;
	return ! error;
}

/*****************************************************************************
 * Exit.
 *****************************************************************************/
void
Engine::Exit( int exitcode )
{
    // Ensure this executes only once.
    if ( gExiting )
        return;

	gEngineEvent.mExit.Broadcast();

#ifndef NO_PYTHON
	gPython.Exit();
#endif

    // Lastly because gExiting disables many things (esp. static objects).
	gExiting = true;
	GFX_CLEANUP();  // sets gExitingGfx

    // Don't call exit() if libc called the atexit() callback
    // as libc is going to exit the program.
    if ( ! mAtExitCalled )
        exit( exitcode );
}

/*****************************************************************************
 * AtExit() is registered by libc atexit().
 * The reason is to ensure that Engine::Exit() is called during exit which
 * is necessary to properly cleanup and exit.
 * On some platforms (Mac), if the program is stopped by the OS GUI,
 * glutMainLoop() will directly call exit() instead of breaking the GLUT event loop.
 *****************************************************************************/
void
EngineAtExit( void )
{
    // Ensure this executes only once.
    if ( ! gEng.mAtExitCalled )
    {
        gEng.mAtExitCalled = true;
        gEng.Exit(0);  // call Engine::Exit()
    }
}
