#include <lvgl/devices/display.h>
#include <lvgl/devices/keyboard.h>
#include <lvgl/devices/pointer.h>
#include <lvgl/lvgl.h>
#include <tactility/device.h>
#include <tactility/drivers/display.h>
#include <tactility/drivers/keyboard.h>
#include <tactility/drivers/pointer.h>
#include <tactility/log.h>

#include <lvgl.h>

#define TAG "lvgl"

void lvgl_devices_attach() {
    lvgl_lock();

    lv_disp_t* lvgl_display = NULL;
    bool display_updates_slowly = false;
    struct Device* kernel_display_device = NULL;
    device_get_first_by_type(&DISPLAY_TYPE, &kernel_display_device);

    // Placeholder drivers (boards not yet migrated to the kernel display driver) register with a
    // NULL api: they exist so the devicetree node resolves, but have nothing for LVGL to bind to.
    if (kernel_display_device != NULL && device_get_driver(kernel_display_device)->api == NULL) {
        device_put(kernel_display_device);
        kernel_display_device = NULL;
    }

    if (kernel_display_device != NULL) {
        uint16_t vres = display_get_resolution_y(kernel_display_device);
        enum DisplayColorFormat color_format = display_get_color_format(kernel_display_device);
        bool swap_bytes = color_format == DISPLAY_COLOR_FORMAT_RGB565_SWAPPED ||
            color_format == DISPLAY_COLOR_FORMAT_BGR565_SWAPPED ||
            color_format == DISPLAY_COLOR_FORMAT_BGR565;
        bool display_requires_full_frame = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_REQUIRES_FULL_FRAME);
        display_updates_slowly = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_SLOW_REFRESH);
        // Without CAP_SWAP_XY the driver can't rotate 90/270 in hardware (display_swap_xy() is
        // null and silently skipped by lvgl_display_apply_rotation()) - LVGL would still switch
        // its own logical w/h for those rotations, mismatching the panel's fixed physical
        // orientation (e.g. RGB/DPI panels, whose video timing is fixed at panel-init time).
        // sw_rotate makes LVGL rotate the rendered pixels in software instead, so the driver
        // itself is never asked to do something it can't.
        bool can_hw_rotate = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_SWAP_XY) &&
            display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_MIRROR);
        struct LvglDisplayConfig lvgl_display_config = {
            .buffer_height = vres > 10 ? vres / 10 : vres,
            .sw_rotate = !can_hw_rotate,
            .swap_bytes = swap_bytes,
            .force_full_frame = display_requires_full_frame
        };
        if (lvgl_display_add(kernel_display_device, &lvgl_display_config, &lvgl_display) == ERROR_NONE) {
            LOG_I(TAG, "Bound %s to LVGL", kernel_display_device->name);
        } else {
            LOG_E(TAG, "Failed to bind %s to LVGL", kernel_display_device->name);
        }
        device_put(kernel_display_device);
    }

    struct Device* kernel_pointer_device = NULL;
    device_get_first_by_type(&POINTER_TYPE, &kernel_pointer_device);
    if (kernel_pointer_device != NULL && device_get_driver(kernel_pointer_device)->api == NULL) {
        device_put(kernel_pointer_device);
        kernel_pointer_device = NULL;
    }
    if (kernel_pointer_device != NULL) {
        lv_indev_t* lvgl_pointer_device;
        if (lvgl_pointer_add(kernel_pointer_device, lvgl_display, &lvgl_pointer_device) == ERROR_NONE) {
            LOG_I(TAG, "Bound %s to LVGL", kernel_pointer_device->name);
            // Slow panels cause taps to be missed due to the long update time, prevent that
            if (display_updates_slowly ) {
                lv_indev_set_long_press_time(lvgl_pointer_device, 2000);
            }
        } else {
            LOG_E(TAG, "Failed to bind %s to LVG", kernel_pointer_device->name);
        }
        device_put(kernel_pointer_device);
    }

    struct Device* kernel_keyboard_device = NULL;
    device_get_first_by_type(&KEYBOARD_TYPE, &kernel_keyboard_device);
    lv_indev_t* lvgl_keyboard_device;
    if (kernel_keyboard_device != NULL) {
        if (lvgl_keyboard_add(kernel_keyboard_device, lvgl_display, &lvgl_keyboard_device) == ERROR_NONE) {
            LOG_I(TAG, "Bound %s to LVGL", kernel_keyboard_device->name);
        } else {
            LOG_E(TAG, "Failed to bind %s to LVGL", kernel_keyboard_device->name);
        }
        device_put(kernel_keyboard_device);
    }

    lvgl_unlock();
}

void lvgl_devices_detach() {
    lvgl_lock();

    lv_indev_t* device = lv_indev_get_next(NULL);
    while (device != NULL) {
        lv_indev_type_t type = lv_indev_get_type(device);
        if (type == LV_INDEV_TYPE_POINTER) {
            lvgl_pointer_remove(device);
        } else if (type == LV_INDEV_TYPE_KEYPAD) {
            lvgl_keyboard_remove(device);
        } else {
            lv_indev_delete(device);
        }
        // Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt
        device = lv_indev_get_next(NULL);
    }

    lv_disp_t* display = lv_disp_get_next(NULL);
    while (display != NULL) {
        lvgl_display_remove(display);
        display = lv_disp_get_next(NULL);
    }

    lvgl_unlock();
}
