// ----------------------------------------------------------------------------
// Blitz-Irrlicht 1.8.3 Wrapper by Ed Musolino 2015
// Based off of Frank Dodd's Irrlicht 1.7 Freebasic Wrapper (2006-2010)
// ----------------------------------------------------------------------------

//  This software is provided 'as-is', without any express Or implied
//  warranty.  In no event will the authors be held liable For any damages
//  arising from the use of this software.

//  Permission is granted To anyone To use this software For any purpose,
//  including commercial applications, And To alter it And redistribute it
//  freely, subject To the following restrictions:

//  1. The Origin of this software must Not be misrepresented; you must not
//     claim that you wrote the original software. If you use this software
//     in a product, an acknowledgement in the product documentation would be
//     appreciated but is Not required.
//  2. Altered source versions must be clearly marked as such, And must Not be
//     misrepresented as being the original software.
//  3. This notice may Not be removed Or altered from any source distribution.
//
// /////////////////////////////////////////////////////////////////////////////

/* ////////////////////////////////////////////////////////////////////////////
includes
*/
#include "bIrrlicht.h"


/* ////////////////////////////////////////////////////////////////////////////
global variables
*/
// ED MUSOLINO ================================================================
unsigned int key_buffer[MAX_ARCHIVED_EVENTS] = { 0 };
unsigned int key_time[MAX_ARCHIVED_EVENTS] = { 0 };
unsigned int key_down[MAX_ARCHIVED_EVENTS] = { 0 };

bool m_mouse_left = false;
bool m_mouse_right = false;
bool m_mouse_middle = false;
float m_mouse_wheel = 0.0;
unsigned int m_mouse_x = 0;
unsigned int m_mouse_y = 0;
// ED MUSOLINO ================================================================

unsigned int uiReadKeyEvent = 0;
unsigned int uiWriteKeyEvent = 0;
//ARCHIVED_KEY_EVENT archivekey[MAX_ARCHIVED_EVENTS];

unsigned int uiReadMouseEvent = 0;
unsigned int uiWriteMouseEvent = 0;
//ARCHIVED_MOUSE_EVENT archivemouse[MAX_ARCHIVED_EVENTS];

unsigned int uiReadGUIEvent = 0;
unsigned int uiWriteGUIEvent = 0;
ARCHIVED_GUI_EVENT archivegui[MAX_ARCHIVED_EVENTS];
ARCHIVED_GUI_EVENT * LastEvent;

//ARCHIVED_KEY_EVENT keyblank = {0,0,0};
//ARCHIVED_MOUSE_EVENT mouseblank = {0,0,0};
ARCHIVED_GUI_EVENT guiblank = {0,0,0};

wchar_t cptrSelectedFile[512];
const wchar_t * g_LastSelectedFile = NULL;

/* ////////////////////////////////////////////////////////////////////////////
external variables
*/
extern IrrlichtDevice *device;
extern IVideoDriver* driver;
extern ISceneManager* smgr;
extern IGUIEnvironment* guienv;
extern StackedEventReceiver receiver;


/* ////////////////////////////////////////////////////////////////////////////
Class Function Declarations
*/

/* ----------------------------------------------------------------------------
This implementation of the Irrlicht event receiver receives events as they
happen and stores them in a circular buffer. the caller can then pop the
events out of the buffer at their convienence. if the events are unattended
and the buffer filled the oldest event is discarded
*/
bool StackedEventReceiver::OnEvent(const SEvent &event)
{
	const wchar_t *cptrFile;
	unsigned int m_key = 0;
    // arbitrate based on the type of event received
    switch ( event.EventType )
    {
    // Key strokes
    case EET_KEY_INPUT_EVENT:
	
		m_key = (unsigned int)event.KeyInput.Key;
		key_down[m_key] = (unsigned int)event.KeyInput.PressedDown;
		switch (key_time[m_key])
		{
			case 0:
				key_buffer[m_key] = 1;
				key_time[m_key] = device->getTimer()->getTime();
				break;
			default:
				if((key_time[m_key])+100 < device->getTimer()->getTime())
				 {
					 key_buffer[m_key] = 1;
				}
				else
				{
					if(key_down[m_key])
					{
						key_buffer[m_key] = 2;
					}
				else
				{
					key_buffer[m_key] = 0;
					key_time[m_key] = 0;
				}
				}
				break;
		}

		break;

    // Mouse actions
    case EET_MOUSE_INPUT_EVENT:

		switch 	((unsigned int)event.MouseInput.Event)
		{
			case 0:
				m_mouse_left = true;
				break;
			case 1:
				m_mouse_right = true;
				break;
			case 2:
				m_mouse_middle = true;
				break;
			case 3:
				m_mouse_left = false;
				break;
			case 4:
				m_mouse_right = false;
				break;
			case 5:
				m_mouse_middle = false;
				break;
			case 7:
				m_mouse_wheel = (float)event.MouseInput.Wheel;
				break;
			default:
				break;
		}

		break;

	// GUI actions
    case EET_GUI_EVENT:
		// handle the event type
		switch(event.GUIEvent.EventType)
		{
		case EGET_SCROLL_BAR_CHANGED:
			// archive the event information
			archivegui[uiWriteGUIEvent].id = event.GUIEvent.Caller->getID();
			archivegui[uiWriteGUIEvent].event = (int)event.GUIEvent.EventType;
			archivegui[uiWriteGUIEvent].x = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
			uiWriteGUIEvent++;
			break;

        case EGET_ELEMENT_FOCUS_LOST:
        case EGET_ELEMENT_FOCUSED:
        case EGET_ELEMENT_HOVERED:
        case EGET_FILE_CHOOSE_DIALOG_CANCELLED:
			// archive the event information but dont consume the event
			archivegui[uiWriteGUIEvent].id = event.GUIEvent.Caller->getID();
			archivegui[uiWriteGUIEvent].event = (int)event.GUIEvent.EventType;
			uiWriteGUIEvent++;
			break;

		case EGET_BUTTON_CLICKED:
		case EGET_EDITBOX_ENTER:
		case EGET_EDITBOX_CHANGED:
			// archive the event information
			archivegui[uiWriteGUIEvent].id = event.GUIEvent.Caller->getID();
			archivegui[uiWriteGUIEvent].event = (int)event.GUIEvent.EventType;
			uiWriteGUIEvent++;
			break;

		case EGET_CHECKBOX_CHANGED:
			// archive the event information
			archivegui[uiWriteGUIEvent].id = event.GUIEvent.Caller->getID();
			archivegui[uiWriteGUIEvent].event = (int)event.GUIEvent.EventType;
			archivegui[uiWriteGUIEvent].x = ((IGUICheckBox*)event.GUIEvent.Caller)->isChecked();
			uiWriteGUIEvent++;
			break;

		case EGET_LISTBOX_CHANGED:
		case EGET_LISTBOX_SELECTED_AGAIN:
			// archive the event information
			archivegui[uiWriteGUIEvent].id = event.GUIEvent.Caller->getID();
			archivegui[uiWriteGUIEvent].event = (int)event.GUIEvent.EventType;
			archivegui[uiWriteGUIEvent].x = ((IGUIListBox*)event.GUIEvent.Caller)->getSelected();
			uiWriteGUIEvent++;
			break;

		case EGET_FILE_SELECTED:
			// archive the event information
			cptrFile = ((IGUIFileOpenDialog*)event.GUIEvent.Caller)->getFileName();
			if ( cptrFile ) wcscpy_s( cptrSelectedFile, cptrFile );
			archivegui[uiWriteGUIEvent].id = event.GUIEvent.Caller->getID();
			archivegui[uiWriteGUIEvent].event = (int)event.GUIEvent.EventType;
			uiWriteGUIEvent++;

			g_LastSelectedFile = cptrSelectedFile;//cptrFile;
			// allow the system to handle the event too
			break;

		default:
			break;
		}

        // if we have filled the buffer
        if ( uiWriteGUIEvent >= MAX_ARCHIVED_EVENTS )
            // roll back to the start
            uiWriteGUIEvent = 0;
		break;

    // these events are not archived at this time
    case EET_LOG_TEXT_EVENT:
    case EET_USER_EVENT:
        break;
    }
    // consume all events
    return false;
}

/* ////////////////////////////////////////////////////////////////////////////
Global Function Declarations

all of the below functions are declared as C functions and are exposed without
any mangled names so that they can be easily imported into imperative
languages like FreeBasic
*/
extern "C"
{

/* ////////////////////////////////////////////////////////////////////////////
INPUT EVENT OPERATIONS
*/

DLL_EXPORT int BBCALL iGetKey()
{
    for (s32 i = 0; i < 255; ++i)
    {
        //if (g_InputControl->keyDown((io::EKeyCodes)g_ScancodeList[i]))
		if (key_down[i])
            return i;
    }
    return 0;
}

DLL_EXPORT void BBCALL iWaitKey()
{
	bool keyhit = false;
	while(!keyhit)
	{
	    for (s32 i = 0; i < 255; ++i)
	    {
			//if (g_InputControl->keyDown((io::EKeyCodes)g_ScancodeList[i]))
			if (key_buffer[i])
	            keyhit = true;
	    }
		device->run();
		device->yield();
	}
//	const SEvent event;
	//while (receiver.OnEvent(event) && !event.EventType == EET_KEY_INPUT_EVENT);
//	while(event.EventType!=EET_KEY_INPUT_EVENT)
//	{
//		receiver.OnEvent(event);
//	}

//	while(receiver.OnEvent(event.EventType)) && (event.EventType!=EET_KEY_INPUT_EVENT));
}


DLL_EXPORT bool BBCALL iKeyHit ( int key )
{
	switch (key_buffer[key])
	{
		case 1:
			if((!key_down[key]) & ((key_time[key])+100 < device->getTimer()->getTime()))
			{
				key_buffer[key] = 0;
				key_time[key] = 0;
			}
			return true;
			break;
		default:
			return false;
			break;
	}
}

DLL_EXPORT bool BBCALL iKeyDown( int key )	//	ED MUSOLINO
{
	if(key_down[key])
	{
	switch (key_buffer[key])
	{
		case 1:
			return true;
			break;
		case 2:
			return true;
			break;
		default:
			//return false;
			break;
	}
	}
	return false;
}

DLL_EXPORT void BBCALL iFlushKeys( void )	//	ED MUSOLINO
{
	// clear key buffers
	for(int x=0; x < MAX_ARCHIVED_EVENTS; ++x)
	{
		key_buffer[x] = 0;
		key_time[x] = 0;
		key_down[x] = 0;
	}
}

DLL_EXPORT bool BBCALL iMouseDown( int button )	//	ED MUSOLINO
{
	switch (button)
	{
	case 1:
		return m_mouse_left;
		//break;
	case 2:
		return m_mouse_right;
		//break;
	case 3:
		return m_mouse_middle;
		//break;
	default:
		break;
	}
	return false;
}

DLL_EXPORT float BBCALL iMouseWheel( void )
{
	float wheel = m_mouse_wheel;
	m_mouse_wheel = 0.0;
	return wheel;
}

DLL_EXPORT void BBCALL iFlushMouse( void )
{
	m_mouse_left = false;
	m_mouse_right = false;
	m_mouse_middle = false;
	m_mouse_wheel = 0.0;
	m_mouse_x = 0;
	m_mouse_y = 0;
}

/* ----------------------------------------------------------------------------
determine whether the GUI consumes events or not
*/
//DLL_EXPORT void BBCALL iGUIEvents( bool gui )
//{
	// Redundant
//}


//=====================================================================================
/*
|| ::DESCRIPTION::
|| This function will convert a WCHAR string to a CHAR string.
||
|| Param 1 :: Pointer to a buffer that will contain the converted string. Ensure this
||            buffer is large enough; if not, buffer overrun errors will occur.
|| Param 2 :: Constant pointer to a source WCHAR string to be converted to CHAR
*/
void wtoc( char* Dest, const wchar_t* Source )
{
    int i = 0;

    while(Source[i] != '\0')
    {
        Dest[i] = (char)Source[i];
        ++i;
    }
}

DLL_EXPORT const char* BBCALL iGetLastSelectedFile( void )
{
    size_t origsize = wcslen(g_LastSelectedFile) + 1;
    size_t convertedChars = 0;
    char strConcat[] = " (char *)";
    size_t strConcatsize = (strlen( strConcat ) + 1)*2;
    const size_t newsize = origsize*2;
    char *nstring = new char[newsize+strConcatsize];
    wcstombs_s(&convertedChars, nstring, newsize, g_LastSelectedFile, _TRUNCATE);
	return nstring;

}

/* ----------------------------------------------------------------------------
determine if a GUI event is available return true if one is
*/
DLL_EXPORT bool BBCALL iGUIEventAvailable( void )
{
    return ( uiReadGUIEvent != uiWriteGUIEvent );
}

/* ----------------------------------------------------------------------------
read the oldest GUI event in the buffer
*/
DLL_EXPORT void BBCALL iReadGUIEvent( void )
{
//    ARCHIVED_GUI_EVENT *report;

    // if there are any events evailable
    if ( uiReadGUIEvent != uiWriteGUIEvent )
    {
        // return the oldest event
//        report = &archivegui[uiReadGUIEvent];
		LastEvent = &archivegui[uiReadGUIEvent];

        // consume the event and if we exceed the buffer
        if ( ++uiReadGUIEvent >= MAX_ARCHIVED_EVENTS )
            // roll back to the start
            uiReadGUIEvent = 0;
    }
    else
        // there are no events available return a blank object
//        report = &guiblank;
		LastEvent = &guiblank;

//    return (int *)report;
}

DLL_EXPORT int BBCALL iGUIEventID( void )
{
	return LastEvent->id;
}

DLL_EXPORT int BBCALL iGUIEvent( void )
{
	return LastEvent->event;
}

DLL_EXPORT int BBCALL iGUIEventData( void )
{
	return LastEvent->x;
}


/* ----------------------------------------------------------------------------
return the file select buffer
*/
//DLL_EXPORT const wchar_t * BBCALL iLastSelectedFile( void )
//{
//	return cptrSelectedFile;
//}

/* ----------------------------------------------------------------------------
show and hide the mouse pointer
*/
DLL_EXPORT void BBCALL iShowMouse( void )
{
	device->getCursorControl()->setVisible(true);
}

/* ----------------------------------------------------------------------------
show and hide the mouse pointer
*/
DLL_EXPORT void BBCALL iHideMouse( void )
{
	device->getCursorControl()->setVisible(false);
}

/* ----------------------------------------------------------------------------
set the position of the mouse pointer and return the relative change in position

void BBCALL IrrSetMousePosition( float &x, float &y )
{
	core::position2d<f32> cursor = device->getCursorControl()->getRelativePosition();
	device->getCursorControl()->setPosition( x, y );
	x = cursor.X;
	y = cursor.Y;
}*/
DLL_EXPORT void BBCALL iMoveMouse( float x, float y )	//ED MUSOLINO
{
	device->getCursorControl()->setPosition( x, y );
}

/* ----------------------------------------------------------------------------
Gets the absoloute position of the mouse pointer.

DLL_EXPORT void BBCALL IrrGetAbsoluteMousePosition( int &x, int &y )
{
    irr::core::position2d<s32> cursor = device->getCursorControl()->getPosition();
    x = cursor.X;
    y = cursor.Y;
}*/
DLL_EXPORT int BBCALL iMouseX( void )		//ED MUSOLINO
{
    irr::core::position2d<s32> cursor = device->getCursorControl()->getPosition();
    return (int)cursor.X;
    //y = cursor.Y;
}

DLL_EXPORT int BBCALL iMouseY( void )		//ED MUSOLINO
{
    irr::core::position2d<s32> cursor = device->getCursorControl()->getPosition();
    return (int)cursor.Y;
    //y = cursor.Y;
}


/* ////////////////////////////////////////////////////////////////////////////
all of the above functions are declared as C functions and are exposed without
any mangled names
*/
}
