#include <string.h>

#include "bsp/display.h"
#include "bsp/esp32_s3_touch_amoled_1_75c.h"
#include "bsp/touch.h"
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_touch.h"
#include "esp_log.h"
#include "raytrace2.h"

static const char *TAG = "raytrace2";
static const int k_strip = 20;

static void blit_rect(esp_lcd_panel_handle_t panel, const uint16_t *fb,
                      uint16_t *pack[2], int x0, int y0, int x1, int y1)
{
    if (x0 < 0) {
        x0 = 0;
    }
    if (y0 < 0) {
        y0 = 0;
    }
    if (x1 > RT2_W) {
        x1 = RT2_W;
    }
    if (y1 > RT2_H) {
        y1 = RT2_H;
    }
    if (x0 >= x1 || y0 >= y1) {
        return;
    }
    if (x0 == 0 && x1 == RT2_W) {
        for (int y = y0; y < y1; y += k_strip) {
            int y2 = y + k_strip;
            if (y2 > y1) {
                y2 = y1;
            }
            ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(
                panel, 0, y, RT2_W, y2, fb + y * RT2_W));
        }
        return;
    }
    int w = x1 - x0;
    int which = 0;
    for (int y = y0; y < y1; y += k_strip) {
        int h = k_strip;
        if (y + h > y1) {
            h = y1 - y;
        }
        uint16_t *dst = pack[which];
        which ^= 1;
        for (int dy = 0; dy < h; dy++) {
            memcpy(dst + dy * w, fb + (y + dy) * RT2_W + x0, (size_t)w * 2u);
        }
        ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel, x0, y, x1, y + h, dst));
    }
}

extern "C" void app_main(void)
{
    const bsp_display_config_t cfg = {
        .max_transfer_sz = BSP_LCD_H_RES * BSP_LCD_V_RES * BSP_LCD_BITS_PER_PIXEL / 8,
    };
    esp_lcd_panel_handle_t panel = nullptr;
    esp_lcd_panel_io_handle_t io = nullptr;
    ESP_ERROR_CHECK(bsp_display_new(&cfg, &panel, &io));
    ESP_ERROR_CHECK(bsp_display_backlight_on());

    bsp_display_cfg_t touch_cfg = {};
    touch_cfg.touch_flags.swap_xy = 0;
    touch_cfg.touch_flags.mirror_x = 1;
    touch_cfg.touch_flags.mirror_y = 1;
    esp_lcd_touch_handle_t tp = nullptr;
    ESP_ERROR_CHECK(bsp_touch_new(&touch_cfg, &tp));

    uint16_t *pack[2];
    pack[0] = (uint16_t *)heap_caps_aligned_alloc(
        64, (size_t)RT2_W * k_strip * 2,
        MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
    pack[1] = (uint16_t *)heap_caps_aligned_alloc(
        64, (size_t)RT2_W * k_strip * 2,
        MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
    if (!pack[0] || !pack[1]) {
        ESP_LOGE(TAG, "strip alloc failed");
        return;
    }

    raytrace2_t fx;
    if (raytrace2_init(&fx) != 0) {
        ESP_LOGE(TAG, "raytrace2_init failed");
        return;
    }
    ESP_LOGI(TAG, "raytrace2 %dx%d cells, %d tiles, %d corners",
             RT2_GW, RT2_GH, fx.vis_n, fx.cvis_n);

    uint32_t last = 0;
    int down = 0;
    int dragged = 0;
    uint16_t sx = 0, sy = 0, lx = 0, ly = 0;
    while (true) {
        if (tp) {
            if (esp_lcd_touch_read_data(tp) == ESP_OK) {
                esp_lcd_touch_point_data_t pt = {};
                uint8_t n = 0;
                if (esp_lcd_touch_get_data(tp, &pt, &n, 1) == ESP_OK && n > 0) {
                    if (!down) {
                        sx = pt.x;
                        sy = pt.y;
                        dragged = 0;
                    } else {
                        int tdx = (int)pt.x - (int)sx;
                        int tdy = (int)pt.y - (int)sy;
                        if (tdx * tdx + tdy * tdy > 144) {
                            dragged = 1;
                        }
                        raytrace2_orbit(&fx, (float)((int)pt.x - (int)lx),
                                        (float)((int)pt.y - (int)ly));
                    }
                    lx = pt.x;
                    ly = pt.y;
                    down = 1;
                } else if (down) {
                    if (!dragged && sy >= RT2_H - 40) {
                        raytrace2_toggle_boxes(&fx);
                    }
                    down = 0;
                }
            }
        }
        raytrace2_frame(&fx);
        raytrace2_draw_fps(&fx);
        for (int i = 0; i < fx.flush_n; i++) {
            blit_rect(panel, fx.pixels, pack,
                      fx.flush[i].x0, fx.flush[i].y0,
                      fx.flush[i].x1, fx.flush[i].y1);
        }
        if (fx.fps != last && fx.fps != 0) {
            last = fx.fps;
            ESP_LOGI(TAG, "%lu fps", (unsigned long)fx.fps);
        }
    }
}
