// ------------------------------------------------------------------------
/*
	\author: Jaap Suter

	Copyright (C) Electronic Arts Canada Inc. 2002.  All rights reserved.	
*/
// ------------------------------------------------------------------------

/*! \file */

#ifndef REVEAL_TWEAKER_H
#define REVEAL_TWEAKER_H

#include "reveal/impl/tree.h"

#include "reveal/color.h"
#include "reveal/gui_colors.h"

#include "reveal/callback/callback_0.h"
#include "reveal/callback/callback_1.h"
#include "reveal/callback/mem_fun_adapter.h"

#include "reveal/impl/menu.h"
#include "reveal/impl/mru_menu_forward.h"
#include "reveal/impl/range_control.h"
#include "reveal/impl/option_control.h"

#include "reveal/impl/gui_metrics.h"

#include "reveal/impl/window.h"
#include "reveal/graph.h"
#include "reveal/text_window.h"
#include "reveal/watch.h"

#include "reveal/global_enable.h"

#include <stddef.h>

namespace Reveal {
	
	class InputState;
	class Renderer;

	namespace Impl
	{
		class Widget;
	}

// ------------------------------------------------------------------------------
/*!
	\brief Core class;

	This is the core class providing the menu system to access values to tweak, view, etc.
	All other components of Reveal are aggregated in this class.
*/
// ------------------------------------------------------------------------------
class Tweaker : public Impl::Tree
{	
public:

	//! Class constants;
	enum
	{
		AUX_BUTTON_NAME_LENGTH  = 16, //!< Maximum length of the name for an auxillary button;
		STATUS_BAR_TEXT_LENGTH  = 64 //!< Maximum length of the name for an auxillary button;
	};

	/*!
		\brief Default constructor;

		Creates an empty menu system, with just a start and pause button (with no behaviour attached to them);
	*/
    Tweaker();

	//! Empty virtual destructor;
	virtual ~Tweaker() {}

	//! Returns true if we are in debug mode (e.g. REVEAL is defined);
	bool DebugMode() const;

	//! Returns true if the menu is currently enabled;
	bool Enabled() const { return mEnabled; }

	//! Enables or disables the menu;
	void SetEnable( bool e ) { mEnabled = e; }

	/*!
		\brief Adds a watch to the tweaker system

		\param T			Type of the function (float or int or char or whatever);
		\param path			Full path from the tweaker though all its submenus, up to the button that adds the watch;
		\param name			Name of the Watch;

		\return				Nothing;
	*/
	template < class T >
	void AddWatch( const char* path, const char* name, const Callback::Callback0< T > callbackFromGame )
	{
		#if defined REVEAL
			if ( IsRevealGloballyEnabled() )
			{
				for ( size_t i = 0; i < MAX_NUM_WINDOWS; ++i )
				{
					if ( mWindows[ i ].Get() == 0 )
					{
						Impl::Window* g = new Watch< T >( name, *this, callbackFromGame );
						mWindows[ i ].Reset( g );

						AddButtonControl( path, name, Callback::MemFunAdapter0( *mWindows[ i ], &Impl::Window::Popup ) );
						return;
					}
				}
				SetStatusBarMessage( "Error: no more room for a Watch" );
			}
		#endif
			return;
	}

	template < class T >
	void AddWatch( const char* path, const char* name, T (*callbackFromGame)() )
	{
		AddWatch( path, name, Callback::Callback0< T >( callbackFromGame ) );
	}

	/*!
		\brief Adds a text window to the tweaker system

		\param NUM_LINES	Number of maximum lines allowed in the buffer;
		\param name			Name of the window;

		\return				Text Window object;
	*/
	template < size_t NUM_LINES >
	TextWindow< NUM_LINES >* AddTextWindow( const char* name, const Callback::Callback1< void, TextWindow< NUM_LINES >& > refreshCallback )
	{
		#if defined REVEAL
			if ( IsRevealGloballyEnabled() )
			{
				for ( size_t i = 0; i < MAX_NUM_WINDOWS; ++i )
				{
					if ( mWindows[ i ].Get() == 0 )
					{
						TextWindow< NUM_LINES >* g = new TextWindow< NUM_LINES >( name, *this, refreshCallback );
						mWindows[ i ].Reset( g );

						return g;
					}
				}
				SetStatusBarMessage( "Error: no more room for a window" );
			}
		#endif
			return NULL;
	}

	//Workaround for not having an update callback (default initializer freaks out on PS2 compiler)
	template < size_t NUM_LINES >
	TextWindow< NUM_LINES >* AddTextWindow( const char* name )
	{
		#if defined REVEAL
			if ( IsRevealGloballyEnabled() )
			{
				for ( size_t i = 0; i < MAX_NUM_WINDOWS; ++i )
				{
					if ( mWindows[ i ].Get() == 0 )
					{
						TextWindow< NUM_LINES >* g = new TextWindow< NUM_LINES >( name, *this );
						mWindows[ i ].Reset( g );

						return g;
					}
				}
				SetStatusBarMessage( "Error: no more room for a window" );
			}
		#endif
			return NULL;
	}

	/*!
		\brief Adds a text window to the tweaker system + a menu item to make it "popup"

		\param NUM_LINES	Number of maximum lines allowed in the buffer;
		\param path			Full path from the tweaker though all its submenus, up to the button that adds the window;
		\param name			Name of the window;

		\return				Text Window object;
	*/
	template < size_t NUM_LINES >
	TextWindow< NUM_LINES >* AddTextWindow( const char* path, const char* name, const Callback::Callback1< void, TextWindow< NUM_LINES >& > refreshCallback )
	{
		#if defined REVEAL
			if ( IsRevealGloballyEnabled() )
			{
				TextWindow< NUM_LINES >* g = AddTextWindow<NUM_LINES>(name, refreshCallback);

				AddButtonControl( path, name, Callback::MemFunAdapter0( *(Impl::Window *)g, &Impl::Window::Popup ) );
				return g;
			}
		#endif
			return NULL;
	}

	//Workaround for not having an update callback (default initializer freaks out on PS2 compiler)
	template < size_t NUM_LINES >
	TextWindow< NUM_LINES >* AddTextWindow( const char* path, const char* name )
	{
		#if defined REVEAL
			if ( IsRevealGloballyEnabled() )
			{
				TextWindow< NUM_LINES >* g = AddTextWindow<NUM_LINES>(name);

				AddButtonControl( path, name, Callback::MemFunAdapter0( *(Impl::Window *)g, &Impl::Window::Popup ) );
				return g;
			}
		#endif
			return NULL;
	}

	/*!
		\brief Adds a graph to the tweaker system, with intelligent min and max;

		\param T			Type of the function (float or int or char or whatever);
		\param N			Number of items in the graph buffer (size of the history);
							This affects the number of lines needed to draw the graph, and thus the drawing speed;
		\param path			Full path from the tweaker though all its submenus, up to the button that adds the graph;
		\param name			Name of the graph;

		\return				Graph object. Todo;
	*/
	template < size_t N, class T >
	Graph< N, T >* AddGraph( const char* path, const char* name, T defaultValue )
	{
		#if defined REVEAL
			if ( IsRevealGloballyEnabled() )
			{
				for ( size_t i = 0; i < MAX_NUM_WINDOWS; ++i )
				{
					if ( mWindows[ i ].Get() == 0 )
					{
						Graph< N, T >* g = new Graph< N, T >( name, *this, defaultValue );
						mWindows[ i ].Reset( g );

						AddButtonControl( path, name, Callback::MemFunAdapter0( *mWindows[ i ], &Impl::Window::Popup ) );
						return g;
					}
				}
				SetStatusBarMessage( "Error: no more room for a graph" );
			}
		#endif
			return NULL;
	}


	/*!
		\brief Adds a graph to the tweaker system with locked min and max values;

		\param T			Type of the function (float or int or char or whatever);
		\param N			Number of items in the graph buffer (size of the history);
							This affects the number of lines needed to draw the graph, and thus the drawing speed;
		\param path			Full path from the tweaker though all its submenus, up to the button that adds the graph;
		\param name			Name of the graph;

		\return				Graph object. Todo;
	*/
	template < size_t N, class T >
	Graph< N, T >* AddGraph( const char* path, const char* name, T lockedMin, T lockedMax )
	{
		#if defined REVEAL
			if ( IsRevealGloballyEnabled() )
			{			
				for ( size_t i = 0; i < MAX_NUM_WINDOWS; ++i )
				{
					if ( mWindows[ i ].Get() == 0 )
					{
						Graph< N, T >* g = new Graph< N, T >( name, *this, lockedMin, lockedMax );
						mWindows[ i ].Reset( g );

						AddButtonControl( path, name, Callback::MemFunAdapter0( *mWindows[ i ], &Impl::Window::Popup ) );
						return g;
					}
				}
				SetStatusBarMessage( "Error: no more room for a graph" );
			}
		#endif
			return NULL;
	}

	/*!
		\brief Removes a window that was previously added with one of the calls above;

		\param window		Window to remove
	*/
	void RemoveWindow (Impl::Window& window);

	//! Cascades all currently visible windows;
	void CascadeWindows ();
		
	/*!
		\brief Provides a way to hook a callback to the pause button;
		\param setPause		Callback that needs to be called whenever the pause button is clicked;
		\param getPause		Callback that obtains the current pause status.
	*/
	void SetPauseCallback( const Callback::Callback1< void, bool >& setPause, const Callback::Callback0< bool >& getPause = Callback::Callback0< bool >() );

	//! Enum used by the SetAuxCallback function;
	typedef enum
	{
		DRAW_NOTHING,
		DRAW_MOUSE,
		DRAW_WINDOWS_AND_MOUSE
	} AuxilliaryMode;

	/*!
		\brief Provides a way to hook a callback to an auxillary button;
		\param idx			Index of the auxillary button you want to use, in the range [0..Tweaker::NUM_AUXILLARY_BUTTONS>;
		\param name			Name that is displayed on the button;
		\param callback		Callback that needs to be called whenever the auxillary button is clicked;
		\param drawCursor   True if the mouse cursor should be drawn during an auxilliary callback;
		\note  When the auxillary button is pressed, the tweaker system becomes invisible and control is given to the
			   callback. Every update the callback will be called with the current input state, until the callback returns false.
			   In that case, the tweaker system takes control back and becomes visible again;
	*/
	void SetAuxCallback( size_t idx, const char* name, const Callback::Callback1< bool, const InputState& >& callback, AuxilliaryMode mode = DRAW_NOTHING );
	
	//! Same as SetAuxCallback, but with automatic aux button assignment
	void AddAuxCallback( const char* name, const Callback::Callback1< bool, const InputState& >& callback, AuxilliaryMode mode = DRAW_NOTHING );
	
	//! Removes the callback with the given name;
	bool RemoveAuxCallback( const char* name );

	//! Returns the maximum number of auxillary callbacks;
	size_t MaxNumAuxCallbacks() const { return Impl::GuiMetrics::NUM_AUXILLARY_BUTTONS; }

	//! Explicitly starts an auxillary callback;
	void StartAuxCallback( const char* name );

	/*!
		\brief Handles all input;
		
		This function needs to be called every frame, to give the tweaker system (and its children) a chance to deal with input;

		\param inputState	An up-to-date instance of your implementation of the Reveal::InputState class
	*/
	void Handle( const InputState& inputState );

	/*!
		\brief Renders the menu system

		This function needs to be called every frame, to give the tweaker (and its children) a chance to render
		themselves, if they want to.
	*/
    void Render( const Renderer& renderer ) const;

	//! Sets a message to appear on the status bar;
	void SetStatusBarMessage( const char* t );

	//! Hides or shows the statusbar;
	void SetStatusBarVisibility( bool v ) { mShowStatusBar = v; }

	//! Hides or shows ALL menus;
	void SetMenusVisibility (bool v) { mShowMenus = v; }

	//! Hides or shows the cursor;
	void SetCursorVisibility (bool v) { mShowCursor = v; }

	//! Adds a hotkey to the system;
	void AddButtonHotKey( Reveal::uint32_t hotKey, const Callback::Callback0< void >& mCallback );

	//! Adds a checkbox hotkey to the system;
	void AddButtonHotKey( Reveal::uint32_t hotKey, const Callback::Callback1< void, bool >& mSetter, const Callback::Callback0< bool >& mGetter );

	//! Callback for the windowing system;
	bool WindowSystem( const InputState& inputState );

	//! Sets the Confirm Menu Expansion flag
	void SetConfirmMenuExpansion(bool flag) { Impl::Tree::SetConfirmMenuExpansion(flag); }

	//! Returns the Confirm Menu Expansion flag
	const bool GetConfirmMenuExpansion() 		{ return Impl::Tree::GetConfirmMenuExpansion(); }

	void SetOverrideInputState(InputState *state)		{ mOverrideInputState = state; }

private:

	// Copy ctor and assignment op disabled;
	Tweaker( const Tweaker& );
	Tweaker& operator = ( const Tweaker& );

	//! Renders the pause button;
  void RenderPauseButton( const Renderer& renderer ) const;

	//! Renders the auxillary buttons;
  void RenderAuxillaryButtons( const Renderer& renderer ) const;

	//! Returns -1 if the mouse is not over an auxillary button, and
	//! the index of the auxillary button the mouse is over otherwise;
	int MouseOverAuxillaryButton() const;

	//! Draws the mouse cursor;
	void RenderMouseCursor( const Renderer& renderer ) const;

	//! Draws all enabled windows;
	void RenderWindows( const Renderer& renderer ) const;

	//! Calls the pause callback;
	void CallPauseCallback();

	bool LetWindowsHandle ( const InputState& origInputState );

	const InputState& CheckOverrideInputState(const InputState& origInputState);

	// ------------------------------------------------------------------------
	// Class constants;
	// ------------------------------------------------------------------------
	enum
	{
		MAX_NUM_HOT_KEYS = 10,
		MAX_NUM_WINDOWS  = 16
	};

	// ------------------------------------------------------------------------
	// Attributes
	// ------------------------------------------------------------------------

	bool				  mEnabled;

	int					 mMouseX;		//!< Absolute GUI space mouse position;
	int					 mMouseY;		//!< Absolute GUI space mouse position;
	
	typedef Callback::Callback1< void, bool > CallbackSetPause; //!< Typedef for the callback used by the pause button;
	typedef Callback::Callback0< bool >		  CallbackGetPause; //!< Typedef for the callback used by the pause button;
  bool				 mPaused;		//!< True if the system is paused;
  CallbackSetPause	 mSetPauseCallback;		//!< Callback for the pause button;
	CallbackGetPause	 mGetPauseCallback;		//!< Callback for the pause button;

	char				mStatusBarText[ STATUS_BAR_TEXT_LENGTH ];
	Reveal::Color		mStatusBarTextColor;
	bool				mShowStatusBar, mShowMenus, mShowCursor;

	SmartPtr::ScopedPtr< Impl::Window > mWindows[ MAX_NUM_WINDOWS ];
	Impl::Window* mpWindowWithFocus;

	InputState *mOverrideInputState;

	//! Structure that holds information for an auxillary button;
	struct AuxButton
	{
		typedef Callback::Callback1< bool, const InputState& > Callback;
		AuxButton() : mEnabled( false ), mCallback(), mMode( DRAW_NOTHING ) { mName[ 0 ] = '\0'; }
		
		bool	 mEnabled;	   //!< True if the control is currently at the auxillary button;
		Callback mCallback;	   //!< Auxillary callback function;
		AuxilliaryMode mMode;  //!< Mode of the auxilliary callback;
		char	 mName[ AUX_BUTTON_NAME_LENGTH ];	//!< Name of the callback;

	} mAuxButtons[ Impl::GuiMetrics::NUM_AUXILLARY_BUTTONS ];
	int mCurAutoAux;

	//! Structure that holds information for a hotkey;
	struct Hotkey
	{
		//init HotKey input to none
		Hotkey() : mHotKey(0) {}

		Reveal::uint32_t					mHotKey;
		Callback::Callback0< void >			mButtonCallback;
		Callback::Callback1< void, bool >	mCheckBoxSetter;
		Callback::Callback0< bool		  > mCheckBoxGetter;
	} mHotKeys[ MAX_NUM_HOT_KEYS ];	
};

} // Namespace Reveal;

#endif

