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

/*! \file */

#ifndef REVEAL_OBJECT_H
#define REVEAL_OBJECT_H

#include <stddef.h>
#include <assert.h>
#include <math.h>

#include "reveal/callback/callback_1.h"
#include "reveal/callback/callback_3.h"
#include "reveal/custom_memory.h"

namespace Reveal {

	class Renderer;
	class InputState;

	namespace Context {

// ------------------------------------------------------------------------------
/*!
	\brief This is the base class for all context-sensitive objects. The actual class
		   that is used is the derived BoundObject that is parametrized over the type T
		   that is bindable;
*/
// ------------------------------------------------------------------------------
class Object
{
	
public:
	
	typedef Callback::Callback1< void, const InputState&      > ClickAndHoldCallback;
	typedef Callback::Callback3< void, float&, float&, float& > GetPositionCallback;

	/*!
		\brief Constructir
		\param sphereRadius			Worldspace radius of the boundingsphere. Used for the picking code;
		\param clickAndHoldCallback	Callback that is called whenever the user clicks on the object and holds the mouse button;
		\param getPosCallback		Callback that is called when the system wants to know the current worldspace position of the object;
	*/
    Object( float sphereRadius, const ClickAndHoldCallback& clickAndHoldCallback, const GetPositionCallback& getPosCallback ) 
		: mGetPositionCallback( getPosCallback ), mClickAndHoldCallback( clickAndHoldCallback ), mSphereRadius( sphereRadius ) 
	{}

	//! 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( !"Reveal: Out of memory, no exception handler enabled, fatal error." );
				#endif
			}
		}
	}

	//! 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 ); }

	void ClickAndHold( const InputState& inputState )
	{
		if ( mClickAndHoldCallback.Valid() ) mClickAndHoldCallback( inputState );
	}
	
	void GetPosition( float& x, float& y, float& z )
	{
		if ( mGetPositionCallback.Valid() ) mGetPositionCallback( x, y, z );
	}

	float DistanceFromRay( float x0, float y0, float z0, float xd, float yd, float zd )
	{
		float xc = 0, yc = 0, zc = 0;
		mGetPositionCallback( xc, yc, zc );
		float a = xd*xd + yd*yd + zd*zd;
		float b = 2 * (xd * (x0 - xc) + yd * (y0 - yc) + zd * (z0 - zc));
		float c = (x0 - xc)*(x0 - xc) + (y0 - yc)*(y0 - yc) + (z0 - zc)*(z0 - zc) - mSphereRadius*mSphereRadius;

		float disc = b*b - 4*c;
		if ( disc < 0.0f ) 
			return -1.0f;
		else
		{
			float t0 = (-b + sqrtf( disc ) )/ 2*a;
			float t1 = (-b - sqrtf( disc ) )/ 2*a;
			if ( t0 < t1 )
				return fabsf( t0 );
			else 
				return fabsf( t1 );
		}
	}

	virtual ~Object() {}
	virtual bool HandleMenu( const InputState& inputState ) = 0;	
	virtual void RenderMenu( const Renderer&   renderer ) = 0;
	virtual void EnableAndShowContext( int x, int y ) = 0;

private:
	GetPositionCallback		mGetPositionCallback;
	ClickAndHoldCallback	mClickAndHoldCallback;
	float					mSphereRadius;
};

	} // Namespace Context;
} // Namespace Reveal;

#endif
