#if MICROPY_PY_LVGL
#include "lvgl/lvgl.h"
// #include "lvgl/src/hal/lv_hal_disp.h"
#include "mpy_m5gfx.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "driver/ppa.h"
#include "esp_heap_caps.h"
#include "esp_private/esp_cache_private.h"
#include <string.h>

#if SOC_PPA_SUPPORTED
#define ALIGN_UP_BY(num, align) (((num) + ((align)-1)) & ~((align)-1))

extern ppa_client_handle_t ppa_srm_handle;
static size_t data_cache_line_size = 0;

static lv_color_t* ppa_buf = NULL;

IRAM_ATTR static void rotate_copy_pixel(const uint16_t* from, uint16_t* to, uint16_t x_start, uint16_t y_start,
                                        uint16_t x_end, uint16_t y_end, uint16_t w, uint16_t h, uint16_t rotation)
{
    ppa_srm_rotation_angle_t ppa_rotation;
    int x_offset = 0, y_offset = 0;

    // Determine rotation settings once and reuse
    switch (rotation) {
        case 90:
            ppa_rotation = PPA_SRM_ROTATION_ANGLE_270;
            x_offset     = h - y_end - 1;
            y_offset     = x_start;
            break;
        case 180:
            ppa_rotation = PPA_SRM_ROTATION_ANGLE_180;
            x_offset     = w - x_end - 1;
            y_offset     = h - y_end - 1;
            break;
        case 270:
            ppa_rotation = PPA_SRM_ROTATION_ANGLE_90;
            x_offset     = y_start;
            y_offset     = w - x_end - 1;
            break;
        default:
            ppa_rotation = PPA_SRM_ROTATION_ANGLE_0;
            break;
    }

    esp_cache_get_alignment(MALLOC_CAP_SPIRAM, &data_cache_line_size);

    // Fill operation config for PPA rotation, without recalculating each time
    ppa_srm_oper_config_t oper_config;
    memset(&oper_config, 0, sizeof(oper_config));

    oper_config.in.buffer         = from,
    oper_config.in.pic_w          = w,
    oper_config.in.pic_h          = h,
    oper_config.in.block_w        = x_end - x_start + 1,
    oper_config.in.block_h        = y_end - y_start + 1,
    oper_config.in.block_offset_x = x_start,
    oper_config.in.block_offset_y = y_start,
    oper_config.in.srm_cm         = (LV_COLOR_DEPTH == 24) ? PPA_SRM_COLOR_MODE_RGB888 : PPA_SRM_COLOR_MODE_RGB565,

    oper_config.out.buffer      = to,
    oper_config.out.buffer_size = ALIGN_UP_BY(sizeof(lv_color_t) * w * h, data_cache_line_size),
    oper_config.out.pic_w = (ppa_rotation == PPA_SRM_ROTATION_ANGLE_90 || ppa_rotation == PPA_SRM_ROTATION_ANGLE_270) ? h : w,
    oper_config.out.pic_h = (ppa_rotation == PPA_SRM_ROTATION_ANGLE_90 || ppa_rotation == PPA_SRM_ROTATION_ANGLE_270) ? w : h,
    oper_config.out.block_offset_x = x_offset,
    oper_config.out.block_offset_y = y_offset,
    oper_config.out.srm_cm         = (LV_COLOR_DEPTH == 24) ? PPA_SRM_COLOR_MODE_RGB888 : PPA_SRM_COLOR_MODE_RGB565,

    oper_config.rotation_angle = ppa_rotation,
    oper_config.scale_x        = 1.0,
    oper_config.scale_y        = 1.0,
    oper_config.rgb_swap       = 0,
    oper_config.byte_swap      = 0,
    oper_config.mode           = PPA_TRANS_MODE_BLOCKING,

    ppa_do_scale_rotate_mirror(ppa_srm_handle, &oper_config);
}


void lvgl_port_rotate_area(lv_display_t* disp, lv_area_t* area)
{
    lv_display_rotation_t rotation = lv_display_get_rotation(disp);

    int32_t w = lv_area_get_width(area);
    int32_t h = lv_area_get_height(area);

    int32_t hres = lv_display_get_horizontal_resolution(disp);
    int32_t vres = lv_display_get_vertical_resolution(disp);
    if (rotation == LV_DISPLAY_ROTATION_90 || rotation == LV_DISPLAY_ROTATION_270) {
        vres = lv_display_get_horizontal_resolution(disp);
        hres = lv_display_get_vertical_resolution(disp);
    }

    switch (rotation) {
        case LV_DISPLAY_ROTATION_0:
            return;
        case LV_DISPLAY_ROTATION_90:
            area->y2 = vres - area->x1 - 1;
            area->x1 = area->y1;
            area->x2 = area->x1 + h - 1;
            area->y1 = area->y2 - w + 1;
            break;
        case LV_DISPLAY_ROTATION_180:
            area->y2 = vres - area->y1 - 1;
            area->y1 = area->y2 - h + 1;
            area->x2 = hres - area->x1 - 1;
            area->x1 = area->x2 - w + 1;
            break;
        case LV_DISPLAY_ROTATION_270:
            area->x1 = hres - area->y2 - 1;
            area->y2 = area->x2;
            area->x2 = area->x1 + h - 1;
            area->y1 = area->y2 - w + 1;
            break;
    }
}
#endif

void gfx_lvgl_flush(lv_display_t *disp_drv, const lv_area_t * area, uint8_t * px_map)
{
    mp_obj_t gfx_obj = mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_display));
    M5GFX *lvgl_gfx = (M5GFX *)((((gfx_obj_t *)MP_OBJ_TO_PTR(gfx_obj))->gfx));
    if (lvgl_gfx == nullptr) {
        return;
    }

#if SOC_PPA_SUPPORTED

    int offsetx1 = area->x1;
    int offsetx2 = area->x2;
    int offsety1 = area->y1;
    int offsety2 = area->y2;

    lv_display_rotation_t rotation = lv_display_get_rotation(disp_drv);

    if (rotation > LV_DISPLAY_ROTATION_0) {
            if (ppa_buf == NULL) {
                size_t buf_size = lv_display_get_draw_buf_size(disp_drv);
                ppa_buf = (lv_color_t*)heap_caps_malloc(buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
                if (ppa_buf == NULL) {
                    ESP_LOGE("gfx_lvgl_flush", "Failed to allocate memory for PPA buffer");
                    return;
                }
            }
            int32_t ww           = lv_area_get_width(area);
            int32_t hh           = lv_area_get_height(area);
            lv_color_format_t cf = lv_display_get_color_format(disp_drv);
            uint32_t w_stride    = lv_draw_buf_width_to_stride(ww, cf);
            uint32_t h_stride    = lv_draw_buf_width_to_stride(hh, cf);
            if (rotation == LV_DISPLAY_ROTATION_180) {
                lv_draw_sw_rotate(px_map, ppa_buf, hh, ww, h_stride, h_stride,
                                  LV_DISPLAY_ROTATION_180, cf);
            } else if (rotation == LV_DISPLAY_ROTATION_90) {
                // printf("%ld %ld\n", w_stride, h_stride);
                // lv_draw_sw_rotate(px_map, ppa_buf, ww, hh, w_stride, h_stride,
                //                   LV_DISPLAY_ROTATION_90, cf);
                // rotate_copy_pixel((uint16_t*)px_map, (uint16_t*)ppa_buf, offsetx1, offsety1,
                //                   offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, 270);
                rotate_copy_pixel((uint16_t*)px_map, (uint16_t*)ppa_buf, 0, 0, offsetx2 - offsetx1,
                                  offsety2 - offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, 270);
            } else if (rotation == LV_DISPLAY_ROTATION_270) {
                lv_draw_sw_rotate(px_map, ppa_buf, ww, hh, w_stride, h_stride,
                                  LV_DISPLAY_ROTATION_270, cf);
            }
            px_map = (uint8_t*)ppa_buf;
            lvgl_port_rotate_area(disp_drv, (lv_area_t*)area);
            offsetx1 = area->x1;
            offsetx2 = area->x2;
            offsety1 = area->y1;
            offsety2 = area->y2;
    }
#endif

    int w = (area->x2 - area->x1 + 1);
    int h = (area->y2 - area->y1 + 1);

    // lvgl_gfx->startWrite();
    // lvgl_gfx->setAddrWindow(area->x1, area->y1, w, h);
    // lvgl_gfx->writePixels((lgfx::rgb565_t *)px_map, w * h);
    // lvgl_gfx->endWrite();

    lvgl_gfx->pushImage(area->x1, area->y1, w, h, (lgfx::rgb565_t *)px_map);
    lv_display_flush_ready(disp_drv);
}

bool gfx_lvgl_touch_read(lv_disp_t *indev_drv, lv_indev_data_t *data)
{
    M5.update();

    if (M5.Touch.getCount()) {
        auto tp = M5.Touch.getDetail(0);
        data->point.x = tp.x;
        data->point.y = tp.y;
        data->state = LV_INDEV_STATE_PRESSED;
    } else {
        data->state = LV_INDEV_STATE_RELEASED;
    }

    return true;
}


void user_lvgl_flush(lv_disp_t *disp_drv, const lv_area_t *area, uint8_t *px_map)
{

    int w = (area->x2 - area->x1 + 1);
    int h = (area->y2 - area->y1 + 1);

    user_panel.startWrite();
    user_panel.setAddrWindow(area->x1, area->y1, w, h);
    user_panel.writePixels((lgfx::rgb565_t *)px_map, w * h);
    user_panel.endWrite();
    lv_display_flush_ready(disp_drv);
}


#endif
