/**
 * @file lv_sdl_mouse.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_sdl_mouse.h"
#if LV_USE_SDL

#include "../../core/lv_group.h"
#include "../../stdlib/lv_string.h"
#include "lv_sdl_private.h"

/*********************
 *      DEFINES
 *********************/

#ifndef KEYBOARD_BUFFER_SIZE
    #define KEYBOARD_BUFFER_SIZE 32
#endif

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void sdl_mouse_read(lv_indev_t * indev, lv_indev_data_t * data);
static void release_indev_cb(lv_event_t * e);

/**********************
 *  STATIC VARIABLES
 **********************/

typedef struct {
    int16_t last_x;
    int16_t last_y;
    bool left_button_down;
#if LV_SDL_MOUSEWHEEL_MODE == LV_SDL_MOUSEWHEEL_MODE_CROWN
    int32_t diff;
#endif
} lv_sdl_mouse_t;

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

lv_indev_t * lv_sdl_mouse_create(void)
{
    lv_sdl_mouse_t * dsc = lv_malloc_zeroed(sizeof(lv_sdl_mouse_t));
    LV_ASSERT_MALLOC(dsc);
    if(dsc == NULL) return NULL;

    lv_indev_t * indev = lv_indev_create();
    LV_ASSERT_MALLOC(indev);
    if(indev == NULL) {
        lv_free(dsc);
        return NULL;
    }

    lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
    lv_indev_set_read_cb(indev, sdl_mouse_read);
    lv_indev_set_driver_data(indev, dsc);

    lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
    lv_indev_add_event_cb(indev, release_indev_cb, LV_EVENT_DELETE, indev);

    return indev;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

static void sdl_mouse_read(lv_indev_t * indev, lv_indev_data_t * data)
{
    lv_sdl_mouse_t * dsc = lv_indev_get_driver_data(indev);

    /*Store the collected data*/
    data->point.x = dsc->last_x;
    data->point.y = dsc->last_y;
    data->state = dsc->left_button_down ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
#if LV_SDL_MOUSEWHEEL_MODE == LV_SDL_MOUSEWHEEL_MODE_CROWN
    data->enc_diff = dsc->diff;
    dsc->diff = 0;
#endif
}

static void release_indev_cb(lv_event_t * e)
{
    lv_indev_t * indev = (lv_indev_t *) lv_event_get_user_data(e);
    lv_sdl_mouse_t * dsc = lv_indev_get_driver_data(indev);
    if(dsc) {
        lv_indev_set_driver_data(indev, NULL);
        lv_indev_set_read_cb(indev, NULL);
        lv_free(dsc);
        LV_LOG_INFO("done");
    }
}

void lv_sdl_mouse_handler(SDL_Event * event)
{
    uint32_t win_id = UINT32_MAX;
    switch(event->type) {
        case SDL_MOUSEBUTTONUP:
        case SDL_MOUSEBUTTONDOWN:
            win_id = event->button.windowID;
            break;
        case SDL_MOUSEMOTION:
            win_id = event->motion.windowID;
            break;
#if LV_SDL_MOUSEWHEEL_MODE == LV_SDL_MOUSEWHEEL_MODE_CROWN
        case SDL_MOUSEWHEEL:
            win_id = event->wheel.windowID;
            break;
#endif
        case SDL_FINGERUP:
        case SDL_FINGERDOWN:
        case SDL_FINGERMOTION:
#if SDL_VERSION_ATLEAST(2,0,12)
            win_id = event->tfinger.windowID;
#endif
            break;
        case SDL_WINDOWEVENT:
            win_id = event->window.windowID;
            break;
        default:
            return;
    }

    lv_display_t * disp = lv_sdl_get_disp_from_win_id(win_id);
    if(disp == NULL) return;

    /*Find a suitable indev*/
    lv_indev_t * indev = lv_indev_get_next(NULL);
    while(indev) {
        if(lv_indev_get_read_cb(indev) == sdl_mouse_read) {
            /*If disp is NULL for any reason use the first indev with the correct type*/
            if(disp == NULL || lv_indev_get_display(indev) == disp) break;
        }
        indev = lv_indev_get_next(indev);
    }

    if(indev == NULL) return;
    lv_sdl_mouse_t * indev_dev = lv_indev_get_driver_data(indev);
    if(indev_dev == NULL) return;

    int32_t hor_res = lv_display_get_horizontal_resolution(disp);
    int32_t ver_res = lv_display_get_vertical_resolution(disp);
    float zoom = lv_sdl_window_get_zoom(disp);

    switch(event->type) {
        case SDL_WINDOWEVENT:
            if(event->window.event == SDL_WINDOWEVENT_LEAVE) {
                indev_dev->left_button_down = false;
            }
            break;
        case SDL_MOUSEBUTTONUP:
            if(event->button.button == SDL_BUTTON_LEFT)
                indev_dev->left_button_down = false;
            break;
        case SDL_WINDOWEVENT_LEAVE:
            indev_dev->left_button_down = false;
            break;
        case SDL_MOUSEBUTTONDOWN:
            if(event->button.button == SDL_BUTTON_LEFT) {
                indev_dev->left_button_down = true;
                indev_dev->last_x = (int16_t)((float)(event->motion.x) / zoom);
                indev_dev->last_y = (int16_t)((float)(event->motion.y) / zoom);
            }
            break;
        case SDL_MOUSEMOTION:
            indev_dev->last_x = (int16_t)((float)(event->motion.x) / zoom);
            indev_dev->last_y = (int16_t)((float)(event->motion.y) / zoom);
            break;

        case SDL_FINGERUP:
            indev_dev->left_button_down = false;
            indev_dev->last_x = (int16_t)((float)hor_res * event->tfinger.x / zoom);
            indev_dev->last_y = (int16_t)((float)ver_res * event->tfinger.y / zoom);
            break;
        case SDL_FINGERDOWN:
            indev_dev->left_button_down = true;
            indev_dev->last_x = (int16_t)((float)hor_res * event->tfinger.x / zoom);
            indev_dev->last_y = (int16_t)((float)ver_res * event->tfinger.y / zoom);
            break;
        case SDL_FINGERMOTION:
            indev_dev->last_x = (int16_t)((float)hor_res * event->tfinger.x / zoom);
            indev_dev->last_y = (int16_t)((float)ver_res * event->tfinger.y / zoom);
            break;
        case SDL_MOUSEWHEEL:
#if LV_SDL_MOUSEWHEEL_MODE == LV_SDL_MOUSEWHEEL_MODE_CROWN
#ifdef __EMSCRIPTEN__
            /*Emscripten scales it wrong*/
            if(event->wheel.y < 0) dsc->diff++;
            if(event->wheel.y > 0) dsc->diff--;
#else
            indev_dev->diff = -event->wheel.y;
#endif  /*__EMSCRIPTEN__*/
#endif /*LV_SDL_MOUSEWHEEL_MODE == LV_SDL_MOUSEWHEEL_MODE_CROWN*/
            break;
    }
    lv_indev_read(indev);
}

#endif /*LV_USE_SDL*/
