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

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

/*! \file */

#include "reveal/tweaker.h"
#include "reveal/input_state.h"
#include "reveal/renderer.h"

#include "reveal/callback/assign_to_ref_adapter.h"
#include "reveal/callback/return_ref_adapter.h"
#include "reveal/callback/mem_fun_adapter.h"

#include "reveal/impl/check_box_control.h"
#include "reveal/impl/button_control.h"
#include "reveal/impl/color_control.h"
#include "reveal/impl/util.h"

#include "reveal/custom_memory.h"
#include "reveal/impl/widget.h"

#include "reveal/global_enable.h"

namespace Reveal {

	// \todo: explain
	namespace Callback {
		namespace Memory {
			void* CallbackMalloc( size_t size ) { return ::Reveal::Memory::RevealMalloc( size ); }
			void CallbackFree( void* ptr ) { ::Reveal::Memory::RevealFree( ptr ); }
		}
	}
	
// ------------------------------------------------------------------------------
Tweaker::Tweaker()
	: Tree( "start" ),
	  mEnabled( false ),
	  mMouseX( 0 ),
	  mMouseY( 0 ),
	  mPaused( false ),	
      mSetPauseCallback(),
	  mGetPauseCallback(),
	  mStatusBarTextColor( GetGuiColors().mTextColor ),
	  mShowStatusBar( false ),
	  mShowMenus( true ),
	  mShowCursor( true ),
	  mWindows(),
		mOverrideInputState(NULL),
	  mAuxButtons(),
	  mCurAutoAux()
{
	strncpy( mStatusBarText, "Welcome to the Reveal Tweaker System", STATUS_BAR_TEXT_LENGTH );
	mStatusBarText[ STATUS_BAR_TEXT_LENGTH - 1 ] = 0;
}

// ------------------------------------------------------------------------------
void Tweaker::Handle( const InputState& theInputState )
{
	if ( ! IsRevealGloballyEnabled() ) return; // Global enable;
	if ( ! mEnabled ) return;				   // Local enable;
	
	#if defined REVEAL
	
		//Check for an override input state
		const InputState& inputState = CheckOverrideInputState(theInputState);
		
		// Fade the status bar text;
		const float fadeDelta = 0.02f; mStatusBarTextColor.A( mStatusBarTextColor.A() - fadeDelta ); if ( mStatusBarTextColor.A() < 0.0f ) mStatusBarTextColor.A( 0.0f );

		// Store the mouse position;
		mMouseX = inputState.MouseX();
		mMouseY = inputState.MouseY();
		
		// Get the current pause state;
		if ( mGetPauseCallback.Valid() ) mPaused = mGetPauseCallback();
	
		// Then try if maybe an auxillary button handles it;
		for ( size_t i = 0; i < Impl::GuiMetrics::NUM_AUXILLARY_BUTTONS; ++i )
		{
			if ( mAuxButtons[ i ].mEnabled )
			{
				if ( mAuxButtons[ i ].mCallback.Valid() ) mAuxButtons[ i ].mEnabled = mAuxButtons[ i ].mCallback( inputState );
				else									  mAuxButtons[ i ].mEnabled = false;
				
				if ( mAuxButtons[ i ].mEnabled ) return;
			}
		}

		// Then try if maybe a hotkey is pressed in combination with the select button;
		if ( inputState.Down( InputState::SELECT ) )
		{
			for ( size_t i = 0; i < MAX_NUM_HOT_KEYS; ++i )
				if ( inputState.Released( mHotKeys[ i ].mHotKey ) )
					if ( mHotKeys[ i ].mButtonCallback.Valid() )
						mHotKeys[ i ].mButtonCallback();
					else
						mHotKeys[ i ].mCheckBoxSetter( !mHotKeys[ i ].mCheckBoxGetter() );			
			return;
		}
		
		// If we're still here, lets try to handle it ourselves;
		{
			//See if window w/ focus handles it
			bool inputHandled = mpWindowWithFocus && ( GetWidgetWithFocus() == mpWindowWithFocus )  && mpWindowWithFocus->Handle( inputState );

			// Let the MRU's or the children try to handle it;
			if ( ! inputHandled )
				inputHandled = LetMrusHandle( inputState );

			//See if a window is being brought into focus
			if ( ! inputHandled )
				inputHandled = LetWindowsHandle( theInputState );

			// If it's not handled, we'll let a possible status bar handle it;
			if ( !inputHandled && mShowStatusBar )
			{
				if ( inputState.Pressed( InputState::LEFT_MOUSE | InputState::CONFIRM )  )
				{
					const bool mouseOverPauseButton = Util::PointInRect( inputState.MouseX(), inputState.MouseY(),
																		Impl::GuiMetrics::PAUSE_BUTTON_X, Impl::GuiMetrics::PAUSE_BUTTON_Y,
																		Impl::GuiMetrics::PAUSE_ITEM_SIZE, Impl::GuiMetrics::PAUSE_ITEM_SIZE );			
					if ( mouseOverPauseButton )
					{
						if ( mSetPauseCallback.Valid() )
						{
							CallPauseCallback();
						}
					}
					else
					{
						int mouseOverAuxButton = MouseOverAuxillaryButton();
						if ( mouseOverAuxButton != -1 )
						{
							StartAuxCallback( mAuxButtons[ mouseOverAuxButton ].mName );
						}	
					}
				}
			}
		}
	#endif // defined REVEAL
}

// ------------------------------------------------------------------------------
// Used by the SetAuxCallback function;
// ------------------------------------------------------------------------------
struct StartAuxCallbackFunctor
{
	StartAuxCallbackFunctor( const char* name, Tweaker& t ) : mName( name ), mTweaker( t ) {}
	void operator () () { mTweaker.StartAuxCallback( mName ); }
    const char* mName;
	Tweaker& mTweaker;
};

// ------------------------------------------------------------------------------
void Tweaker::SetAuxCallback( size_t idx, const char* name, const Callback::Callback1< bool, const InputState& >& callback, AuxilliaryMode mode )
{
	#if defined REVEAL
		if ( IsRevealGloballyEnabled() )
		{
			assert( idx < Impl::GuiMetrics::NUM_AUXILLARY_BUTTONS );
			assert( name );
			mAuxButtons[ idx ].mEnabled = false;
			mAuxButtons[ idx ].mCallback = callback;
			mAuxButtons[ idx ].mMode = mode;
			strncpy( mAuxButtons[ idx ].mName, name, AUX_BUTTON_NAME_LENGTH );
			mAuxButtons[ idx ].mName[ AUX_BUTTON_NAME_LENGTH - 1 ] = 0;
			AddButtonControl( "aux", mAuxButtons[ idx ].mName, Callback::Callback0< void >( StartAuxCallbackFunctor( mAuxButtons[ idx ].mName, *this ) ) );
		}
	#endif // defined REVEAL
}

// ------------------------------------------------------------------------------
void Tweaker::AddAuxCallback( const char* name, const Callback::Callback1< bool, const InputState& >& callback, AuxilliaryMode mode )
{
#if defined REVEAL
		size_t insertPos = mCurAutoAux;
		bool bWasPresent = false;
		//Remove any old instances of same aux first
		for ( size_t i = 0; i < Impl::GuiMetrics::NUM_AUXILLARY_BUTTONS; ++i )
		{
            const char *tmp = strstr( name, mAuxButtons[ i ].mName );

            if ( (tmp) && ( !strcmp( tmp, mAuxButtons[ i ].mName ) ) )
			{
                AuxButton::Callback emptyCB;

                Remove( "aux", mAuxButtons[ i ].mName );
				mAuxButtons[ i ].mName[ 0 ] = 0;
                mAuxButtons[ i ].mCallback = emptyCB;
				bWasPresent = true;
				insertPos = i;
				break;
			}
		}

		SetAuxCallback( insertPos, name, callback, mode);

		//Only increment if it wasn't there already
		if (! bWasPresent)
			mCurAutoAux++;

		//Loop around when we run out
		if (mCurAutoAux >= Impl::GuiMetrics::NUM_AUXILLARY_BUTTONS)
			mCurAutoAux = 0;
#endif
}

// ------------------------------------------------------------------------------
bool Tweaker::RemoveAuxCallback( const char* name )
{
	#if defined REVEAL
		for ( size_t i = 0; i < Impl::GuiMetrics::NUM_AUXILLARY_BUTTONS; ++i )
		{
			if ( strstr( mAuxButtons[ i ].mName, name ) == mAuxButtons[ i ].mName )
			{
				Remove( "aux", mAuxButtons[ i ].mName );
				mAuxButtons[ i ].mName[ 0 ] = 0;
				return true;
			}
		}		
	#endif // defined REVEAL
	return false;
}

// ------------------------------------------------------------------------------
void Tweaker::Render( const Renderer& renderer ) const
{
	if ( ! IsRevealGloballyEnabled() ) return; // Global enable;
	if ( ! mEnabled ) return;				   // Local enable;


	#if defined REVEAL

		// If an auxillary buttons is enabled, we don't render;
		for ( size_t i = 0; i < Impl::GuiMetrics::NUM_AUXILLARY_BUTTONS; ++i )
			if ( mAuxButtons[ i ].mEnabled )
			{
				if ( mAuxButtons[ i ].mMode == DRAW_MOUSE )
					RenderMouseCursor( renderer );
				else
				if ( mAuxButtons[ i ].mMode == DRAW_WINDOWS_AND_MOUSE )
				{
					RenderWindows( renderer );
					RenderMouseCursor( renderer );
				}

				return;
			}
		
		const Reveal::uint32_t barColor   = GetGuiColors().mStatusBarColor;
	
		if ( mShowStatusBar )
		{
			renderer.Bar( 0, Impl::GuiMetrics::SCREEN_HEIGHT - Impl::GuiMetrics::BAR_HEIGHT, Impl::GuiMetrics::SCREEN_WIDTH, Impl::GuiMetrics::BAR_HEIGHT, barColor );
			renderer.Line( 0, Impl::GuiMetrics::SCREEN_HEIGHT - Impl::GuiMetrics::BAR_HEIGHT, Impl::GuiMetrics::SCREEN_WIDTH, Impl::GuiMetrics::SCREEN_HEIGHT - Impl::GuiMetrics::BAR_HEIGHT, GetGuiColors().mBorderColor );
			renderer.Line( 0, Impl::GuiMetrics::BAR_HEIGHT, Impl::GuiMetrics::SCREEN_WIDTH, Impl::GuiMetrics::BAR_HEIGHT, GetGuiColors().mBorderColor );
			renderer.String( Impl::GuiMetrics::TEXT_BORDER_WIDTH, Impl::GuiMetrics::SCREEN_HEIGHT - Impl::GuiMetrics::GetRowHeight(), mStatusBarText, mStatusBarTextColor.Get32BitARGB() ); 	
			RenderPauseButton( renderer );	
			RenderAuxillaryButtons( renderer );
		}

		RenderWindows( renderer );

		if ( mShowMenus )
			RenderMrus( renderer );	

		if ( mShowCursor )
		 RenderMouseCursor( renderer );

	#endif // defined REVEAL
}

// ------------------------------------------------------------------------------
void Tweaker::RenderAuxillaryButtons( const Renderer& renderer ) const
{
	int x = Impl::GuiMetrics::AUX_BUTTON_RIGHT_MOST_X;
	for ( size_t i = 0; i < Impl::GuiMetrics::NUM_AUXILLARY_BUTTONS; ++i )
	{
		if ( mAuxButtons[ i ].mName[ 0 ] != 0 )
		{
			renderer.Bar( x, Impl::GuiMetrics::AUX_BUTTON_Y, Impl::GuiMetrics::AUX_BUTTON_WIDTH,
						  Impl::GuiMetrics::AUX_BUTTON_HEIGHT, GetGuiColors().mInteractiveBackgroundColor );
			renderer.String( x + Impl::GuiMetrics::TEXT_BORDER_WIDTH, Impl::GuiMetrics::AUX_BUTTON_Y + Impl::GuiMetrics::TEXT_BORDER_HEIGHT,
							 mAuxButtons[ i ].mName, GetGuiColors().mInteractiveForegroundColor );
		}
		x -= Impl::GuiMetrics::AUX_BUTTON_WIDTH + Impl::GuiMetrics::AUX_BUTTON_SPACING;
	}
}

// ------------------------------------------------------------------------------
void Tweaker::RenderPauseButton( const Renderer& renderer ) const
{
	renderer.Bar( Impl::GuiMetrics::PAUSE_BUTTON_X, Impl::GuiMetrics::PAUSE_BUTTON_Y,
                  Impl::GuiMetrics::PAUSE_ITEM_SIZE, Impl::GuiMetrics::PAUSE_ITEM_SIZE, GetGuiColors().mInteractiveBackgroundColor );

	int border = Impl::GuiMetrics::PAUSE_ITEM_SIZE / 5;
	
	if ( ! mPaused )
	{
		renderer.Bar( Impl::GuiMetrics::PAUSE_BUTTON_X + border, Impl::GuiMetrics::PAUSE_BUTTON_Y + border,
					  border, Impl::GuiMetrics::PAUSE_ITEM_SIZE - 2 * border, GetGuiColors().mInteractiveForegroundColor );
		renderer.Bar( Impl::GuiMetrics::PAUSE_BUTTON_X + 3 * border, Impl::GuiMetrics::PAUSE_BUTTON_Y + border,
					  border, Impl::GuiMetrics::PAUSE_ITEM_SIZE - 2 * border, GetGuiColors().mInteractiveForegroundColor );
	}
	else
	{
		renderer.Triangle( Impl::GuiMetrics::PAUSE_BUTTON_X	+ border, Impl::GuiMetrics::PAUSE_BUTTON_Y + border,
						   Impl::GuiMetrics::PAUSE_BUTTON_X	+ border, Impl::GuiMetrics::PAUSE_BUTTON_Y + Impl::GuiMetrics::PAUSE_ITEM_SIZE - border,
						   Impl::GuiMetrics::PAUSE_BUTTON_X + Impl::GuiMetrics::PAUSE_ITEM_SIZE - border, Impl::GuiMetrics::PAUSE_BUTTON_Y + Impl::GuiMetrics::PAUSE_ITEM_SIZE/2,
						   GetGuiColors().mInteractiveForegroundColor );
	}
}

// ------------------------------------------------------------------------------
int Tweaker::MouseOverAuxillaryButton() const
{
	for ( int i = 0; i < static_cast< int >( Impl::GuiMetrics::NUM_AUXILLARY_BUTTONS ); ++i )
	{
		if ( mAuxButtons[ i ].mName[ 0 ] != 0 )
		{
			if ( Util::PointInRect( mMouseX, mMouseY,
									Impl::GuiMetrics::AUX_BUTTON_RIGHT_MOST_X - i * (Impl::GuiMetrics::AUX_BUTTON_SPACING + Impl::GuiMetrics::AUX_BUTTON_WIDTH),
									Impl::GuiMetrics::AUX_BUTTON_Y, Impl::GuiMetrics::AUX_BUTTON_WIDTH, Impl::GuiMetrics::AUX_BUTTON_HEIGHT ) )
			{
				return i;
			}
		}
	}	
	
	return -1;
}

// ------------------------------------------------------------------------------
void Tweaker::SetStatusBarMessage( const char* t )
{
	strncpy( mStatusBarText, t, STATUS_BAR_TEXT_LENGTH );
	mStatusBarText[ STATUS_BAR_TEXT_LENGTH - 1 ] = 0;
	mStatusBarTextColor.A( 1.0f );
}

// ------------------------------------------------------------------------------
bool Tweaker::DebugMode() const
{
	#ifdef REVEAL
		return IsRevealGloballyEnabled();
	#else
		return false;
	#endif
}

// ------------------------------------------------------------------------------
void Tweaker::StartAuxCallback( const char* name )
{
	size_t i;

	// Disable a possible other running callback;
	for ( i = 0; i < Impl::GuiMetrics::NUM_AUXILLARY_BUTTONS; ++i ) mAuxButtons[ i ].mEnabled = false;

	for ( i = 0; i < Impl::GuiMetrics::NUM_AUXILLARY_BUTTONS; ++i )
		if ( strstr( mAuxButtons[ i ].mName, name ) == mAuxButtons[ i ].mName )
			mAuxButtons[ i ].mEnabled = true;
}

// -------------------------------------------
void Tweaker::CallPauseCallback()
{
	if ( mGetPauseCallback.Valid() ) mPaused = mGetPauseCallback();
	mPaused = !mPaused;
	mSetPauseCallback( mPaused );
	if ( mGetPauseCallback.Valid() ) mPaused = mGetPauseCallback();
}

// -------------------------------------------
void Tweaker::SetPauseCallback( const Callback::Callback1< void, bool >& setPause, const Callback::Callback0< bool >& getPause )
{
	mSetPauseCallback = setPause;
	mGetPauseCallback = getPause;
	AddButtonControl( "", "pause", Callback::MemFunAdapter0( *this, &Tweaker::CallPauseCallback ) );
}

// -----------------------------------------------------------------------
void Tweaker::AddButtonHotKey( Reveal::uint32_t hotKey, const Callback::Callback0< void >& mCallback )
{
	for ( size_t i = 0; i < MAX_NUM_HOT_KEYS; ++i )
	{
		if ( ! mHotKeys[ i ].mHotKey )
		{
			mHotKeys[ i ].mHotKey = hotKey;
			mHotKeys[ i ].mButtonCallback = mCallback;
			return;
		}
	}
}

// -----------------------------------------------------------------------
void Tweaker::AddButtonHotKey( Reveal::uint32_t hotKey, const Callback::Callback1< void, bool >& mSetter, const Callback::Callback0< bool >& mGetter )
{
	for ( size_t i = 0; i < MAX_NUM_HOT_KEYS; ++i )
	{
		if ( ! mHotKeys[ i ].mHotKey )
		{
			mHotKeys[ i ].mHotKey = hotKey;
			mHotKeys[ i ].mCheckBoxSetter = mSetter;
			mHotKeys[ i ].mCheckBoxGetter = mGetter;
			return;
		}
	}
}

// -----------------------------------------------------------------------
void Tweaker::RenderMouseCursor( const Renderer& renderer ) const
{
	const int mouseCorner1X = 16;
	const int mouseCorner1Y = 32;
	const int mouseCorner2X = 24;
	const int mouseCorner2Y = 24;
	renderer.Triangle( mMouseX,				    mMouseY,
						mMouseX + mouseCorner1X, mMouseY + mouseCorner1Y,
						mMouseX + mouseCorner2X, mMouseY + mouseCorner2Y, GetGuiColors().mCursorColor );	
}

bool Tweaker::LetWindowsHandle ( const InputState& inputState )
{
	//If the window w/ focus didn't handle it, see if one w/o focus will
	for ( size_t i = 0; i < MAX_NUM_WINDOWS; ++i )
	{
		Impl::Window* pWindow = mWindows[ i ].Get();
			if ( ( pWindow != 0 ) && (pWindow->IsVisible()) && pWindow->GetFocus( inputState ) )
			{
				mpWindowWithFocus = pWindow;
				SetWidgetWithFocus(mpWindowWithFocus);
				SetOpenedMru(NULL);
				mpWindowWithFocus->Handle( inputState );

				return true;
			}
	}
	
	return false;
}

// -----------------------------------------------------------------------
bool Tweaker::WindowSystem( const InputState& theInputState )
{
	//Check for an override input state
	const InputState& inputState = CheckOverrideInputState(theInputState);

	if ( inputState.Released( InputState::BACK ) )
		return false;
	else
	{
		//See if window w/ focus handles it
		bool inputHandled = mpWindowWithFocus && ( GetWidgetWithFocus() == mpWindowWithFocus )  && mpWindowWithFocus->Handle( inputState );

		if (! inputHandled )
			LetWindowsHandle( theInputState );

		return true;
	}
}

// -----------------------------------------------------------------------
void Tweaker::RenderWindows( const Renderer& renderer ) const
{
	//Draw the window that has focus on top of all others
	for ( size_t i = 0; i < MAX_NUM_WINDOWS; ++i )
			if ( ( mWindows[ i ].Get() != 0 ) && ( mWindows[ i ].Get() != mpWindowWithFocus ) )
				mWindows[ i ]->Render( renderer, GetGuiColors() );
	
	if (mpWindowWithFocus)
		mpWindowWithFocus->Render( renderer, GetGuiColors() );
}

// -----------------------------------------------------------------------
const InputState& Tweaker::CheckOverrideInputState(const InputState& origInputState)
{
	//is there an override inputState?
	if (mOverrideInputState)
	{
		return (*mOverrideInputState);
	}

	//return orig inputstate
	return (origInputState);
}

void Tweaker::RemoveWindow (Impl::Window& window)
{
	for ( size_t i = 0; i < MAX_NUM_WINDOWS; ++i )
	{
		if ( mWindows[ i ].Get() == &window )
		{
			mWindows[ i ].Reset();
			if ( &window == mpWindowWithFocus )	//Need a new window to have focus
			{
				mpWindowWithFocus = NULL;
				for ( size_t i = 0; i < MAX_NUM_WINDOWS; ++i )
				{
					if ( mWindows[ i ].Get() != NULL )
						mpWindowWithFocus = mWindows[ i ].Get();
				}
			}
		}
	}
}

void Tweaker::CascadeWindows ()
{
	Impl::Window* visibleWindows[MAX_NUM_WINDOWS] = {0};
	int numWindows = 0;

	//2 columns
	enum { NUM_COLUMNS=2 };

	//Find out how many visible windows we have
	for ( size_t i = 0; i < MAX_NUM_WINDOWS; ++i )
	{
		Impl::Window* pWindow = mWindows[ i ].Get();
		if ( ( pWindow != 0 ) && ( pWindow->IsVisible() ) )
			visibleWindows[numWindows++] = pWindow;
	}

	int curX = 0, curY = 0;
	
	//If only 1 window, cascade = maximize
	int width = Impl::GuiMetrics::SCREEN_WIDTH;
	int height = Impl::GuiMetrics::SCREEN_HEIGHT;

	//- bar at top
	if (mShowMenus)
	{
		height -= Impl::GuiMetrics::BAR_HEIGHT;
		curY = Impl::GuiMetrics::BAR_HEIGHT;
	}

	//- bar at the bottom
	if (mShowStatusBar)
		height -= Impl::GuiMetrics::BAR_HEIGHT;

	if (numWindows > 1)
	{
		int roundedToColumns = ((numWindows - 1) / NUM_COLUMNS) + 1;
		width /= NUM_COLUMNS;
		height /= roundedToColumns;
	}

	//Now cascade them
	for ( int i = 0; i < numWindows; ++i )
	{
		visibleWindows[i]->Show( curX, curY );
		visibleWindows[i]->SetSize( width, height );
		
		curX += width;
		//If next window won't fit, go to next row
		if ( ( curX + width ) > Impl::GuiMetrics::SCREEN_WIDTH )
		{
			curX = 0;
			curY += height;
		}
	}
}

} // Namespace Reveal;
