#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <GL/glu.h>
#include <stdio.h>

#include "GHOST_Event.h"
#include "GHOST_EventButton.h"
#include "GHOST_EventCursor.h"
#include "GHOST_EventKey.h"
#include "GHOST_EventWheel.h"
#include "GHOST_Types.h"
#include "GHOST_WindowZETA.h"

GHOST_WindowZETA::GHOST_WindowZETA(
	GHOST_SystemZETA* systemZETA,
	const STR_String& title,
	GHOST_TInt32 left,
	GHOST_TInt32 top,
	GHOST_TUns32 width,
	GHOST_TUns32 height,
	GHOST_TWindowState state,
	GHOST_TDrawingContextType type = GHOST_kDrawingContextTypeNone,
	const bool stereoVisual = false )
	: GHOST_Window( title, left, top, width, height, state, type, stereoVisual )
{
	fSystemZETA = systemZETA;

	// Create the BWindow and the BGLView
	BRect frame( left, top, left + width, top + height );
	fBWindow = new GHOSTBWindow(
		this,
		frame,
		title,
		B_TITLED_WINDOW_LOOK,
		B_NORMAL_WINDOW_FEEL,
		B_OUTLINE_RESIZE
		);
	
	fBWindow->Show();
}

GHOST_WindowZETA::~GHOST_WindowZETA()
{
	// Close the BWindow.
	fBWindow->Lock();
	fBWindow->Quit();
	fBWindow = NULL;
	fBGLView = NULL;
}

/*
 * GHOST_IWindow methods for GHOST_WindowZETA
 */

GHOST_TSuccess
GHOST_WindowZETA::activateDrawingContext()
{
	if( getDrawingContextType() == GHOST_kDrawingContextTypeOpenGL )
	{
		STR_String title;
		getTitle( title );

		fBWindow->LockContext();
		
		return GHOST_kSuccess;
	}
	else
	{
		return GHOST_kFailure;
	}
}

void
GHOST_WindowZETA::clientToScreen(
	GHOST_TInt32 inX,
	GHOST_TInt32 inY,
	GHOST_TInt32& outX,
	GHOST_TInt32& outY) const
{
	BPoint inpoint( inX, inY );
	BPoint outpoint;
	
	if( fBWindow->Lock() )
	{
		outpoint = fBWindow->ConvertToScreen( inpoint );

		outX = ( int )outpoint.x;
		outY = ( int )outpoint.y;
	
		fBWindow->Unlock();
	}
}


void
GHOST_WindowZETA::getClientBounds(
	GHOST_Rect& bounds ) const
{
	if( fBWindow->Lock() )
	{
		BRect cbounds = fBWindow->Bounds();
		
		bounds.set(
			0,
			0,
			cbounds.right,
			cbounds.bottom);
		fBWindow->Unlock();
	}
}

GHOST_TWindowState
GHOST_WindowZETA::getState() const
{
	// additional things later perhaps
	
	if( fBWindow->IsMinimized() == true )
	{
		printf( "ZETA: -> minimized\n" );
		return GHOST_kWindowStateMinimized;
	}
	else
	{
		printf( "ZETA: -> normal or maximized\n" );
		return GHOST_kWindowStateNormal;
	}
}

void
GHOST_WindowZETA::getTitle(
	STR_String& title ) const
{
	STR_String wtitle = "";
	wtitle = fBWindow->Title();
	if( wtitle.Length() > 0 )
		title = wtitle;
	else
		title = "untitled";
}

bool
GHOST_WindowZETA::getValid() const
{
	// What needs to be assured here exactly?
	
	if( fBWindow == NULL )
	{
		return false;
	}
	else
	{
		return true;
	}
}

void
GHOST_WindowZETA::getWindowBounds(
	GHOST_Rect& bounds ) const
{
	if( fBWindow->Lock() )
	{
	
		BRect wbounds = fBWindow->Frame();

		bounds.set(
			wbounds.left,
			wbounds.top,
			wbounds.right,
			wbounds.bottom );
		
		fBWindow->Unlock();
	}
}

GHOST_TSuccess
GHOST_WindowZETA::invalidate()
{
	/* is this performed at all? BeBook states no. Find a solution ;) */
	fBWindow->UpdateIfNeeded();
	
	return GHOST_kSuccess;
}

void
GHOST_WindowZETA::screenToClient(
	GHOST_TInt32 inX,
	GHOST_TInt32 inY,
	GHOST_TInt32& outX,
	GHOST_TInt32& outY) const
{
	/*
	 * It seems as if I need to do nothing here.
	 * The mouse coordinates fired in GHOSTBWindow are
	 * already in client coordinates.
	 */
	
	if( inX < 0 )
		inX = 0;
	if( inY < 0 )
		inY = 0;
	
	outX = inX;
	outY = inY;
}

GHOST_TSuccess
GHOST_WindowZETA::setClientHeight(
	GHOST_TUns32 height )
{
	BRect bounds = fBWindow->Bounds();
	
	fBWindow->ResizeTo( bounds.right, height );
	
	return GHOST_kSuccess;
}

GHOST_TSuccess
GHOST_WindowZETA::setClientSize(
	GHOST_TUns32 width,
	GHOST_TUns32 height )
{
	fBWindow->ResizeTo( width, height );
	
	return GHOST_kSuccess;
}

GHOST_TSuccess
GHOST_WindowZETA::setClientWidth(
	GHOST_TUns32 width )
{
	BRect bounds = fBWindow->Bounds();
	
	fBWindow->ResizeTo( width, bounds.bottom );
	
	return GHOST_kSuccess;
}

GHOST_TSuccess
GHOST_WindowZETA::setOrder(
	GHOST_TWindowOrder order )
{
	if( order == GHOST_kWindowOrderTop )
	{
		if( fBWindow->IsActive() == false )
			fBWindow->Activate( true );
	}
	else if( order == GHOST_kWindowOrderBottom )
	{
		if( fBWindow->IsActive() == true )
			fBWindow->SendBehind( fBWindow );
	}
	else
	{
		return GHOST_kFailure;
	}
	
	return GHOST_kSuccess;
}

GHOST_TSuccess
GHOST_WindowZETA::setState(
	GHOST_TWindowState state )
{
	// TODO Implement normal, max/fullscreen.
	
	if( state == getState() )
	{
		return GHOST_kSuccess;
	}
	
	switch( state )
	{
		case GHOST_kWindowStateNormal :
		case GHOST_kWindowStateMaximized :
		case GHOST_kWindowStateFullScreen :
		{
			if( fBWindow->IsMinimized() == true )
				fBWindow->Minimize( false );
			
			break;
		}
		case GHOST_kWindowStateMinimized :
		{
			if( fBWindow->IsMinimized() == false )
				fBWindow->Minimize( true );
			
			break;
		}
		default :
		{
			return GHOST_kFailure;
		}
	}
	
	return GHOST_kSuccess;
}

void
GHOST_WindowZETA::setTitle(
	const STR_String& title )
{
	fBWindow->Lock();
	fBWindow->SetTitle( title );
	fBWindow->Unlock();
}

GHOST_TSuccess
GHOST_WindowZETA::swapBuffers()
{
	if( getDrawingContextType() == GHOST_kDrawingContextTypeOpenGL )
	{
		fBWindow->SwapGLBuffers();
		
		return GHOST_kSuccess;
	}
	else
	{
		return GHOST_kFailure;
	}
}

/*
 * GHOST_Window methods for GHOST_WindowZETA
 */

GHOST_TSuccess
GHOST_WindowZETA::installDrawingContext(
	GHOST_TDrawingContextType type )
{
	fBWindow->LockContext();
	
	return GHOST_kSuccess;
}

GHOST_TSuccess
GHOST_WindowZETA::removeDrawingContext()
{
	printf( "ZETA: GHOST_WindowZETA::removeDrawingContext()\n" );

	fBWindow->UnlockContext();	
	
	return GHOST_kSuccess;
}

const unsigned char ZETA_CROSSHAIR_CURSOR[] = {
16,1,7,7,1,0,1,0,1,0,1,0,1,0,1,0,0,0,252,126,0,0,1,0,1,0,1,0,1,0,1,0,1,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

const unsigned char ZETA_PENCIL_CURSOR[] = {
16,1,0,0,192,0,240,0,124,0,114,0,33,0,32,128,16,64,8,32,4,16,2,8,1,4,0,130,0,65,0,34,
0,20,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

const unsigned char ZETA_WAIT_CURSOR[] = {
16,1,7,7,7,224,25,24,32,4,64,2,64,2,129,1,129,1,193,1,129,243,128,1,128,1,64,2,64,2,32,4,
25,24,7,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

const unsigned char ZETA_UPDOWN_CURSOR[] = {
16,1,7,7,1,0,3,128,7,192,15,224,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,15,224,7,192,
3,128,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

const unsigned char ZETA_LEFTRIGHT_CURSOR[] = {
16,1,7,7,0,0,0,0,0,0,0,0,16,8,48,12,127,254,255,255,127,254,48,12,16,8,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

GHOST_TSuccess
GHOST_WindowZETA::setWindowCursorShape(
	GHOST_TStandardCursor shape )
{
	switch( shape )
	{
		case GHOST_kStandardCursorDefault :
		{
			be_app->SetCursor( B_HAND_CURSOR );
			break;
		}
		case GHOST_kStandardCursorWait :
		{
			be_app->SetCursor( ZETA_WAIT_CURSOR );
			break;
		}
		case GHOST_kStandardCursorText :
		{
			be_app->SetCursor( B_CURSOR_I_BEAM );
			break;
		}
		case GHOST_kStandardCursorCrosshair :
		{
			be_app->SetCursor( ZETA_CROSSHAIR_CURSOR );
			break;
		}
		case GHOST_kStandardCursorUpDown :
		{
			be_app->SetCursor( ZETA_UPDOWN_CURSOR );
			break;
		}
		case GHOST_kStandardCursorLeftRight :
		{
			be_app->SetCursor( ZETA_LEFTRIGHT_CURSOR );
			break;
		}
		case GHOST_kStandardCursorPencil :
		{
			be_app->SetCursor( ZETA_PENCIL_CURSOR );
			break;
		}
		default :
		{
			GHOST_PRINT( "GHOST_WindowZETA::setWindowCursorShape() :  unhandled cursor type!\n" );
			return GHOST_kFailure;
		}
	}
	
	return GHOST_kSuccess;
}

GHOST_TSuccess
GHOST_WindowZETA::setWindowCursorVisibility(
	bool visible )
{
	if( visible )
	{
		be_app->ShowCursor();
	}
	else
	{
		be_app->HideCursor();
	}
	
	return GHOST_kSuccess;
}

GHOST_TSuccess
GHOST_WindowZETA::setWindowCustomCursorShape(
	GHOST_TUns8 bitmap[16][2],
	GHOST_TUns8 mask[16][2],
	int hotX,
	int hotY )
{
	GHOST_PRINT( "ZETA: GHOST_WindowZETA::setWindowCustomCursorShape([short]) ->  not implemented!\n" );

	// TODO
	
	return GHOST_kFailure;
}

GHOST_TSuccess
GHOST_WindowZETA::setWindowCustomCursorShape(
	GHOST_TUns8* bitmap,
	GHOST_TUns8* mask,
	int szx,
	int szy,
	int hotX,
	int hotY,
	int fg,
	int bg )
{
	GHOST_PRINT( "ZETA: GHOST_WindowZETA::setWindowCustomCursorShape([long]) -> not implemented\n" );

	// TODO
	
	return GHOST_kFailure;	
}

/*
 * ZETA methods for GHOST_WindowZETA
 */

GHOST_SystemZETA*
GHOST_WindowZETA::getSystemZETA()
{
	return fSystemZETA;
}

/*
 * ZETA stuff
 */

/*
 * GHOSTBWindow
 */

GHOSTBWindow::GHOSTBWindow(
	GHOST_WindowZETA* windowZETA,
	BRect frame,
	const char* title,
	window_look look,
	window_feel feel,
	uint32 flags,
	uint32 workspaces = B_CURRENT_WORKSPACE )
	: BWindow(
		frame,
		title,
		look,
		feel,
		flags,
		workspaces )
{
	fWindowZETA = windowZETA;

	fBGLView = new GHOSTBGLView(
		Bounds(),
		"BGLVIEW",
		B_FOLLOW_ALL,
		B_WILL_DRAW,
		BGL_RGB
		| BGL_DOUBLE
		| BGL_DEPTH
		| BGL_ACCUM		// this one and below needed?
		| BGL_ALPHA
		| BGL_STENCIL
		);


	AddChild( fBGLView );
}

void
GHOSTBWindow::LockContext()
{
	fBGLView->LockGL();
}

void
GHOSTBWindow::UnlockContext()
{
	fBGLView->UnlockGL();
}

void
GHOSTBWindow::SwapGLBuffers()
{
	fBGLView->SwapBuffers();
}

GHOSTBWindow::~GHOSTBWindow()
{
}

void
GHOSTBWindow::DispatchMessage(
	BMessage* msg,
	BHandler* target )
{
	switch( msg->what )
	{
		case B_KEY_DOWN :
		case B_KEY_UP :
		{			
			int8 bbyte = 0;
			int32 bkey = 0;
			
			msg->FindInt8( "byte", &bbyte );
			msg->FindInt32( "key", &bkey );
			
			GHOST_TKey gkey = GHOST_kKeyUnknown;
			
			if( ( bbyte >= 'A' ) && ( bbyte <= 'Z' ) )
			{
				gkey = GHOST_TKey( bbyte - 'A' + int( GHOST_kKeyA ) );
			}
			else if( ( bbyte >= 'a' ) && ( bbyte <= 'z' ) )
			{
				gkey = GHOST_TKey( bbyte - 'a' + int( GHOST_kKeyA ) );
			}
			else if( ( bkey >= B_F1_KEY ) && ( bkey <= B_F12_KEY ) )
			{
				gkey = GHOST_TKey( bkey - B_F1_KEY + int( GHOST_kKeyF1 ) );
			}
			else
			{				
				/* multibyte character recognition could go in here */
				
				if( gkey == GHOST_kKeyUnknown )
				{
					switch( bkey )
					{
						/* Numpad keys */
						case 0x64 :		gkey = GHOST_kKeyNumpad0;			break;
						case 0x58 :		gkey = GHOST_kKeyNumpad1;			break;
						case 0x59 :		gkey = GHOST_kKeyNumpad2;			break;
						case 0x5a :		gkey = GHOST_kKeyNumpad3;			break;
						case 0x48 :		gkey = GHOST_kKeyNumpad4;			break;
						case 0x49 :		gkey = GHOST_kKeyNumpad5;			break;
						case 0x4a :		gkey = GHOST_kKeyNumpad6;			break;
						case 0x37 :		gkey = GHOST_kKeyNumpad7;			break;
						case 0x38 :		gkey = GHOST_kKeyNumpad8;			break;
						case 0x39 :		gkey = GHOST_kKeyNumpad9;			break;
						case 0x65 :		gkey = GHOST_kKeyNumpadPeriod;		break;
						case 0x5b :		gkey = GHOST_kKeyNumpadEnter;		break;
						case 0x3a :		gkey = GHOST_kKeyNumpadPlus;		break;
						case 0x25 :		gkey = GHOST_kKeyNumpadMinus;		break;
						case 0x24 :		gkey = GHOST_kKeyNumpadAsterisk;	break;
						case 0x23 :		gkey = GHOST_kKeyNumpadSlash;		break;

 						/* Other keys */
						case 0x1e :		gkey = GHOST_kKeyBackSpace;			break;
						case 0x26 :		gkey = GHOST_kKeyTab;				break;
						case 0x47 :		gkey = GHOST_kKeyEnter;				break;
						case 0x1  :		gkey = GHOST_kKeyEsc;				break;
						case 0x5e :		gkey = GHOST_kKeySpace;				break;
						
 						/* Arrow keys */
 						case 0x61 : 	gkey = GHOST_kKeyLeftArrow;			break;
 						case 0x63 :		gkey = GHOST_kKeyRightArrow;		break;
 						case 0x57 :		gkey = GHOST_kKeyUpArrow;			break;
 						case 0x62 :		gkey = GHOST_kKeyDownArrow;			break;
 						
 						/* Navigation keys */
 						case 0x1f :		gkey = GHOST_kKeyInsert;			break;
 						case 0x32 :		gkey = GHOST_kKeyDelete;			break;
 						case 0x20 :		gkey = GHOST_kKeyHome;				break;
 						case 0x35 : 	gkey = GHOST_kKeyEnd;				break;
 						case 0x21 :		gkey = GHOST_kKeyUpPage;			break;
 						case 0x36 :		gkey = GHOST_kKeyDownPage;			break;
					}
				}
				
				if( gkey == GHOST_kKeyUnknown )
				{
					switch( bbyte )
					{
						/* normal Number keys */
						case 0x30 :		gkey = GHOST_kKey0;					break;
						case 0x31 :		gkey = GHOST_kKey1;					break;
						case 0x32 :		gkey = GHOST_kKey2;					break;
						case 0x33 :		gkey = GHOST_kKey3;					break;
						case 0x34 :		gkey = GHOST_kKey4;					break;
						case 0x35 :		gkey = GHOST_kKey5;					break;
						case 0x36 :		gkey = GHOST_kKey6;					break;
						case 0x37 :		gkey = GHOST_kKey7;					break;
						case 0x38 :		gkey = GHOST_kKey8;					break;
						case 0x39 :		gkey = GHOST_kKey9;					break;
						
						/* additional keys */
						case 0x22 : 	gkey = GHOST_kKeyQuote;				break;
 						case 0x2c :		gkey = GHOST_kKeyComma;				break;
 						case 0x2d :		gkey = GHOST_kKeyMinus;				break;
 						case 0x2e :		gkey = GHOST_kKeyPeriod;			break;
						case 0x2f :		gkey = GHOST_kKeySlash;				break;
						case 0x3b :		gkey = GHOST_kKeySemicolon;			break;
						case 0x3d : 	gkey = GHOST_kKeyEqual;				break;
						case 0x5b :		gkey = GHOST_kKeyLeftBracket;		break;
						case 0x5c :		gkey = GHOST_kKeyBackslash;			break;
						case 0x5d :		gkey = GHOST_kKeyRightBracket;		break;
						case 0x60 :		gkey = GHOST_kKeyAccentGrave;		break;
					}
				}

				/* Unhandled GHOST keys: */
				// GHOST_kKeyLineFeed ?
				// GHOST_kKeyClear ?
			}
			
			GHOST_TEventType type;
			if( msg->what == B_KEY_DOWN )
				type = GHOST_kEventKeyDown;
			else
				type = GHOST_kEventKeyUp;
			
			fWindowZETA->getSystemZETA()->pushEvent(
				new GHOST_EventKey(
					fWindowZETA->getSystemZETA()->getMilliSeconds(),
					type,
					fWindowZETA,
					gkey,
					bbyte ) );
			
			/* needs to be forwarded */
			BWindow::DispatchMessage( msg, target );
			
			break;
		}
		case B_MOUSE_DOWN :
		case B_MOUSE_UP :
		{
			GHOST_TButtonMask bmask = GHOST_kButtonMaskLeft;
						
			int32 buttons;
			if( msg->FindInt32( "buttons", &buttons ) != B_OK )
				break;
			
			/*
			 * as the B_MOUSE_UP message doesnt tell which button was released,
			 * we store the last button further below in fLastMouseButton.
			 * when we receive the B_MOUSE_UP, we switch( fLastMouseButton )
			 * instead against buttons.
			 */
			
			switch( msg->what == B_MOUSE_DOWN ? buttons : fLastMouseButton )
			{
				case B_PRIMARY_MOUSE_BUTTON :
				{
					bmask = GHOST_kButtonMaskLeft;
					break;
				}
				case B_SECONDARY_MOUSE_BUTTON :
				{
					bmask = GHOST_kButtonMaskRight;
					break;
				}
				case B_TERTIARY_MOUSE_BUTTON :
				{
					bmask = GHOST_kButtonMaskMiddle;
					break;
				}
			}
			
			GHOST_TEventType type;			
			if( msg->what == B_MOUSE_DOWN )
			{
				fLastMouseButton = buttons;
				type = GHOST_kEventButtonDown;
			}
			else
			{
				type = GHOST_kEventButtonUp;
			}
			
			fWindowZETA->getSystemZETA()->pushEvent(
				new GHOST_EventButton(
					fWindowZETA->getSystemZETA()->getMilliSeconds(),
					type,
					fWindowZETA,
					bmask ) );
			
			BWindow::DispatchMessage( msg, target );
			
			break;
		}
		case B_MOUSE_MOVED :
		{
			BPoint point;
			if( msg->FindPoint( "where", &point ) != B_OK )
				break;
			
			fWindowZETA->getSystemZETA()->pushEvent(
				new GHOST_EventCursor(
					fWindowZETA->getSystemZETA()->getMilliSeconds(),
					GHOST_kEventCursorMove,
					fWindowZETA,
					point.x,
					point.y ) );
			
			BWindow::DispatchMessage( msg, target );
			
			break;
		}
		case B_MOUSE_WHEEL_CHANGED :
		{
			float value;
			if( msg->FindFloat( "be:wheel_delta_y", &value ) != B_OK )
				break;
				
			//value *= 3;	/* dunno if to unprecise */
			
			fWindowZETA->getSystemZETA()->pushEvent(
				new GHOST_EventWheel(
					fWindowZETA->getSystemZETA()->getMilliSeconds(),
					fWindowZETA,
					floor( value ) ) );
			
			BWindow::DispatchMessage( msg, target );
			
			break;
		}
		case B_WINDOW_ACTIVATED :
		{
			bool active;
			msg->FindBool( "active", &active );
			
			GHOST_TEventType type;
			if( active == true )
				type = GHOST_kEventWindowActivate;
			else
				type = GHOST_kEventWindowDeactivate;
				
			fWindowZETA->getSystemZETA()->pushEvent(
				new GHOST_Event(
					fWindowZETA->getSystemZETA()->getMilliSeconds(),
					type,
					fWindowZETA ) );
			
			break;
		}
		case B_WINDOW_RESIZED :
		{
			fWindowZETA->getSystemZETA()->pushEvent(
				new GHOST_Event(
					fWindowZETA->getSystemZETA()->getMilliSeconds(),
					GHOST_kEventWindowSize,
					fWindowZETA ) );
			
			BWindow::DispatchMessage( msg, target );
			
			break;
		}
		case B_QUIT_REQUESTED :
		{
			fWindowZETA->getSystemZETA()->pushEvent(
				new GHOST_Event(
					fWindowZETA->getSystemZETA()->getMilliSeconds(),
					GHOST_kEventWindowClose,
					fWindowZETA ) );
			
			break;
		}
		case _UPDATE_ :
		case _UPDATE_IF_NEEDED_ :
		{
			fWindowZETA->getSystemZETA()->pushEvent(
				new GHOST_Event(
					fWindowZETA->getSystemZETA()->getMilliSeconds(),
					GHOST_kEventWindowUpdate,
					fWindowZETA ) );
			
			/* needs to be forwarded */
			BWindow::DispatchMessage( msg, target );
			
			break;
		}
		default :
		{
			BWindow::DispatchMessage( msg, target );
		}
	}
}

/*
 * GHOSTBGLView
 */

GHOSTBGLView::GHOSTBGLView(
	BRect frame,
	char* name,
	uint32 resizingmode,
	uint32 flags,
	uint32 type )
	: BGLView( frame, name, resizingmode, flags, type )
{
	/* currently empty */
}
