/*****************************************************************************
 * $Id: eng_class.hh,v 1.46 2006/08/10 03:18:58 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Engine classes that serve as the engine's interface.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#ifndef ENG_CLASS_HH
#define ENG_CLASS_HH 1

#include "gfx_shared_val.hh"
#include "gfx_file.hh"
#include "eng_init.hh"
#include "eng_filter.hh"
#include "eng_lod.hh"
#include "eng_eye.hh"
#include "eng_view.hh"
#include "eng_debug.hh"
#include "eng_world.hh"
#include "meta_world.hh"

////////////////////////////////////////////////////////////////////////////////
/// @brief Engine event nexus.
/// @remarks The engine broadcasts events out to listener functions in other modules.
/// Using this event mechanism helps make the engine modular.
///
/// Example of client registering as a listener:
///
///     Client function is called when enginer becomes ready.
///     void ListenerEngineReady(void);
///     gEngineEvent.mEngineReady.Listen( ListenerEngineReady, true );
///
class EngineEvents
{
public:
	Event<void>			mEngineReady;		// other modules should listen for this, not gfxsysReady
	Event<void>			mGfxsysReady;
	Event<void>			mExit;
	Event<void>			mAnimate;
	Event<void>			mTick;
	Event<void>			mNeedTextureDirVec;
	Event<void>			mDrawObjects;
	Event<void>			mResetScene;
	Event<void>			mPreDrawViews;
	Event<void>			mPostDrawViews;
	Event< Geom<int>& >	mReshape;
	Event<void>			mLodUserChanged;
	Event< std::pair<eMouseEvent,bool> >	mMouseButton;
	Event< std::pair<int,int> >				mMouseMotion;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief Class that represents the 3D engine.
/// @remarks Has no constructor.  Initialization is done specially by Init*Cmdarg().
class Engine
{
PREVENT_COPYING(Engine)
public:					Engine( void );
public:					~Engine();
public:		int			Init( int argc, char** argv, int width, int height );
private:	void		InitPreCmdarg( void );
private:	void		InitPostCmdarg( void );
private:	void		InitTextures( bool gfxsysReady );
private:	void		InitTexturesTgaFiles( bool gfxsysReady );
private:	void		Cmdarg( int argc, char** argv );
public:		void		Exit( int exitcode );
private:	bool		SelfCheck( void );
private:	void		Render( void );
private:	void		Animate( void );
private:	void		Tick( int milsecFrame, int milsecTotal );
public:     DynaPtrc    GetCurrent( void ) { return mCurrent; }
public:     void        SetCurrent( DynaPtrc dyna ) { mCurrent = dyna; }
// Return timer frequency in milli-seconds.
public:		int			GetMilsecTimerFreq( void ) { return mMilsecTimerFreq; }
// Return time elapsed in the actual frame which is defined as the time between current/previous timer-ticks.
public:		int			GetMilsecFrame( void ) { return mMilsecFrame; }
// Return time elapsed since program start.
public:		int			GetMilsecTotal( void ) { return mMilsecTotal; }
// Return frames dropped between the current/previous timer-ticks.
public:		int			GetDroppedFrames( void )
						{
							// If no frames were dropped, the timer-tick frequency in
							// milliseconds will equal the delta of current/previous ticks.
							int droppedFrames = mMilsecFrame / mMilsecTimerFreq - 1;
							ASSERT( droppedFrames >= 0 );
							return droppedFrames;
						}
public:		void		ResetScene( void );
public:		void		DrawObjects( void );
private:	void		SortObjects( void );
private:	void		DrawSortedObjects( void );
public:		void		EnableFog( bool enable = true );
public:		int			SetVerbose( int verbose );
public:		bool		IfVerbose( void ) { return mVerbose; }
public:		bool		IfQuiet( void ) { return mQuiet; }
public:		bool		IfBusy( void ) { return mBusy; }
public:		void		PostRedisplay( void ) { ++mRedisplay; }
public:		bool		IfCollisions( void ) { return mCollisions; }
public:		void		EnableCollisions( bool f ) { mCollisions = f; }
public:		int			TestViewport( const EyeVertex* vertexs, int vertexCnt );
public:		void		SetSeed( uint seed ) { mSeed = seed; }
public:		uint		GetSeed( void ) { return mSeed; }
public:		eSpeed		GetSpeed( void ) { return mSpeed; }
public:		void		SetSpeed( eSpeed speed ) { mSpeed = speed; }
public:		void		SetTextureDirVec( const vector<string>& textureDirVec ) { mTextureDirVec = textureDirVec; }
public:		TexturePtrc	GetTexture( const char* textureName )
						{
							map<string,Texture*>::iterator itr = mTextureMap.find( textureName );
							ASSERT( itr != mTextureMap.end() );
							return itr->second;
						}

			// User-selected level-of-detail (LOD).
public:		bool		IfReducedLodUser( void )            { return mLodUser.IfReducedLod(); }
public:		eLodUser	GetLodUser( void )                  { return mLodUser.Get(); }
public:		void		SetLodUser( eLodUser lod )          { mLodUser.Set( lod ); }
public:		void		SetLodUser( const string& lod )     { mLodUser.Set( String2LodUser(lod) ); }
public:		void		ResetLodUser( void )                { mLodUser.Reset(); }
public:		void		IncLodUser( void )                  { mLodUser.Inc(); }
public:		bool		IfAtLeastLodUser( eLodUser lod )    { return mLodUser.IfAtLeastLod( lod ); }
public:		bool		IfLessLodUser( eLodUser lod )       { return mLodUser.IfLessLod( lod ); }
public:		bool		IfMaxLodUser( void )                { return mLodUser.IfMaxLod(); }
public:		bool		IfMinLodUser( void )                { return mLodUser.IfMinLod(); }
public:	    const string& LodUserString( void )		        { return mLodUser.Lod2String( GetLodUser() ); }
public:	    const string& LodUser2String( eLodUser lod )    { return mLodUser.Lod2String( lod ); }
public:		eLodUser	String2LodUser( const string& lod ) { return mLodUser.String2Lod( lod ); }

public:		RGBA		GetBackgroundRGBA( void ) { return mBackgroundRGBA; }
public:		RGBA		GetFogRGBA( void ) { return mFogRGBA; }
public:		fp			GetFogDensity( void ) { return mFogDensity; }
public:		void		SetBackgroundRGBA( const RGBA rgba ) { mBackgroundRGBA = rgba; }
public:		void		SetFogRGBA( const RGBA rgba ) { mFogRGBA = rgba; }
public:		void		SetFogDensity( fp fogDensity ) { mFogDensity = fogDensity; }
public:		bool		IfFSAA( void ) { return mFSAA; } ///< if video hw supports full-screen antialiasing
	// ------- to be initialized by cmd-arg processing -------------------------
private:	bool					mTestFrameRate;
	// ------- to be initialized before cmd-arg processing ---------------------
			// These must be initialized.
private:	uint					mSeed;
private:    bool                    mAtExitCalled;      ///< if atexit() callback was called
private:	int						mBusy;
private:	int						mVerbose;			///< 0=not 1=verbose 2=very
private:	int						mQuiet;				///< 0=not 1=quiet   2=very
private:	eSpeed					mSpeed;
private:    enum                    { DEFAULT_ENGINE_CACHE_SIZE = 512 };
private:	int						mCacheSize;
private:	int						mMilsecTimerFreq;	///< frequency of timer-tick in milli-seconds
private:	int						mMilsecFrame;		///< elapsed time relative to previous timer-tick
private:	int						mMilsecTotal;		///< elapsed time relative to program start
private:	bool					mCollisions;
private:	vector<string>			mTextureDirVec;		///< events broadcast to assign this
public:		ostream*				mStreamStatus;		///< for printing status info
private:	RGBA					mBackgroundRGBA;
private:	RGBA					mFogRGBA;
private:	fp						mFogDensity;
private:	bool					mFSAA;				///< if video hw supports full-screen antialiasing
#if _WIN32
private:	bool					mConsoleEnabled;
#endif
			// These have default values or don't need initialization.
#if STATS
public:		EngineStats				mStats;
#endif
private:	LodUser					mLodUser;
private:	int						mRedisplay;
public:		TempArray<int8>			mTempArrayInt8;
public:		TempArray<LocalVertex>	mTempArrayLocalVertex;
public:		TempArray<WorldVertex>	mTempArrayWorldVertex;
public:		TempArray<EyeVertex>	mTempArrayEyeVertex;
	// ------- to be initialized after cmd-arg processing ----------------------
	// 3D objects.
public:		Eye						mEye;		///< very frequent so keep as non-pointer for speed
public:		DynaPtr					mCurrent;
	// View/render state.
public:		ViewMaster				mView;
	// Lists and caches.
private:	void					KillZombies();
private:	Dlist<Object>			mZombieDlist;
private:	Cache<WorldVertex>		mCacheWorldVertex;
private:	Cache<Normal>			mCacheNormals;
public:		void					PutCacheWorldVertex( CacheOpq<WorldVertex>* opq, WorldVertex* o )
									{
										mCacheWorldVertex.Put( opq, o );
									}
public:		void					PutCacheNormals( CacheOpq<Normal>* opq, Normal* o )
									{
										mCacheNormals.Put( opq, o );
									}
public:		WorldVertex*			GetCacheWorldVertex( CacheOpq<WorldVertex>* opq )
									{
										WorldVertex* o = mCacheWorldVertex.Get( opq );
										ENGINE_STATS_CODE( if ( o == NULL ) ++mStats.mCacheMissVertexs; )
										return o;
									}
public:		Normal*					GetCacheNormals( CacheOpq<Normal>* opq )
									{
										Normal* o = mCacheNormals.Get( opq );
										ENGINE_STATS_CODE( if ( o == NULL ) ++mStats.mCacheMissNormals; )
										return o;
									}
	// Misc.
private:	map<string,Texture*>	mTextureMap;
	// ------- cmd-line args ---------------------------------------------------
public:		struct EngineCmdarg
			{
			public:
				EngineCmdarg( void )
				{
					mArgv0			= NULL;
					mLodUser		= false;
					mTimerMilsec	= DEFAULT_TIMER_MILSEC;
					mCacheSize		= 0;
					mSeedArg		= false;
					mSeedVal		= 0xeb39c4e5;
					mSameSeed		= false;
				}
			public:
				char*	mArgv0;
				bool	mLodUser;
			private:
				int		mTimerMilsec;
				int		mCacheSize;
				bool	mSeedArg;		// if -seed arg passed
				uint	mSeedVal;		// value following -seed
				bool	mSameSeed;
			friend class Engine;
			};
public:		EngineCmdarg	mCmdarg;

#include "eng_class_math.hh"

friend class Object;
friend void EngineAtExit( void );
#if GFXSYS_OPENGL
friend void OpenglDisplayCallback( void );
friend void OpenglTimer( UNUSED int unused );
#endif // GFXSYS_OPENGL

#if DEBUG
public:		string		GetObjectNameRendering( void ) { return mObjectNameRendering; }
public:		void		SetObjectNameRendering( ObjectPtrc obj ) { mObjectNameRendering = obj->GetName(); }
private:	string		mObjectNameRendering;
friend ostream& operator<<( ostream& strm, const EngineStats& o );
#endif

	// Test frame rate (FPS).
#if DEBUG
private:	void		TickTestFrameRate( void );
private:	void		TestFrameRate( void );
private:	void		TestFrameRateRender( void );
private:	void		TestFrameRateSys( string& result );
#	if __linux__
private:	void		TestFrameRate_times( string& result );
#	else
private:	void		TestFrameRate_gettimeofday( string& result );
#	endif // _linux__
#endif // DEBUG
};

#endif // ENG_CLASS_HH
