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

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

/*! \file */

#include "reveal/impl/tree.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/indexed_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"

namespace Reveal {

#if defined _MSC_VER
#pragma warning( disable : 4355 ) // warning C4355: 'this' : used in base member initializer list (I know what I'm doing);
#endif

	namespace Impl {
	
// ------------------------------------------------------------------------------
Tree::Tree( const char* rootName )
	: mOpenedMru( 0 ),
	  mRoot( rootName, *this, NULL ),
	  mGuiColors(),
	  mWidgetWithFocus( 0 ),
	  mLastWidgetWithFocus( 0 ),
	  mConfirmMenuExpansion(false),
	  mHelpMode(Off)
{
	// The root goes in the first one, and becomes locked;
	mMruMenus[ 0 ].SetMruMenu( &mRoot );
	mMruMenus[ 0 ].Lock();
	mMruMenus[ 0 ].Open( *this );
}

void Tree::RepositionMrus( int separationWidth )
{
	for ( int i = 0; i < Impl::GuiMetrics::NUM_MRUS; ++i ) 
		mMruMenus[ i ].Show( separationWidth * i );
}

// ------------------------------------------------------------------------------
void Tree::SetOpenedMru( Impl::MruMenuForward* mruMenu )
{
	#if defined REVEAL
		if ( mOpenedMru && (mruMenu != mOpenedMru) ) mOpenedMru->Close();
		mOpenedMru = mruMenu;
	#endif // defined REVEAL
}

// ------------------------------------------------------------------------------
void Tree::AddCheckBoxControl( const char* path, const char* name, const Callback::Callback1< void, bool >& callbackToGame, const Callback::Callback0< bool >& callbackFromGame )
{
	#if defined REVEAL
		if ( IsRevealGloballyEnabled() )
		{
			Impl::Widget* control = new Impl::CheckBoxControl( name, *this, callbackToGame, callbackFromGame );
			mRoot.AddControl( path, control );
		}
	#endif // defined REVEAL
}

// ------------------------------------------------------------------------------
void Tree::AddIndexedCheckBoxControl( const char* path, const char* name, uint32_t index, const Callback::Callback2< void, uint32_t, bool >& callbackToGame, const Callback::Callback1< bool, uint32_t >& callbackFromGame )
{
#if defined REVEAL
	if ( IsRevealGloballyEnabled() )
	{
		Impl::Widget* control = new Impl::IndexedCheckBoxControl( name, *this, index, callbackToGame, callbackFromGame );
		mRoot.AddControl( path, control );
	}		
#endif // defined REVEAL
}

// ------------------------------------------------------------------------------
void Tree::AddButtonControl( const char* path, const char* name, const Callback::Callback0< void >& callback )
{
	#if defined REVEAL	
		if ( IsRevealGloballyEnabled() )
		{
			Impl::Widget* control = new Impl::ButtonControl( name, *this, callback );
			mRoot.AddControl( path, control );
		}
	#endif // defined REVEAL
}

// ------------------------------------------------------------------------------
void Tree::AddColorControl( const char* path, const char* name, const Callback::Callback1< void, const Color& >& callbackFromGame, const Callback::Callback0< Color >& callbackToGame )
{
	#if defined REVEAL
		if ( IsRevealGloballyEnabled() )
		{
			Impl::Widget* control = new Impl::ColorControl( name, *this, callbackFromGame, callbackToGame );
			mRoot.AddControl( path, control );
		}
	#endif // defined REVEAL
}

// ------------------------------------------------------------------------------
void Tree::RenderMrus( const Renderer& renderer ) const
{
	for ( int i = 0; i < Impl::GuiMetrics::NUM_MRUS; ++i )
		mMruMenus[ i ].Render( renderer, mGuiColors, &mMruMenus[ i ] == mOpenedMru ? Impl::MruMenuForward::DRAW_CHILDREN : Impl::MruMenuForward::DONT_DRAW_CHILDREN );

	//Only draw the help if the widget's had focus for a while
	if (mHelpTimer >= HelpWait)
	{
		RenderHelpWindow(renderer);
	}
}

// ------------------------------------------------------------------------------
void Tree::SetMruMenu( Impl::Menu& menu )
{
	Impl::MruMenuForward* swappedOut = 0;
	for ( int i = 0; i < Impl::GuiMetrics::NUM_MRUS; ++i )
	{
		if ( mMruMenus[ i ].ForwardsTo( &menu ) ) return;
		if ( ! mMruMenus[ i ].IsLocked() )
			if ( mOpenedMru != &mMruMenus[ i ] )
				if ( !swappedOut || mMruMenus[ i ].IsLessRecentlyUsedThan( *swappedOut ) )
				{
					swappedOut = &mMruMenus[ i ];
				}
	}
	if ( swappedOut ) swappedOut->SetMruMenu( &menu );
}

// ------------------------------------------------------------------------------
void Tree::Remove( const char* path, const char* name )
{
	assert( path );

	const size_t pathLength = strlen(path);
	const size_t pLength = pathLength >= Impl::Widget::MAX_PATH_STRING_LENGTH ? Impl::Widget::MAX_PATH_STRING_LENGTH - 1 : pathLength;

	char p[ Impl::Widget::MAX_PATH_STRING_LENGTH ];
	strncpy( p, path, pLength );
	p[ pLength ] = 0;
	for ( size_t i = 0; i < pLength; ++i ) 
	{
		p[ i ] = static_cast< char >( tolower( static_cast< unsigned char >( path[ i ] ) ) );
	}

	if ( name )
	{
		const size_t nameLength = strlen(name);
		const size_t nLength = nameLength >= Impl::Widget::MAX_PATH_STRING_LENGTH ? Impl::Widget::MAX_PATH_STRING_LENGTH - 1 : nameLength;

		char n[ Impl::Widget::MAX_NAME_STRING_LENGTH ];
		strncpy( n, name, nLength );
		n[ nLength ] = 0;
		for ( size_t i = 0; i < nLength; ++i ) 
		{
			n[ i ] = static_cast< char >( tolower( static_cast< unsigned char >( name[ i ] ) ) );
		}
		mRoot.Remove( p, n );
	}	
	else
	{
		mRoot.Remove( p, 0 );
	}	
}

// ------------------------------------------------------------------------------
void Tree::RemoveFromMrus( Impl::Menu& menu )
{
	for ( int i = 0; i < Impl::GuiMetrics::NUM_MRUS; ++i )
	{
		if ( mMruMenus[ i ].ForwardsTo( &menu ) )
		{
			mMruMenus[ i ].SetMruMenu( NULL );
		}
	}
}

// ------------------------------------------------------------------------------
void Tree::NextMruMenu()
{
	for ( int i = 0; i < Impl::GuiMetrics::NUM_MRUS; ++i )
	{
		if ( mOpenedMru == &mMruMenus[ i ] )
		{
			for ( size_t j = i + 1; ; ++j )
			{
				if ( ! mMruMenus[ j % Impl::GuiMetrics::NUM_MRUS ].Empty() )
				{
					mMruMenus[ j % Impl::GuiMetrics::NUM_MRUS ].Open( *this );
					return;
				}
			}
		}
	}
}

// ------------------------------------------------------------------------------
void Tree::PrevMruMenu()
{
	for ( int i = 0; i < Impl::GuiMetrics::NUM_MRUS; ++i )
	{
		if ( mOpenedMru == &mMruMenus[ i ] )
		{
			for ( size_t j = (i == 0 ? Impl::GuiMetrics::NUM_MRUS - 1 : i - 1); ; --j )
			{
				if ( ! mMruMenus[ j ].Empty() )
				{
					mMruMenus[ j ].Open( *this );
					return;
				}
				if ( j == 0 ) j = Impl::GuiMetrics::NUM_MRUS - 1;
			}
		}
	}
}

// ------------------------------------------------------------------------------
void Tree::SetWidgetWithFocus( Impl::Widget* w )
{
	if ( mWidgetWithFocus != w )
	{
		if ( w == NULL ) mLastWidgetWithFocus = mWidgetWithFocus;
		if ( mWidgetWithFocus ) mWidgetWithFocus->LoseFocus();
		mWidgetWithFocus = w;
		if ( mWidgetWithFocus ) mWidgetWithFocus->GiveFocus();

		mHelpTimer = 0;
	}
}

// ------------------------------------------------------------------------------
bool Tree::LetMrusHandle( const InputState& inputState )
{
	bool inputHandled = false;

	// First try to let the open mru handle it;
	if ( mOpenedMru )
	{
		inputHandled = mOpenedMru->Handle( inputState, *this );
	}

	// If it's not handled yet, let the other menu's try to handle it;
	if ( !inputHandled )
	{
		for ( int i = 0; i < Impl::GuiMetrics::NUM_MRUS; ++i )
		{			
			if ( mOpenedMru != &mMruMenus[ i ] )
			{
				inputHandled = mMruMenus[ i ].Handle( inputState, *this );
				if ( inputHandled ) break;
			}
		}
	}

	// If no one handling input and down button pressed, open up the last used menu, if valid, or the start menu, if not.
	if ( !inputHandled )
	{
		if( inputState.Released(InputState::DOWN | InputState::UP | InputState::LEFT | InputState::RIGHT) ) 
		{
			if( mLastWidgetWithFocus != NULL && mLastWidgetWithFocus->IsVisible() ) SetWidgetWithFocus( mLastWidgetWithFocus );
			else mMruMenus[0].Open( *this );
		}
	}

	mHelpTimer++;

	return inputHandled;
}

// ------------------------------------------------------------------------------
void Tree::ShowMRU()
{
	for ( int i = 0; i < Impl::GuiMetrics::NUM_MRUS; ++i )
		mMruMenus[ i ].Show( Impl::GuiMetrics::GetWidgetWidth() * i );
}

// ------------------------------------------------------------------------------
void Tree::ShowRoot( int x, int y )
{
	mRoot.Show( x, y );
	mRoot.GiveFocus();
}

// ------------------------------------------------------------------------------
void Tree::RenderHelpWindow ( const Renderer& renderer ) const
{
	if ((! mWidgetWithFocus) || (! mWidgetWithFocus->HasHelp()) || (Off == mHelpMode))
		return;

	enum { FadeFrames = 10 };

	// Local function definition;
	struct CharacterRangeRSearch
	{
		const char* operator () ( const char* str, size_t len, char e )
		{
			const char* end = str + len - 1;
			while ( end >= str )
			{
				if (*end == e)
					return end;

				end--;
			}

			return NULL;
		}
	} StrRSearch;

	//Color fade in
	Reveal::uint32_t textColor = mGuiColors.mHelpWindowText, windowColor = mGuiColors.mHelpWindowColor;

	if (mHelpTimer < HelpWait + FadeFrames)
	{
		Reveal::uint32_t endTextAlpha = mGuiColors.mHelpWindowText >> 24;
		Reveal::uint32_t endWindowAlpha = mGuiColors.mHelpWindowColor >> 24;
		unsigned int elapsed = mHelpTimer - HelpWait;
		
		textColor = (mGuiColors.mHelpWindowText & 0x00FFFFFF) | (((endTextAlpha * elapsed) / FadeFrames) << 24);
		windowColor = (mGuiColors.mHelpWindowColor & 0x00FFFFFF) | (((endWindowAlpha * elapsed) / FadeFrames) << 24);
	}

	const static size_t numCharsAcross = 28; //Good number, don't ask
	const static size_t maxNumLines = 16;
	
	//Get the height of the window by counting how many lines of help we have
	int numLines = 0;
	const char* currentLine = mWidgetWithFocus->GetHelp();
	const char* nextLine = NULL;
	size_t lineLength = 0, longestLine = 0;

	while (currentLine)
	{
		numLines++;
		nextLine = strchr(currentLine, '\n');
		if (nextLine)
		{
			lineLength = nextLine - currentLine;

			nextLine++;
		}
		else
			lineLength = strlen(currentLine);

		if (lineLength >= numCharsAcross)
		{
			//Find the first "break" and split to a new line
			const char* split = StrRSearch(currentLine, numCharsAcross, ' ');
			if (split)
				nextLine = split + 1;

			longestLine = numCharsAcross;
		}
		else if (lineLength > longestLine)
			longestLine = lineLength;

		currentLine = nextLine;
	}
	
	int helpWindowWidth = (int)numCharsAcross * Impl::GuiMetrics::GetCharacterWidth();
	int helpWindowHeight = numLines * (Impl::GuiMetrics::TEXT_HEIGHT + 2) + 30;	//Adjusts to how much help there is

	int posX = 0;
	int posY = 0;

	if (NextToWidget == mHelpMode)
	{
		posX = mWidgetWithFocus->X() + mWidgetWithFocus->Width();
		posY = mWidgetWithFocus->Y() + mWidgetWithFocus->Height();

		//Default help at bottom-right of widget, but adjust
		//if it's going to be outside of the screen
		if ((posX + helpWindowWidth) >= Impl::GuiMetrics::SCREEN_WIDTH)
		{
			int newPos = mWidgetWithFocus->X() - helpWindowWidth;
			if (newPos > 0)	//Only use it if we're better off
				posX = newPos;
		}

		if ((posY + helpWindowHeight) >= Impl::GuiMetrics::SCREEN_HEIGHT)
		{
			int newPos = mWidgetWithFocus->Y() - helpWindowHeight;
			if (newPos > 0)	//Only use it if we're better off
				posY = newPos;
		}
	}
	else	//BottomOfScreen
	{
		posX = 0;
		posY = Impl::GuiMetrics::SCREEN_HEIGHT - helpWindowHeight - 50;
	}

	renderer.Bar( posX, posY, helpWindowWidth, helpWindowHeight, windowColor );

	currentLine = mWidgetWithFocus->GetHelp();
	nextLine = NULL;
	int curPosY = posY + 15;
	char buffer[numCharsAcross];
	numLines = 0;
	while (currentLine && (numLines < maxNumLines))
	{
		numLines++;
		nextLine = strchr(currentLine, '\n');

		if (nextLine)
		{
			lineLength = nextLine - currentLine;

			nextLine++;
		}
		else
			lineLength = strlen(currentLine);

		if (lineLength >= numCharsAcross)
		{
			//Find the first "break" and split to a new line
			//If we don't find one, simply clip the line
			const char* split = StrRSearch(currentLine, numCharsAcross, ' ');
			if (split)
			{
				lineLength = split - currentLine;

				nextLine = split + 1;
			}
			else
				lineLength = numCharsAcross - 1;
		}

		//Only print up to the \n character or till the line is split
		strncpy(buffer, currentLine, lineLength);
		buffer[lineLength] = '\0';

		renderer.String(posX + 15, curPosY, buffer, textColor);

		currentLine = nextLine;
		curPosY += Impl::GuiMetrics::TEXT_HEIGHT;
	}
}

// ------------------------------------------------------------------------------
void Tree::RenderRoot( const Renderer& renderer ) const
{
	mRoot.Render( renderer, mGuiColors );

	//Only draw the help if the widget's had focus for a while
	if (mHelpTimer >= HelpWait)
	{
		RenderHelpWindow(renderer);
	}
}

void Tree::AddHelp ( const char* path, const char* help )
{
	Impl::Widget* widget = mRoot.GetWidget(path);

	if (widget)
		widget->SetHelp(help);
}

void Tree::SetHelpMode ( HelpMode m )
{ mHelpMode = m; }

void Tree::Update()
{
	mRoot.Update();
}

	} // Namespace Impl;
} // Namespace Reveal;
