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

/*! \file */

#ifndef REVEAL_IMPL_WINDOW_H
#define REVEAL_IMPL_WINDOW_H

#include <stddef.h>
#include "reveal/impl/widget.h"
#include "reveal/impl/gui_metrics.h"

#ifdef __EA_GCCXML
#define assert      // allows the cpp-xml parser to parse this header without issue
#endif

namespace Reveal {

	namespace Impl {

// ------------------------------------------------------------------------------
/*!
	\brief Todo
*/
// ------------------------------------------------------------------------------
class Window : public Widget
{
public:

	Window( const char* name, Tree& tree )
		: Widget( name, tree ),
		  // Some magically created default settings for the 
		  // window-dimensions that kinda look okay;
		  mWidth( GuiMetrics::SCREEN_WIDTH / 2 ),
		  mHeight( GuiMetrics::SCREEN_HEIGHT / 2 ),
		  mState( IDLE )
	{
		SetX( 200 );
		SetY( 200 );
	}

	virtual int Width()  const { return mWidth; }
	virtual int Height() const { return mHeight; }
	
	//! Shows the window at a default location;
	void Popup()
	{
		Widget::Show( X(), Y() );
	}

	//! Resizes the window
	void SetSize (int newWidth, int newHeight)
	{
		mWidth = newWidth;
		mHeight = newHeight;
	}

	/*!
		\brief Windows behave differently than widgets, so we must do a force override;

		\return true if the widget handled the input, false otherwise
		\note Calls the virtual \a DoHandle which must be implemented by derived clases.
	*/
	Widget* Handle( const InputState& inputState )
	{
		return DoHandle ( inputState );
	}

	//! Custom new, using the allocator;
	void* operator new( size_t size )
	{
		for( ;; )
		{
			void* p = Memory::RevealMalloc( size );			
			if ( p ) 
			{
				return p;
			}
			else
			{
				#ifdef REVEAL_EXCEPTIONS
					std::new_handler globalHandler = std::set_new_handler( NULL );
					std::set_new_handler( globalHandler );
					if (globalHandler) (*globalHandler)();
					else throw std::bad_alloc();
				#else
					assert( !"Out of memory, no exception handler enabled, fatal error." );
				#endif
			}
		}
	}

	virtual bool GetFocus ( const InputState& inputState )
	{
		return ( mState == IDLE )
			   && inputState.Pressed( InputState::LEFT_MOUSE | InputState::CONFIRM )
			   && Util::PointInRect( inputState.MouseX(), inputState.MouseY(), X(), Y(), mWidth, mHeight );
	}

	//! Custom delete, using the allocator;
	void operator delete( void* ptr ) { if ( ! ptr ) return; Memory::RevealFree( ptr ); }

	//! Custom array new, using the allocator;
	void* operator new [] ( size_t size ) 
	{ 
		return operator new( size ); 
	}
	
	//! Custom array delete, using the allocator;
	void operator delete [] ( void* ptr ) { operator delete( ptr ); }
	
protected:

	virtual Widget* DoHandle( const InputState& inputState )
	{
		if ( mState == IDLE )
		{
			if ( inputState.Pressed( InputState::LEFT_MOUSE | InputState::CONFIRM ) )
			{
				if ( Util::PointInRect( inputState.MouseX(), inputState.MouseY(), CrossX(), CrossY(), GuiMetrics::GetItemSize(), GuiMetrics::GetItemSize() ) )
				{
					Hide();
					return this;
				}
				else
				if ( Util::PointInRect( inputState.MouseX(), inputState.MouseY(), X() + mWidth - 20, Y() + mHeight - 20, 20, 20 ) )
				{
					mState = RESIZING;
					mMouseStartX = inputState.MouseX() - mWidth;
					mMouseStartY =  inputState.MouseY() - mHeight;
					return this;
				}
				else
				if ( Util::PointInRect( inputState.MouseX(), inputState.MouseY(), X(), Y(), mWidth, GuiMetrics::GetRowHeight() ) )
				{
					mState = DRAGGING;
					mMouseStartX =  inputState.MouseX() - X();
					mMouseStartY =  inputState.MouseY() - Y();
					return this;
				}
			}
			
			return 0;
		}
		else
		if ( mState == DRAGGING )
		{
			SetX( inputState.MouseX() - mMouseStartX );
			SetY( inputState.MouseY() - mMouseStartY );
			if ( X() < 0 ) SetX( 0 );
			if ( Y() < 0 ) SetY( 0 );
			const int border = 100;
			if ( X() > (GuiMetrics::SCREEN_WIDTH  - border) ) SetX( GuiMetrics::SCREEN_WIDTH  - border );
			if ( Y() > (GuiMetrics::SCREEN_HEIGHT - border) ) SetY( GuiMetrics::SCREEN_HEIGHT - border );
			if ( ! inputState.Down( InputState::LEFT_MOUSE | InputState::CONFIRM ) ) mState = IDLE;
		}
		else
		if ( mState == RESIZING )
		{
			mWidth  = inputState.MouseX() - mMouseStartX;
			mHeight = inputState.MouseY() - mMouseStartY;
			const int minWidth  = 100;
			const int minHeight = 100;
			if ( mWidth < minWidth ) mWidth = minWidth;
			if ( mHeight < minHeight ) mHeight = minHeight;
			if ( (X() + mWidth) > GuiMetrics::SCREEN_WIDTH ) mWidth -= (X() + mWidth) - GuiMetrics::SCREEN_WIDTH;
			if ( (Y() + mHeight) > GuiMetrics::SCREEN_HEIGHT ) mHeight -= (Y() + mHeight) - GuiMetrics::SCREEN_HEIGHT;
			if ( ! inputState.Down( InputState::LEFT_MOUSE | InputState::CONFIRM ) ) mState = IDLE;
		}
		else return 0;
		
		return this;
	}

	virtual void DoRender( const Renderer& renderer, const GuiColors& guiColors ) const
	{
		// Draw the closing cross;
		{
			renderer.Line( CrossX(), CrossY(), CrossX() + GuiMetrics::GetItemSize(), CrossY() + GuiMetrics::GetItemSize(), guiColors.mInteractiveForegroundColor );
			renderer.Line( CrossX() + GuiMetrics::GetItemSize(), CrossY(), CrossX(), CrossY() + GuiMetrics::GetItemSize(), guiColors.mInteractiveForegroundColor );
		}
		DrawBorder( renderer, guiColors );
	}

	int CrossX() const { return X() + Width() - GuiMetrics::GetItemSize() - GuiMetrics::BORDER_WIDTH; }
	int CrossY() const { return Y() + GuiMetrics::BORDER_HEIGHT; }

private:

	// Copy ctor and assignment operator are disabled;
	Window( const Window& );
	Window& operator = ( const Window& );

	// Attributes;

	int  mWidth;
	int  mHeight;

	//The location of the mouse when we start dragging/resizing
	int mMouseStartX, mMouseStartY;

	enum
	{
		IDLE,
		DRAGGING,
		RESIZING
	} mState;
};

	} // Namespace Impl;

} // Namespace Reveal;

#endif
