//
// Irrlicht Wrapper for Imperative Languages
// Frank Dodd (2006)
//
// Additional Work
// Michael Taupitz (2006)
//
// This wrapper DLL encapsulates a sub-set of the features of the powerful
// Irrlicht 3D Graphics Engine exposing the Object Oriented architecture and
// providing a functional 3D SDK for languages that are not object oriented.
//
// This source was created with the help of the great examples in the Irrlicht
// SDK and the excellent Irrlicht documentation. This software was developed
// using the GCC compiler and Code::Blocks IDE
//

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


/* ////////////////////////////////////////////////////////////////////////////
global variables
*/
STATIC_EXPORT unsigned int uiReadKeyEvent = 0;
STATIC_EXPORT unsigned int uiWriteKeyEvent = 0;
STATIC_EXPORT ARCHIVED_KEY_EVENT archivekey[MAX_ARCHIVED_EVENTS];

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


STATIC_EXPORT unsigned int uiReadGuiEvent = 0;
STATIC_EXPORT unsigned int uiWriteGuiEvent = 0;
STATIC_EXPORT ARCHIVED_GUI_EVENT archivegui[MAX_ARCHIVED_EVENTS];

STATIC_EXPORT ARCHIVED_KEY_EVENT keyblank = {0,0,0};
STATIC_EXPORT ARCHIVED_MOUSE_EVENT mouseblank = {0,0,0};
STATIC_EXPORT ARCHIVED_GUI_EVENT guiblank = {NULL,EGET_ELEMENT_FOCUS_LOST,-1 };



/* ////////////////////////////////////////////////////////////////////////////
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)
{
    // arbitrate based on the type of event received
    switch ( event.EventType )
    {
    // Key strokes
    case EET_KEY_INPUT_EVENT:
        // archive the event information
        archivekey[uiWriteKeyEvent].uikey = (unsigned int)event.KeyInput.Key;
        archivekey[uiWriteKeyEvent].uidirection = (unsigned int)event.KeyInput.PressedDown;
        archivekey[uiWriteKeyEvent].uiflags =
                (unsigned int)event.KeyInput.Control * 2 +
                (unsigned int)event.KeyInput.Shift;

        // move to the next event buffer slot
        // if we have reached the end of the buffer
        if ( ++uiWriteKeyEvent >= MAX_ARCHIVED_EVENTS )
            // roll back to the start
            uiWriteKeyEvent = 0;

        // if the read and write positions are the same
        if ( uiReadKeyEvent == uiWriteKeyEvent )
        {
            // our buffer has overflowed, erase the oldest event
            // if we have reached the end of the buffer
            if ( ++uiReadKeyEvent >= MAX_ARCHIVED_EVENTS )
                // roll back to the start
                uiReadKeyEvent = 0;
        }
        break;

    // Mouse actions
    case EET_MOUSE_INPUT_EVENT:
        // archive the event information
        archivemouse[uiWriteMouseEvent].uiaction = (unsigned int)event.MouseInput.Event;
        archivemouse[uiWriteMouseEvent].wheel = event.MouseInput.Wheel;
        archivemouse[uiWriteMouseEvent].ix = (unsigned int)event.MouseInput.X;
        archivemouse[uiWriteMouseEvent++].iy = (unsigned int)event.MouseInput.Y;

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

    // these events are not archived at this time
    case EET_GUI_EVENT:
        archivegui[uiWriteGuiEvent].Caller = event.GUIEvent.Caller;
        archivegui[uiWriteGuiEvent].EventType = event.GUIEvent.EventType;
        archivegui[uiWriteGuiEvent].ItemId  = event.GUIEvent.Caller->getID();

        //sonderlocke für Menus....
        if (event.GUIEvent.EventType == EGET_MENU_ITEM_SELECTED)
        {
            // a menu item was clicked
            IGUIContextMenu* menu = (IGUIContextMenu*)event.GUIEvent.Caller;
            archivegui[uiWriteGuiEvent].ItemId = menu->getItemCommandId(menu->getSelectedItem());
        }
        //sonderlocke für Filedialog....
        //Das ist das Problem beim konsumieren der einzelnen Events. Der Dialog is weg, bevor
        // man reagieren kann......
        //dafür sollte der CallbackEventReceiver eingesetzt werden.


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



    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
*/
/****h* Events
* NAME
*    Read Irrlicht-Events
* DESCRIPTION
*   The IrrlichtEngine send some Events to a EventReceiver. These Evants are to
*   determine Mouse-clicks or mouse movements. There are Events for Keys, for the GUI,
*   and for the Logging. You can also create your own Events.
*
*   In this Section you will find several functions to get informations about these
*   Events. These Functiona are still from the original IrrlichtWrapper. But i'm not
*   satisfied with this solution, while the techinique is slow and error-prone. So in
*   the Background exists a Object that catches all Events from Irrlicht, and stores them
*   in a Array. And the User have now to walk through all Events in the Arrays.
*   The Problem is now, that you loose the sequence, and there are some events you have
*   to react directly (e.g. in the GUI) !.
*
*   But here is a small example how to use the 'old' solution :
*
* EXAMPLE
*   ; while there are key events waiting to be processed
*   While IrrKeyEventAvailable()
*
*       ; read the key event out. the key event has three parameters the key
*       ; scan code, the direction of the key and flags that indicate whether
*       ; the control key or the shift keys were also pressed
*       *KeyEvent.IRR_KEY_EVENT = IrrReadKeyEvent()
*
*       ; arbitrate based on the key that was pressed
*       Select *KeyEvent\key
*       ; .. now check the Key and start the action ...
*       EndSelect
*
*   Wend
*   ; same goes for mouse and GUI-Events !
*
* DESCRIPTION
*   So i created a 'better' Solution, and would recommend the new one. It's faster and
*   more in the 'Irrlicht'-Style.
*
* EXAMPLE
*   ;Callback-Procedure:
*   ProcedureDLL eventCallback(*event.SEvent)
*       ;Gui-Events
*       If *event\EventType = #IRR_EET_GUI_EVENT
*           id.l = IrrGuiGetId(*event\GuiEvent\Caller)
*           Select *event\GuiEvent\EventType
*               Case #IRR_EGET_MENU_ITEM_SELECTED
*               Case #IRR_EGET_FILE_SELECTED:
*               Case #IRR_EGET_SCROLL_BAR_CHANGED:;
*               Case #IRR_EGET_COMBO_BOX_CHANGED:
*               Case #IRR_EGET_BUTTON_CLICKED:
*           EndSelect
*       EndIf
*
*       ;Key-Events
*       If *event\EventType = #IRR_EET_KEY_INPUT_EVENT
*           key.l = *event\KeyEvent\Key
*       EndIf
*
*       If *event\EventType = #IRR_EET_MOUSE_INPUT_EVENT
*           mouse_X.l = *event\MouseEvent\X
*           mouse_Y.l = *event\MouseEvent\X
*           mouse_wheel.f = *event\MouseEvent\Wheel
*       EndIf
*       ProcedureReturn #False
*   EndProcedure
*
*   ;Create the Event-Receiver, and set it as the active
*   ;EventReceiver in Irrlicht.
*   *ev.l = IrrCreateEventReceiver(@eventCallback)
*   IrrSetEventReceiver(*ev)
*
* NOTE
*   It is planned to remove these function from the Wrapper, since there is a much
*   better Solution.
****
*/

/****f* Events/IrrKeyEventAvailable
* NAME
*    IrrKeyEventAvailable - Determine if a key event is available
* SYNOPSIS
*    IrrKeyEventAvailable()
* INPUTS
* FUNCTION
*   Find out if there is a key event ready to be Read. Returns #True If there is an
*   event available.
*   (You have to start/init Irrlicht with the IrrStart() and set the EventReceiver on !)
* EXAMPLE
*   ; while there are key events waiting to be processed
*   While IrrKeyEventAvailable()
*
*       ; read the key event out. the key event has three parameters the key
*       ; scan code, the direction of the key and flags that indicate whether
*       ; the control key or the shift keys were also pressed
*       *KeyEvent.IRR_KEY_EVENT = IrrReadKeyEvent()
*
*       ; arbitrate based on the key that was pressed
*       Select *KeyEvent\key
*       ; .. now check the Key and start the action ...
*       EndSelect
*
*   Wend
* RESULT
*    #True, if there is a Key-Event available
****
* This is for creating the PB-Importfile, and avoiding to make everything manual
#PBIMPORT:IrrKeyEventAvailable()
*/

STATIC_EXPORT bool DLL_EXPORT IrrKeyEventAvailable( void )
{
    return ( uiReadKeyEvent != uiWriteKeyEvent );
}

/****f* Events/IrrReadKeyEvent
* NAME
*    IrrReadKeyEvent - read the oldest key event in the buffer
* SYNOPSIS
*    IrrReadKeyEvent()
* INPUTS
* FUNCTION
*   Read a key event out
* EXAMPLE
*   ; while there are key events waiting to be processed
*   While IrrKeyEventAvailable()
*
*       ; read the key event out. the key event has three parameters the key
*       ; scan code, the direction of the key and flags that indicate whether
*       ; the control key or the shift keys were also pressed
*       *KeyEvent.IRR_KEY_EVENT = IrrReadKeyEvent()
*
*       ; arbitrate based on the key that was pressed
*       Select *KeyEvent\key
*       ; .. now check the Key and start the action ...
*       EndSelect
*
*   Wend
* RESULT
*    pointer to a IRR_KEY_EVENT-Structure
****
* This is for creating the PB-Importfile, and avoiding to make everything manual
#PBIMPORT:IrrReadKeyEvent()
*/
STATIC_EXPORT ARCHIVED_KEY_EVENT *DLL_EXPORT IrrReadKeyEvent( void )
{
    ARCHIVED_KEY_EVENT *report;

    // if there are any events evailable
    if ( uiReadKeyEvent != uiWriteKeyEvent )
    {
        // return the oldest event
        report = &archivekey[uiReadKeyEvent];

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

    return report;
}

/****f* Events/IrrMouseEventAvailable
* NAME
*    IrrMouseEventAvailable - Discover if a mouse event is available
* SYNOPSIS
*    IrrMouseEventAvailable()
* INPUTS
* FUNCTION
*   Find out if there is a mouse event ready to be Read. Returns #True If there is an
*   event available.
*   (You have to start/init Irrlicht with the IrrStart() and set the EventReceiver on !)
* EXAMPLE
*   ; while there are mouse events waiting to be processed
*   While IrrMouseEventAvailable()
*
*       ; read the mouse event out.
*       *MouseEvent.IRR_MOUSE_EVENT = IrrReadMouseEvent()
*
*       ; find out the mouse-action
*       Select *MouseEvent\action
*           Case #IRR_EMIE_LMOUSE_PRESSED_DOWN
*           Case #IRR_EMIE_MOUSE_MOVED
*               mouseX.l = *MouseEvent\x
*               mouseY.l = *MouseEvent\y
*           Case #IRR_EMIE_MOUSE_WHEEL
*               mouseWheel.f = *MouseEvent\wheel
*       EndSelect
*
*   Wend
* RESULT
*    #True, if there is a Mouse-Event available
****
* This is for creating the PB-Importfile, and avoiding to make everything manual
#PBIMPORT:IrrMouseEventAvailable()
*/
STATIC_EXPORT bool DLL_EXPORT IrrMouseEventAvailable( void )
{
    return ( uiReadMouseEvent != uiWriteMouseEvent );
}

/****f* Events/IrrReadMouseEvent
* NAME
*    IrrReadMouseEvent - Read the oldest mouse event in the buffer
* SYNOPSIS
*    IrrReadMouseEvent()
* INPUTS
* FUNCTION
*   read mouse event
* EXAMPLE
*   ; while there are mouse events waiting to be processed
*   While IrrReadMouseEvent()
*
*       ; read the mouse event out.
*       *MouseEvent.IRR_MOUSE_EVENT = IrrReadMouseEvent()
*
*       ; find out the mouse-action
*       Select *MouseEvent\action
*           Case #IRR_EMIE_LMOUSE_PRESSED_DOWN
*           Case #IRR_EMIE_MOUSE_MOVED
*               mouseX.l = *MouseEvent\x
*               mouseY.l = *MouseEvent\y
*           Case #IRR_EMIE_MOUSE_WHEEL
*               mouseWheel.f = *MouseEvent\wheel
*       EndSelect
*
*   Wend
* RESULT
*    pointer to a IRR_MOUSE_EVENT-Structure
****
* This is for creating the PB-Importfile, and avoiding to make everything manual
#PBIMPORT:IrrReadMouseEvent()
*/
STATIC_EXPORT ARCHIVED_MOUSE_EVENT *DLL_EXPORT IrrReadMouseEvent( void )
{
    ARCHIVED_MOUSE_EVENT *report;

    // if there are any events available
    if ( uiReadMouseEvent != uiWriteMouseEvent )
    {
        // return the oldest event
        report = &archivemouse[uiReadMouseEvent];

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

    return report;
}


/****f* Events/IrrGuiEventAvailable
* NAME
*    IrrGuiEventAvailable - discover if a gui event is available
* SYNOPSIS
*    IrrGuiEventAvailable()
* INPUTS
* FUNCTION
*   read gui event
* EXAMPLE
*   ; while there are gui events waiting to be processed
*   While IrrGuiEventAvailable()
*
*       ; read the gui event out.
*       *GuiEvent.IRR_GUI_EVENT = IrrReadGuiEvent()
*
*       ; find out if there is gui-action
*       If *GuiEvent\EventType > 0
*           ;find out the caller (here a button we created before)
*           If *guievent\Caller = *button And *guievent\EventType = #IRR_EGET_BUTTON_CLICKED
*               ;... do something ...
*           Endif
*       Endif
*   Wend
* RESULT
*    #True, if there is a GUI-Event available
****
* This is for creating the PB-Importfile, and avoiding to make everything manual
#PBIMPORT:IrrGuiEventAvailable()
*/
STATIC_EXPORT bool DLL_EXPORT IrrGuiEventAvailable( void )
{
    return ( uiReadGuiEvent != uiWriteGuiEvent );
}

/****f* Events/IrrReadGuiEvent
* NAME
*    IrrReadGuiEvent - discover if a gui event is available
* SYNOPSIS
*    IrrReadGuiEvent()
* INPUTS
* FUNCTION
*   read gui event
* EXAMPLE
*   ; while there are gui events waiting to be processed
*   While IrrGuiEventAvailable()
*
*       ; read the gui event out.
*       *GuiEvent.IRR_GUI_EVENT = IrrReadGuiEvent()
*
*       ; find out if there is gui-action
*       If *GuiEvent\EventType > 0
*           ;find out the caller (here a button we created before)
*           If *guievent\Caller = *button And *guievent\EventType = #IRR_EGET_BUTTON_CLICKED
*               ;... do something ...
*           Endif
*       Endif
*   Wend
* RESULT
*    pointer to a IRR_GUI_EVENT-Structure
****
* This is for creating the PB-Importfile, and avoiding to make everything manual
#PBIMPORT:IrrReadGuiEvent()
*/
STATIC_EXPORT ARCHIVED_GUI_EVENT *DLL_EXPORT IrrReadGuiEvent( void )
{
    ARCHIVED_GUI_EVENT *report;

    // if there are any events available
    if ( uiReadGuiEvent != uiWriteGuiEvent )
    {
        // return the oldest event
        report = &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 evente available return a blank object
        report = &guiblank;

    return report;
}


/****f* GUI/IrrDisplayMouse
* NAME
*    IrrDisplayMouse - show and hide the mouse pointer
* SYNOPSIS
*    IrrDisplayMouse()
* INPUTS
*   bEnabled.l  - If #True, the Mousepointer is visible, if #False not.
* FUNCTION
*   Enables or disables the Mouse-Pointer
* EXAMPLE
*   IrrDisplayMouse(#False) ; No Mouse-Poitner
* RESULT
*    nothing
****
* This is for creating the PB-Importfile, and avoiding to make everything manual
#PBIMPORT:IrrDisplayMouse(bEnabled.l)
*/
STATIC_EXPORT void DLL_EXPORT IrrDisplayMouse( bool boShow )
{
	device->getCursorControl()->setVisible(boShow);
}


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