/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997-2009 Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Sam Lantinga
    slouken@libsdl.org

    QNX Graphics Framework SDL driver
    Copyright (C) 2009 Mike Gorchak
    (mike@malva.ua, lestat@i.com.ua)
*/

#include "SDL_gf_input.h"

#include "SDL_config.h"
#include "SDL_events.h"
#include "../../events/SDL_mouse_c.h"
#include "../../events/SDL_keyboard_c.h"

/* Include QNX HIDDI definitions */
#include "SDL_hiddi_keyboard.h"
#include "SDL_hiddi_mouse.h"
#include "SDL_hiddi_joystick.h"

/* Mouse related functions */
SDL_Cursor *gf_createcursor(SDL_Surface * surface, int hot_x, int hot_y);
int gf_showcursor(SDL_Cursor * cursor);
void gf_movecursor(SDL_Cursor * cursor);
void gf_freecursor(SDL_Cursor * cursor);
void gf_warpmouse(SDL_Mouse * mouse, SDL_WindowID windowID, int x, int y);
void gf_freemouse(SDL_Mouse * mouse);

/* HIDDI interacting functions */
static int32_t hiddi_connect_devices();
static int32_t hiddi_disconnect_devices();

int32_t
gf_addinputdevices(_THIS)
{
    SDL_VideoData *gfdata = (SDL_VideoData *) _this->driverdata;
    SDL_DisplayData *didata;
    struct SDL_Mouse gf_mouse;
    SDL_Keyboard gf_keyboard;
    SDLKey keymap[SDL_NUM_SCANCODES];
    SDL_MouseData *mdata;
    uint32_t it;

    for (it = 0; it < _this->num_displays; it++) {
        SDL_VideoDisplay *display = &_this->displays[it];

        /* Clear SDL mouse structure */
        SDL_memset(&gf_mouse, 0x00, sizeof(struct SDL_Mouse));

        /* Allocate SDL_MouseData structure */
        mdata = (SDL_MouseData *) SDL_calloc(1, sizeof(SDL_MouseData));
        if (mdata == NULL) {
            SDL_OutOfMemory();
            return -1;
        }

        /* Mark this mouse with ID 0 */
        gf_mouse.id = it;
        gf_mouse.driverdata = (void *) mdata;
        gf_mouse.CreateCursor = gf_createcursor;
        gf_mouse.ShowCursor = gf_showcursor;
        gf_mouse.MoveCursor = gf_movecursor;
        gf_mouse.FreeCursor = gf_freecursor;
        gf_mouse.WarpMouse = gf_warpmouse;
        gf_mouse.FreeMouse = gf_freemouse;

        /* Get display data */
        didata = (SDL_DisplayData *) display->driverdata;

        /* Store SDL_DisplayData pointer in the mouse driver internals */
        mdata->didata = didata;

        /* Set cursor pos to 0,0 to avoid cursor disappearing in some drivers */
        gf_cursor_set_pos(didata->display, 0, 0, 0);

        /* Register mouse cursor in SDL */
        SDL_AddMouse(&gf_mouse, "GF mouse cursor", 0, 0, 1);
    }

    /* Keyboard could be one only */
    SDL_zero(gf_keyboard);
    SDL_AddKeyboard(&gf_keyboard, -1);

    /* Add scancode to key mapping, HIDDI uses USB HID codes, so */
    /* map will be exact one-to-one */
    SDL_GetDefaultKeymap(keymap);
    SDL_SetKeymap(0, 0, keymap, SDL_NUM_SCANCODES);

    /* Connect to HID server and enumerate all input devices */
    hiddi_connect_devices();

    return 0;
}

int32_t
gf_delinputdevices(_THIS)
{
    /* Disconnect from HID server and release input devices */
    hiddi_disconnect_devices();

    /* Delete keyboard */
    SDL_KeyboardQuit();

    /* Destroy all of the mice */
    SDL_MouseQuit();
}

/*****************************************************************************/
/* GF Mouse related functions                                                */
/*****************************************************************************/
SDL_Cursor *
gf_createcursor(SDL_Surface * surface, int hot_x, int hot_y)
{
    gf_cursor_t *internal_cursor;
    SDL_Cursor *sdl_cursor;
    uint8_t *image0 = NULL;
    uint8_t *image1 = NULL;
    uint32_t it;
    uint32_t jt;
    uint32_t shape_color;

    /* SDL converts monochrome cursor shape to 32bpp cursor shape      */
    /* and we must convert it back to monochrome, this routine handles */
    /* 24/32bpp surfaces only                                          */
    if ((surface->format->BitsPerPixel != 32)
        && (surface->format->BitsPerPixel != 24)) {
        SDL_SetError("GF: Cursor shape is not 24/32bpp.");
        return NULL;
    }

    /* Since GF is not checking data, we must check */
    if ((surface->w == 0) || (surface->h == 0)) {
        SDL_SetError("GF: Cursor shape dimensions are zero");
        return NULL;
    }

    /* Allocate memory for the internal cursor format */
    internal_cursor = (gf_cursor_t *) SDL_calloc(1, sizeof(gf_cursor_t));
    if (internal_cursor == NULL) {
        SDL_OutOfMemory();
        return NULL;
    }

    /* Allocate memory for the SDL cursor */
    sdl_cursor = (SDL_Cursor *) SDL_calloc(1, sizeof(SDL_Cursor));
    if (sdl_cursor == NULL) {
        SDL_free(internal_cursor);
        SDL_OutOfMemory();
        return NULL;
    }

    /* Allocate two monochrome images */
    image0 = (uint8_t *) SDL_calloc(1, ((surface->w + 7) / 8) * surface->h);
    if (image0 == NULL) {
        SDL_free(sdl_cursor);
        SDL_free(internal_cursor);
        SDL_OutOfMemory();
        return NULL;
    }
    image1 = (uint8_t *) SDL_calloc(1, ((surface->w + 7) >> 3) * surface->h);
    if (image1 == NULL) {
        SDL_free(image0);
        SDL_free(sdl_cursor);
        SDL_free(internal_cursor);
        SDL_OutOfMemory();
        return NULL;
    }

    /* Set driverdata as GF cursor format */
    sdl_cursor->driverdata = (void *) internal_cursor;
    internal_cursor->type = GF_CURSOR_BITMAP;
    internal_cursor->hotspot.x = hot_x;
    internal_cursor->hotspot.y = hot_y;
    internal_cursor->cursor.bitmap.w = surface->w;
    internal_cursor->cursor.bitmap.h = surface->h;
    internal_cursor->cursor.bitmap.color0 = SDL_GF_MOUSE_COLOR_BLACK;
    internal_cursor->cursor.bitmap.color1 = SDL_GF_MOUSE_COLOR_WHITE;

    /* Setup cursor shape images */
    internal_cursor->cursor.bitmap.stride = ((surface->w + 7) >> 3);
    internal_cursor->cursor.bitmap.image0 = image0;
    internal_cursor->cursor.bitmap.image1 = image1;

    /* Convert cursor from 32 bpp */
    for (jt = 0; jt < surface->h; jt++) {
        for (it = 0; it < surface->w; it++) {
            shape_color =
                *((uint32_t *) ((uint8_t *) surface->pixels +
                                jt * surface->pitch +
                                it * surface->format->BytesPerPixel));
            switch (shape_color) {
            case SDL_GF_MOUSE_COLOR_BLACK:
                {
                    *(image0 + jt * (internal_cursor->cursor.bitmap.stride) +
                      (it >> 3)) |= 0x80 >> (it % 8);
                    *(image1 + jt * (internal_cursor->cursor.bitmap.stride) +
                      (it >> 3)) &= ~(0x80 >> (it % 8));
                }
                break;
            case SDL_GF_MOUSE_COLOR_WHITE:
                {
                    *(image0 + jt * (internal_cursor->cursor.bitmap.stride) +
                      (it >> 3)) &= ~(0x80 >> (it % 8));
                    *(image1 + jt * (internal_cursor->cursor.bitmap.stride) +
                      (it >> 3)) |= 0x80 >> (it % 8);
                }
                break;
            case SDL_GF_MOUSE_COLOR_TRANS:
                {
                    *(image0 + jt * (internal_cursor->cursor.bitmap.stride) +
                      (it >> 3)) &= ~(0x80 >> (it % 8));
                    *(image1 + jt * (internal_cursor->cursor.bitmap.stride) +
                      (it >> 3)) &= ~(0x80 >> (it % 8));
                }
                break;
            default:
                {
                    /* The same as transparent color, must not happen */
                    *(image0 + jt * (internal_cursor->cursor.bitmap.stride) +
                      (it >> 3)) &= ~(0x80 >> (it % 8));
                    *(image1 + jt * (internal_cursor->cursor.bitmap.stride) +
                      (it >> 3)) &= ~(0x80 >> (it % 8));
                }
                break;
            }
        }
    }

    return sdl_cursor;
}

int
gf_showcursor(SDL_Cursor * cursor)
{
    SDL_VideoDisplay *display;
    SDL_DisplayData *didata;
    SDL_Window *window;
    SDL_WindowID window_id;
    gf_cursor_t *internal_cursor;
    int32_t status;

    /* Get current window id */
    window_id = SDL_GetFocusWindow();
    if (window_id <= 0) {
        SDL_MouseData *mdata = NULL;

        /* If there is no current window, then someone calls this function */
        /* to set global mouse settings during SDL initialization          */
        if (cursor != NULL) {
            mdata = (SDL_MouseData *) cursor->mouse->driverdata;
            didata = (SDL_DisplayData *) mdata->didata;
            if ((didata == NULL) || (mdata == NULL)) {
                return;
            }
        } else {
            /* We can't get SDL_DisplayData at this point, return fake success */
            return 0;
        }
    } else {
        /* Sanity checks */
        window = SDL_GetWindowFromID(window_id);
        if (window != NULL) {
            display = SDL_GetDisplayFromWindow(window);
            if (display != NULL) {
                didata = (SDL_DisplayData *) display->driverdata;
                if (didata == NULL) {
                    return -1;
                }
            } else {
                return -1;
            }
        } else {
            return -1;
        }
    }

    /* Check if we need to set new shape or disable cursor shape */
    if (cursor != NULL) {
        /* Retrieve GF cursor shape */
        internal_cursor = (gf_cursor_t *) cursor->driverdata;
        if (internal_cursor == NULL) {
            SDL_SetError("GF: Internal cursor data is absent");
            return -1;
        }
        if ((internal_cursor->cursor.bitmap.image0 == NULL) ||
            (internal_cursor->cursor.bitmap.image1 == NULL)) {
            SDL_SetError("GF: Cursor shape is absent");
            return -1;
        }

        /* Store last shown cursor to display data */
        didata->cursor.type = internal_cursor->type;
        didata->cursor.hotspot.x = internal_cursor->hotspot.x;
        didata->cursor.hotspot.y = internal_cursor->hotspot.y;
        if (internal_cursor->cursor.bitmap.w > SDL_VIDEO_GF_MAX_CURSOR_SIZE) {
            didata->cursor.cursor.bitmap.w = SDL_VIDEO_GF_MAX_CURSOR_SIZE;
        } else {
            didata->cursor.cursor.bitmap.w = internal_cursor->cursor.bitmap.w;
        }

        if (didata->cursor.cursor.bitmap.h > SDL_VIDEO_GF_MAX_CURSOR_SIZE) {
            didata->cursor.cursor.bitmap.h = SDL_VIDEO_GF_MAX_CURSOR_SIZE;
        } else {
            didata->cursor.cursor.bitmap.h = internal_cursor->cursor.bitmap.h;
        }

        didata->cursor.cursor.bitmap.color0 =
            internal_cursor->cursor.bitmap.color0;
        didata->cursor.cursor.bitmap.color1 =
            internal_cursor->cursor.bitmap.color1;
        didata->cursor.cursor.bitmap.stride =
            internal_cursor->cursor.bitmap.stride;
        SDL_memcpy(didata->cursor.cursor.bitmap.image0,
                   internal_cursor->cursor.bitmap.image0,
                   ((internal_cursor->cursor.bitmap.w +
                     7) / (sizeof(uint8_t) * 8)) *
                   internal_cursor->cursor.bitmap.h);
        SDL_memcpy(didata->cursor.cursor.bitmap.image1,
                   internal_cursor->cursor.bitmap.image1,
                   ((internal_cursor->cursor.bitmap.w +
                     7) / (sizeof(uint8_t) * 8)) *
                   internal_cursor->cursor.bitmap.h);

        /* Setup cursor shape */
        status = gf_cursor_set(didata->display, 0, internal_cursor);
        if (status != GF_ERR_OK) {
            if (status != GF_ERR_NOSUPPORT) {
                SDL_SetError("GF: Can't set hardware cursor shape");
                return -1;
            }
        }

        /* Enable just set cursor */
        status = gf_cursor_enable(didata->display, 0);
        if (status != GF_ERR_OK) {
            if (status != GF_ERR_NOSUPPORT) {
                SDL_SetError("GF: Can't enable hardware cursor");
                return -1;
            }
        }

        /* Set cursor visible */
        didata->cursor_visible = SDL_TRUE;
    } else {
        /* SDL requests to disable cursor */
        status = gf_cursor_disable(didata->display, 0);
        if (status != GF_ERR_OK) {
            if (status != GF_ERR_NOSUPPORT) {
                SDL_SetError("GF: Can't disable hardware cursor");
                return -1;
            }
        }

        /* Set cursor invisible */
        didata->cursor_visible = SDL_FALSE;
    }

    /* New cursor shape is set */
    return 0;
}

void
gf_movecursor(SDL_Cursor * cursor)
{
    SDL_VideoDisplay *display;
    SDL_DisplayData *didata;
    SDL_Window *window;
    SDL_WindowID window_id;
    int32_t status;
    uint32_t xmax;
    uint32_t ymax;

    /* Get current window id */
    window_id = SDL_GetFocusWindow();
    if (window_id <= 0) {
        didata = (SDL_DisplayData *) cursor->mouse->driverdata;
        if (didata == NULL) {
            return;
        }
    } else {
        /* Sanity checks */
        window = SDL_GetWindowFromID(window_id);
        if (window != NULL) {
            display = SDL_GetDisplayFromWindow(window);
            if (display != NULL) {
                didata = (SDL_DisplayData *) display->driverdata;
                if (didata == NULL) {
                    return;
                }
            } else {
                return;
            }
        } else {
            return;
        }
    }

    /* Add checks for out of screen bounds position */
    if (cursor->mouse->x < 0) {
        cursor->mouse->x = 0;
    }
    if (cursor->mouse->y < 0) {
        cursor->mouse->y = 0;
    }

    /* Get window size to clamp maximum coordinates */
    SDL_GetWindowSize(window_id, &xmax, &ymax);
    if (cursor->mouse->x >= xmax) {
        cursor->mouse->x = xmax - 1;
    }
    if (cursor->mouse->y >= ymax) {
        cursor->mouse->y = ymax - 1;
    }

    status =
        gf_cursor_set_pos(didata->display, 0, cursor->mouse->x,
                          cursor->mouse->y);
    if (status != GF_ERR_OK) {
        if (status != GF_ERR_NOSUPPORT) {
            SDL_SetError("GF: Can't set hardware cursor position");
            return;
        }
    }
}

void
gf_freecursor(SDL_Cursor * cursor)
{
    gf_cursor_t *internal_cursor;

    if (cursor != NULL) {
        internal_cursor = (gf_cursor_t *) cursor->driverdata;
        if (internal_cursor != NULL) {
            if (internal_cursor->cursor.bitmap.image0 != NULL) {
                SDL_free((uint8_t *) internal_cursor->cursor.bitmap.image0);
            }
            if (internal_cursor->cursor.bitmap.image1 != NULL) {
                SDL_free((uint8_t *) internal_cursor->cursor.bitmap.image1);
            }
            SDL_free(internal_cursor);
        }
    }
}

void
gf_warpmouse(SDL_Mouse * mouse, SDL_WindowID windowID, int x, int y)
{
    SDL_VideoDisplay *display;
    SDL_DisplayData *didata;
    SDL_Window *window;
    uint32_t xmax;
    uint32_t ymax;
    int32_t status;

    /* Sanity checks */
    window = SDL_GetWindowFromID(windowID);
    if (window != NULL) {
        display = SDL_GetDisplayFromWindow(window);
        if (display != NULL) {
            didata = (SDL_DisplayData *) display->driverdata;
            if (didata == NULL) {
                return;
            }
        } else {
            return;
        }
    } else {
        return;
    }

    /* Add checks for out of screen bounds position */
    if (x < 0) {
        x = 0;
    }
    if (y < 0) {
        y = 0;
    }

    /* Get window size to clamp maximum coordinates */
    SDL_GetWindowSize(windowID, &xmax, &ymax);
    if (x >= xmax) {
        x = xmax - 1;
    }
    if (y >= ymax) {
        y = ymax - 1;
    }

    status = gf_cursor_set_pos(didata->display, 0, x, y);
    if (status != GF_ERR_OK) {
        if (status != GF_ERR_NOSUPPORT) {
            SDL_SetError("GF: Can't set hardware cursor position");
            return;
        }
    }
}

void
gf_freemouse(SDL_Mouse * mouse)
{
    if (mouse->driverdata == NULL) {
        return;
    }

    /* Mouse framework doesn't deletes automatically our driverdata */
    SDL_free(mouse->driverdata);
    mouse->driverdata = NULL;

    return;
}

/*****************************************************************************/
/* HIDDI handlers code                                                       */
/*****************************************************************************/
static key_packet key_last_state[SDL_HIDDI_MAX_DEVICES];

static void
hiddi_keyboard_handler(uint32_t devno, uint8_t * report_data,
                       uint32_t report_len)
{
    key_packet *packet;
    uint32_t it;
    uint32_t jt;

    packet = (key_packet *) report_data;

    /* Check for special states */
    switch (report_len) {
    case 8:                    /* 8 bytes of report length */
        {
            for (it = 0; it < 6; it++) {
                /* Check for keyboard overflow, when it can't handle */
                /* many simultaneous pressed keys */
                if (packet->codes[it] == HIDDI_KEY_OVERFLOW) {
                    return;
                }
            }
        }
        break;
    default:
        {
            /* Do not process unknown reports */
            return;
        }
        break;
    }

    /* Check if modifier key was pressed */
    if (packet->modifiers != key_last_state[devno].modifiers) {
        if (((packet->modifiers & HIDDI_MKEY_LEFT_CTRL) ==
             HIDDI_MKEY_LEFT_CTRL)
            && (key_last_state[devno].modifiers & HIDDI_MKEY_LEFT_CTRL) == 0) {
            /* Left Ctrl key was pressed */
            SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_LCTRL);
        }
        if (((packet->modifiers & HIDDI_MKEY_LEFT_CTRL) == 0) &&
            (key_last_state[devno].modifiers & HIDDI_MKEY_LEFT_CTRL) ==
            HIDDI_MKEY_LEFT_CTRL) {
            /* Left Ctrl key was released */
            SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_LCTRL);
        }
        if (((packet->modifiers & HIDDI_MKEY_LEFT_SHIFT) ==
             HIDDI_MKEY_LEFT_SHIFT)
            && (key_last_state[devno].modifiers & HIDDI_MKEY_LEFT_SHIFT) == 0) {
            /* Left Shift key was pressed */
            SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_LSHIFT);
        }
        if (((packet->modifiers & HIDDI_MKEY_LEFT_SHIFT) == 0) &&
            (key_last_state[devno].modifiers & HIDDI_MKEY_LEFT_SHIFT) ==
            HIDDI_MKEY_LEFT_SHIFT) {
            /* Left Shift key was released */
            SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_LSHIFT);
        }
        if (((packet->modifiers & HIDDI_MKEY_LEFT_ALT) == HIDDI_MKEY_LEFT_ALT)
            && (key_last_state[devno].modifiers & HIDDI_MKEY_LEFT_ALT) == 0) {
            /* Left Alt key was pressed */
            SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_LALT);
        }
        if (((packet->modifiers & HIDDI_MKEY_LEFT_ALT) == 0) &&
            (key_last_state[devno].modifiers & HIDDI_MKEY_LEFT_ALT) ==
            HIDDI_MKEY_LEFT_ALT) {
            /* Left Alt key was released */
            SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_LALT);
        }
        if (((packet->modifiers & HIDDI_MKEY_LEFT_WFLAG) ==
             HIDDI_MKEY_LEFT_WFLAG)
            && (key_last_state[devno].modifiers & HIDDI_MKEY_LEFT_WFLAG) == 0) {
            /* Left Windows flag key was pressed */
            SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_LGUI);
        }
        if (((packet->modifiers & HIDDI_MKEY_LEFT_WFLAG) == 0) &&
            (key_last_state[devno].modifiers & HIDDI_MKEY_LEFT_WFLAG) ==
            HIDDI_MKEY_LEFT_WFLAG) {
            /* Left Windows flag key was released */
            SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_LGUI);
        }
        if (((packet->modifiers & HIDDI_MKEY_RIGHT_CTRL) ==
             HIDDI_MKEY_RIGHT_CTRL)
            && (key_last_state[devno].modifiers & HIDDI_MKEY_RIGHT_CTRL) == 0) {
            /* Right Ctrl key was pressed */
            SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_RCTRL);
        }
        if (((packet->modifiers & HIDDI_MKEY_RIGHT_CTRL) == 0) &&
            (key_last_state[devno].modifiers & HIDDI_MKEY_RIGHT_CTRL) ==
            HIDDI_MKEY_RIGHT_CTRL) {
            /* Right Ctrl key was released */
            SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_RCTRL);
        }
        if (((packet->modifiers & HIDDI_MKEY_RIGHT_SHIFT) ==
             HIDDI_MKEY_RIGHT_SHIFT)
            && (key_last_state[devno].modifiers & HIDDI_MKEY_RIGHT_SHIFT) ==
            0) {
            /* Right Shift key was pressed */
            SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_RSHIFT);
        }
        if (((packet->modifiers & HIDDI_MKEY_RIGHT_SHIFT) == 0) &&
            (key_last_state[devno].modifiers & HIDDI_MKEY_RIGHT_SHIFT) ==
            HIDDI_MKEY_RIGHT_SHIFT) {
            /* Right Shift key was released */
            SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_RSHIFT);
        }
        if (((packet->modifiers & HIDDI_MKEY_RIGHT_ALT) ==
             HIDDI_MKEY_RIGHT_ALT)
            && (key_last_state[devno].modifiers & HIDDI_MKEY_RIGHT_ALT) == 0) {
            /* Right Alt key was pressed */
            SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_RALT);
        }
        if (((packet->modifiers & HIDDI_MKEY_RIGHT_ALT) == 0) &&
            (key_last_state[devno].modifiers & HIDDI_MKEY_RIGHT_ALT) ==
            HIDDI_MKEY_RIGHT_ALT) {
            /* Right Alt key was released */
            SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_RALT);
        }
        if (((packet->modifiers & HIDDI_MKEY_RIGHT_WFLAG) ==
             HIDDI_MKEY_RIGHT_WFLAG)
            && (key_last_state[devno].modifiers & HIDDI_MKEY_RIGHT_WFLAG) ==
            0) {
            /* Right Windows flag key was pressed */
            SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_RGUI);
        }
        if (((packet->modifiers & HIDDI_MKEY_RIGHT_WFLAG) == 0) &&
            (key_last_state[devno].modifiers & HIDDI_MKEY_RIGHT_WFLAG) ==
            HIDDI_MKEY_RIGHT_WFLAG) {
            /* Right Windows flag key was released */
            SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_RGUI);
        }
    }

    /* Check each key in the press/release buffer */
    switch (report_len) {
    case 8:                    /* 8 bytes of report length */
        {
            /* Check if at least one key was unpressed */
            for (it = 0; it < 6; it++) {
                if (key_last_state[devno].codes[it] == HIDDI_KEY_UNPRESSED) {
                    /* if stored keycode is zero, find another */
                    continue;
                }
                for (jt = 0; jt < 6; jt++) {
                    /* Find stored keycode in the current pressed codes */
                    if (packet->codes[jt] == key_last_state[devno].codes[it]) {
                        /* If found then particular key state is not changed */
                        break;
                    }
                }

                /* Check if pressed key can't longer be found */
                if (jt == 6) {
                    SDL_SendKeyboardKey(0, SDL_RELEASED,
                                        key_last_state[devno].codes[it]);
                }
            }

            /* Check if at least one new key was pressed */
            for (it = 0; it < 6; it++) {
                if (packet->codes[it] == HIDDI_KEY_UNPRESSED) {
                    continue;
                }
                for (jt = 0; jt < 6; jt++) {
                    /* Find new keycode it the array of old pressed keys */
                    if (packet->codes[it] == key_last_state[devno].codes[jt]) {
                        break;
                    }
                }

                /* Check if new key was pressed */
                if (jt == 6) {
                    SDL_SendKeyboardKey(0, SDL_PRESSED, packet->codes[it]);
                }
            }
        }
    default:                   /* unknown keyboard report type */
        {
            /* Ignore all unknown reports */
        }
        break;
    }

    /* Store last state */
    key_last_state[devno] = *packet;
}

static uint32_t mouse_last_state_button[SDL_HIDDI_MAX_DEVICES];
static uint32_t collect_reports = 0;

static void
hiddi_mouse_handler(uint32_t devno, uint8_t * report_data,
                    uint32_t report_len)
{
    uint32_t it;
    uint32_t sdlbutton;

    /* We do not want to collect stored events */
    if (collect_reports == 0) {
        return;
    }

    /* Check for special states */
    switch (report_len) {
    case 8:                    /* 8 bytes of report length, usually multi-button USB mice */
        {
            mouse_packet8 *packet;
            packet = (mouse_packet8 *) report_data;

            /* Send motion event if motion really was */
            if ((packet->horizontal_precision != 0)
                || (packet->vertical_precision != 0)) {
                SDL_SendMouseMotion(0, 1, packet->horizontal_precision,
                                    packet->vertical_precision, 0);
            }

            /* Send mouse button press/release events */
            if (mouse_last_state_button[devno] != packet->buttons) {
                /* Cycle all buttons status */
                for (it = 0; it < 8; it++) {
                    /* convert hiddi button id to sdl button id */
                    switch (it) {
                    case 0:
                        {
                            sdlbutton = SDL_BUTTON_LEFT;
                        }
                        break;
                    case 1:
                        {
                            sdlbutton = SDL_BUTTON_RIGHT;
                        }
                        break;
                    case 2:
                        {
                            sdlbutton = SDL_BUTTON_MIDDLE;
                        }
                        break;
                    default:
                        {
                            sdlbutton = it + 1;
                        }
                        break;
                    }

                    /* Button pressed */
                    if (((packet->buttons & (0x01 << it)) == (0x01 << it)) &&
                        ((mouse_last_state_button[devno] & (0x01 << it)) ==
                         0x00)) {
                        SDL_SendMouseButton(0, SDL_PRESSED, sdlbutton);
                    }
                    /* Button released */
                    if (((packet->buttons & (0x01 << it)) == 0x00) &&
                        ((mouse_last_state_button[devno] & (0x01 << it)) ==
                         (0x01 << it))) {
                        SDL_SendMouseButton(0, SDL_RELEASED, sdlbutton);
                    }
                }
                mouse_last_state_button[devno] = packet->buttons;
            }

            /* Send mouse wheel events */
            if (packet->wheel != 0) {
                /* Send vertical wheel event only */
                SDL_SendMouseWheel(0, 0, packet->wheel);
            }
        }
        break;
    case 4:                    /* 4 bytes of report length, usually PS/2 mice */
        {
            mouse_packet4 *packet;
            packet = (mouse_packet4 *) report_data;

            /* Send motion event if motion really was */
            if ((packet->horizontal != 0) || (packet->vertical != 0)) {
                SDL_SendMouseMotion(0, 1, packet->horizontal,
                                    packet->vertical, 0);
            }

            /* Send mouse button press/release events */
            if (mouse_last_state_button[devno] != packet->buttons) {
                /* Cycle all buttons status */
                for (it = 0; it < 8; it++) {
                    /* convert hiddi button id to sdl button id */
                    switch (it) {
                    case 0:
                        {
                            sdlbutton = SDL_BUTTON_LEFT;
                        }
                        break;
                    case 1:
                        {
                            sdlbutton = SDL_BUTTON_RIGHT;
                        }
                        break;
                    case 2:
                        {
                            sdlbutton = SDL_BUTTON_MIDDLE;
                        }
                        break;
                    default:
                        {
                            sdlbutton = it + 1;
                        }
                        break;
                    }

                    /* Button pressed */
                    if (((packet->buttons & (0x01 << it)) == (0x01 << it)) &&
                        ((mouse_last_state_button[devno] & (0x01 << it)) ==
                         0x00)) {
                        SDL_SendMouseButton(0, SDL_PRESSED, sdlbutton);
                    }
                    /* Button released */
                    if (((packet->buttons & (0x01 << it)) == 0x00) &&
                        ((mouse_last_state_button[devno] & (0x01 << it)) ==
                         (0x01 << it))) {
                        SDL_SendMouseButton(0, SDL_RELEASED, sdlbutton);
                    }
                }
                mouse_last_state_button[devno] = packet->buttons;
            }

            /* Send mouse wheel events */
            if (packet->wheel != 0) {
                /* Send vertical wheel event only */
                SDL_SendMouseWheel(0, 0, packet->wheel);
            }
        }
        break;
    }
}

/*****************************************************************************/
/* HIDDI interacting code                                                    */
/*****************************************************************************/
static hidd_device_ident_t hiddevice = {
    HIDD_CONNECT_WILDCARD,      /* vendor id:  any */
    HIDD_CONNECT_WILDCARD,      /* product id: any */
    HIDD_CONNECT_WILDCARD,      /* version:    any */
};

static hidd_connect_parm_t hidparams =
    { NULL, HID_VERSION, HIDD_VERSION, 0, 0, &hiddevice, NULL, 0 };

static void hiddi_insertion(struct hidd_connection *connection,
                            hidd_device_instance_t * device_instance);
static void hiddi_removal(struct hidd_connection *connection,
                          hidd_device_instance_t * instance);
static void hiddi_report(struct hidd_connection *connection,
                         struct hidd_report *report, void *report_data,
                         uint32_t report_len, uint32_t flags, void *user);

static hidd_funcs_t hidfuncs =
    { _HIDDI_NFUNCS, hiddi_insertion, hiddi_removal, hiddi_report, NULL };

/* HID handle, singletone */
struct hidd_connection *connection = NULL;

/* SDL detected input device types, singletone */
static uint32_t sdl_input_devices[SDL_HIDDI_MAX_DEVICES];

static int
hiddi_register_for_reports(struct hidd_collection *col,
                           hidd_device_instance_t * device_instance)
{
    int it;
    uint16_t num_col;
    struct hidd_collection **hidd_collections;
    struct hidd_report_instance *report_instance;
    struct hidd_report *report;
    int status = 0;
    hidview_device_t *device;

    for (it = 0; it < 10 && !status; it++) {
        status =
            hidd_get_report_instance(col, it, HID_INPUT_REPORT,
                                     &report_instance);
        if (status == EOK) {
            status =
                hidd_report_attach(connection, device_instance,
                                   report_instance, 0,
                                   sizeof(hidview_device_t), &report);
            if (status == EOK) {
                device = hidd_report_extra(report);
                device->report = report;
                device->instance = report_instance;
            }
        }
    }
    hidd_get_collections(NULL, col, &hidd_collections, &num_col);

    for (it = 0; it < num_col; it++) {
        hiddi_register_for_reports(hidd_collections[it], device_instance);
    }

    return EOK;
}

static void
hiddi_insertion(struct hidd_connection *connection,
                hidd_device_instance_t * device_instance)
{
    uint32_t it;
    struct hidd_collection **hidd_collections;
    uint16_t num_col;

    /* get root level collections */
    hidd_get_collections(device_instance, NULL, &hidd_collections, &num_col);
    for (it = 0; it < num_col; it++) {
        hiddi_register_for_reports(hidd_collections[it], device_instance);
    }
}

static void
hiddi_removal(struct hidd_connection *connection,
              hidd_device_instance_t * instance)
{
    hidd_reports_detach(connection, instance);
}

static void
hiddi_report(struct hidd_connection *connection, hidd_report_t * report,
             void *report_data, uint32_t report_len, uint32_t flags,
             void *user)
{
    if (report->dev_inst->devno >= SDL_HIDDI_MAX_DEVICES) {
        /* Unknown HID device, with devno number out of supported range */
        return;
    }

    /* Check device type which generates event */
    switch (sdl_input_devices[report->dev_inst->devno]) {
    case SDL_GF_HIDDI_NONE:
        {
            /* We do not handle other devices type */
            return;
        }
        break;
    case SDL_GF_HIDDI_MOUSE:
        {
            /* Call mouse handler */
            hiddi_mouse_handler(report->dev_inst->devno, report_data,
                                report_len);
        }
        break;
    case SDL_GF_HIDDI_KEYBOARD:
        {
            /* Call keyboard handler */
            hiddi_keyboard_handler(report->dev_inst->devno, report_data,
                                   report_len);
        }
        break;
    case SDL_GF_HIDDI_JOYSTICK:
        {
            /* Call joystick handler */
        }
        break;
    }
}

static
hiddi_get_device_type(uint8_t * report_data, uint16_t report_length)
{
    hid_byte_t byte;
    uint16_t usage_page = 0;
    uint16_t usage = 0;
    uint16_t data = 0;

    while (report_length && !(usage_page && usage)) {
        if (hidp_analyse_byte(*report_data, &byte)) {
            /* Error in parser, do nothing */
        }
        data = hidp_get_data((report_data + 1), &byte);
        switch (byte.HIDB_Type) {
        case HID_TYPE_GLOBAL:
            if (!usage_page && byte.HIDB_Tag == HID_GLOBAL_USAGE_PAGE) {
                usage_page = data;
            }
            break;
        case HID_TYPE_LOCAL:
            if (!usage && byte.HIDB_Tag == HID_LOCAL_USAGE) {
                usage = data;
            }
            break;
        }
        report_data += byte.HIDB_Length + 1;
        report_length -= byte.HIDB_Length + 1;
    }

    switch (usage_page) {
    case HIDD_PAGE_DESKTOP:
        {
            switch (usage) {
            case HIDD_USAGE_MOUSE:
                {
                    return SDL_GF_HIDDI_MOUSE;
                }
                break;
            case HIDD_USAGE_JOYSTICK:
                {
                    return SDL_GF_HIDDI_JOYSTICK;
                }
                break;
            case HIDD_USAGE_KEYBOARD:
                {
                    return SDL_GF_HIDDI_KEYBOARD;
                }
                break;
            }
        }
        break;
    case HIDD_PAGE_DIGITIZER:
        {
            /* Do not handle digitizers */
        }
        break;
    case HIDD_PAGE_CONSUMER:
        {
            /* Do not handle consumer input devices */
        }
        break;
    }

    return SDL_GF_HIDDI_NONE;
}

static int32_t
hiddi_connect_devices()
{
    int32_t status;
    uint32_t it;
    uint8_t *report_data;
    uint16_t report_length;
    hidd_device_instance_t instance;

    /* Cleanup initial keys and mice state */
    SDL_memset(key_last_state, 0x00,
               sizeof(key_packet) * SDL_HIDDI_MAX_DEVICES);
    SDL_memset(mouse_last_state_button, 0x00,
               sizeof(uint32_t) * SDL_HIDDI_MAX_DEVICES);

    status = hidd_connect(&hidparams, &connection);
    if (status != EOK) {
        connection=NULL;
        return -1;
    }

    for (it = 0; it < SDL_HIDDI_MAX_DEVICES; it++) {
        /* Get device instance */
        status = hidd_get_device_instance(connection, it, &instance);
        if (status != EOK) {
            continue;
        }

        status =
            hidd_get_report_desc(connection, &instance, &report_data,
                                 &report_length);
        if (status != EOK) {
            continue;
        }

        status = hiddi_get_device_type(report_data, report_length);
        sdl_input_devices[it] = status;

        free(report_data);
    }

    /* Disconnect from HID server */
    status = hidd_disconnect(connection);
    if (status != EOK) {
        connection=NULL;
        return -1;
    }

    /* Add handlers for HID devices */
    hidparams.funcs = &hidfuncs;

    status = hidd_connect(&hidparams, &connection);
    if (status != EOK) {
        connection=NULL;
        return -1;
    }

    return 0;
}

static int32_t
hiddi_disconnect_devices()
{
    int32_t status;

    hiddi_disable_mouse();

    /* Disconnect from HID server */
    if (connection!=NULL)
    {
        status = hidd_disconnect(connection);
        if (status != EOK) {
            return -1;
        }
    }
}

void
hiddi_enable_mouse()
{
    collect_reports = 1;
}

void
hiddi_disable_mouse()
{
    collect_reports = 0;
}
