#include "k230_display.h"
#include "mpi_gsdma_api.h"

#include <stdio.h>
#include <string.h>
#include <unistd.h> // For usleep

#include <algorithm>
#include <iostream>

Display::Display()
{
    // Initialize all members
    memset(&m_connector_info, 0, sizeof(m_connector_info));
}

Display::~Display()
{
    if (m_is_inited) {
        deinit();
    }
}

// =================================================================================
// Core Display Methods
// =================================================================================

int Display::init(k_connector_type type, int osd_num, int width, int height, int fps)
{
    if (m_is_inited) {
        printf("Display already initialized.\n");
        return 0;
    }

    m_osd_layer_num  = osd_num;
    m_connector_type = type;

    // 1. Get Connector Info
    if (kd_mpi_get_connector_info(m_connector_type, &m_connector_info) != K_SUCCESS) {
        printf("Get display device info failed for type %d.\n", type);
        return -1;
    }

    // 2. Handle Virtual Device configuration
    if (m_connector_type == VIRTUAL_DISPLAY_DEVICE) {
        int w = (width != 0) ? width : 640;
        int h = (height != 0) ? height : 480;
        int f = (fps != 0) ? fps : 90;

        if (w & 7) {
            printf("width must be an integral multiple of 8 pixels for virtual device\n");
            return -1;
        }

        m_connector_info.resolution.hdisplay = w;
        m_connector_info.resolution.vdisplay = h;
        m_connector_info.resolution.pclk     = f;
    }

    m_width  = m_connector_info.resolution.hdisplay;
    m_height = m_connector_info.resolution.vdisplay;

    if (m_width == 0 || m_height == 0) {
        printf("Invalid display configuration (0x0 resolution).\n");
        return -1;
    }

    // 3. Power on and init connector (omitted for brevity, assume success)
    int connector_fd = kd_mpi_connector_open(m_connector_info.connector_name);
    if (connector_fd < 0) {
        printf("Can't open display device\n");
        return -1;
    }
    kd_mpi_connector_power_set(connector_fd, K_TRUE);
    kd_mpi_connector_init(connector_fd, m_connector_info);
    kd_mpi_connector_close(connector_fd);

    // 4. Hardware specific fixups (if necessary)
    if (m_connector_type == ST7701_V1_MIPI_2LAN_368X544_60FPS && m_height == 552) {
        m_height = 544;
    }

    // Ensure at least one OSD layer buffer is provisioned
    if (m_osd_layer_num < 1) {
        m_osd_layer_num = 1;
    }

    // 5. Configure VB pool for OSD buffers
    if (!_config_vb(m_width * m_height * 4, m_osd_layer_num, VB_REMAP_MODE_NOCACHE)) {
        printf("Display configure buffer for OSD failed.\n");
        return -1;
    }

    // 7. Bind/configure any layers that were set up before init
    for (int i = 0; i < K_VO_MAX_CHN_NUMS; ++i) {
        if (m_layer_bind_cfgs[i]) {
            m_layer_bind_cfgs[i]->bind();
            if (i <= LAYER_VIDEO2) {
                _config_video_layer(m_layer_bind_cfgs[i]->layer_config);
            } else {
                _config_osd_layer(m_layer_bind_cfgs[i]->layer_config);
            }
        }
    }

    m_is_inited = true;

    return 0;
}

int Display::deinit()
{
    if (!m_is_inited) {
        return 0;
    }

    // 1. Disable all layers
    for (int i = 0; i < K_VO_MAX_CHN_NUMS; ++i) {
        disable_layer(i);
    }

    // 2. Power off connector (omitted for brevity, assume implementation remains)
    int connector_fd = kd_mpi_connector_open(m_connector_info.connector_name);
    if (connector_fd >= 0) {
        kd_mpi_connector_power_set(connector_fd, K_FALSE);
        kd_mpi_connector_close(connector_fd);
    }

    // 3. Reset display
    kd_display_reset();

    // 4. Unbind all layers
    for (auto& bind_cfg : m_layer_bind_cfgs) {
        if (bind_cfg) {
            bind_cfg->unbind();
            bind_cfg.reset();
        }
    }

    // 6. Release all layer buffers
    for (auto& buf : m_layer_disp_buffers) {
        buf.reset();
    }

    // 7. Destroy VO VB pool
    if (m_pool_id != VB_INVALID_POOLID) {
        kd_mpi_vb_destory_pool(m_pool_id);
        m_pool_id = VB_INVALID_POOLID;
    }

    // 8. Reset state
    m_osd_layer_num  = 1;
    m_connector_type = CONNECTOR_BUTT;
    m_width          = 0;
    m_height         = 0;
    m_is_inited      = false;

    return 0;
}

int Display::bind_layer(std::tuple<int, int, int> src, int layer, std::tuple<int, int, int, int> rect, int pixelFormat,
                        int flag, int alpha)
{
    if (layer < 0 || layer >= K_VO_MAX_CHN_NUMS) {
        printf("layer(%d) out of range\n", layer);
        return -1;
    }

    if (m_layer_bind_cfgs[layer]) {
        unbind_layer(layer);
        printf("bind_layer: layer(%d) has been bound, auto unbind it.\n", layer);
    }

    m_layer_bind_cfgs[layer]               = std::make_unique<BindConfig>();
    m_layer_bind_cfgs[layer]->src_chn      = { (k_mod_id)std::get<0>(src), (k_s32)std::get<1>(src), (k_s32)std::get<2>(src) };
    m_layer_bind_cfgs[layer]->dst_chn      = { K_ID_VO, 0, (k_s32)layer };
    m_layer_bind_cfgs[layer]->layer_config = { layer, rect, pixelFormat, flag, alpha, false };

    if (m_is_inited) {
        m_layer_bind_cfgs[layer]->bind();
        if (layer <= LAYER_VIDEO2) {
            _config_video_layer(m_layer_bind_cfgs[layer]->layer_config);
        } else {
            _config_osd_layer(m_layer_bind_cfgs[layer]->layer_config);
        }
    }

    return 0;
}

int Display::unbind_layer(int layer)
{
    if (layer < 0 || layer >= K_VO_MAX_CHN_NUMS) {
        printf("layer(%d) out of range\n", layer);
        return -1;
    }

    if (m_layer_bind_cfgs[layer]) {
        m_layer_bind_cfgs[layer]->unbind();
        m_layer_bind_cfgs[layer].reset();
        return 0;
    } else {
        printf("unbind layer(%d) failed, not bound.\n", layer);
        return -1;
    }
}

void Display::disable_layer(int layer)
{
    if (layer < 0 || layer >= K_VO_MAX_CHN_NUMS) {
        return;
    }

    if (m_layer_cfgs[layer].configured) {
        if (layer <= LAYER_VIDEO2) {
            kd_mpi_vo_disable_video_layer((k_vo_layer)layer);
        } else {
            kd_mpi_vo_osd_disable((k_vo_osd)(layer - LAYER_OSD0));
        }
    }

    m_layer_cfgs[layer] = LayerConfig(); // Reset to default
}

int Display::fps()
{
    if (m_connector_type == VIRTUAL_DISPLAY_DEVICE) {
        return m_connector_info.resolution.pclk;
    } else {
        k_u32 htotal = m_connector_info.resolution.htotal;
        k_u32 vtotal = m_connector_info.resolution.vtotal;
        k_u32 pclk   = m_connector_info.resolution.pclk;

        if (htotal == 0 || vtotal == 0)
            return 0;
        int fps = pclk * 1000 / htotal / vtotal;
        return (fps < 200) ? fps : 0;
    }
}

int Display::_config_video_layer(const LayerConfig& config)
{
    if (m_layer_cfgs[config.layer] == config && m_layer_cfgs[config.layer].configured) {
        // printf("layer(%d) config not changed.\n", config.layer);
        return 0;
    }

    if (config.layer < LAYER_VIDEO1 || config.layer > LAYER_VIDEO2) {
        printf("layer(%d) out of range for video\n", config.layer);
        return -1;
    }

    if (config.pix_format != PIXEL_FORMAT_YUV_SEMIPLANAR_420) {
        printf("bind video layer only support format PIXEL_FORMAT_YUV_SEMIPLANAR_420\n");
        return -1;
    }

    disable_layer(config.layer);

    int width  = std::get<2>(config.rect);
    int height = std::get<3>(config.rect);
    if (width == 0)
        width = m_width;
    if (height == 0)
        height = m_height;

    // Video layer 2 does not support rotation
    if (config.layer == LAYER_VIDEO2 && (config.flag & 0xF) != K_ROTATION_0) {
        printf("Video layer 2 does not support rotation.\n");
        return -1;
    }

    // Swap width/height if rotated 90 or 270
    if ((config.flag & K_ROTATION_90) || (config.flag & K_ROTATION_270)) {
        std::swap(width, height);
    }

    if (width == 0 || height == 0 || (width & 7)) {
        printf("width must be non-zero and an integral multiple of 8 pixels\n");
        return -1;
    }

    k_vo_point offset   = { (k_u32)std::get<0>(config.rect), (k_u32)std::get<1>(config.rect) };
    k_vo_size  img_size = { (k_u32)width, (k_u32)height };

    k_vo_video_layer_attr video_attr;
    memset(&video_attr, 0, sizeof(video_attr));
    video_attr.pixel_format = (k_pixel_format)config.pix_format;
    video_attr.func         = config.flag;
    video_attr.stride       = (width / 8 - 1) + ((height - 1) << 16);

    memcpy(&video_attr.display_rect, &offset, sizeof(k_vo_point));
    memcpy(&video_attr.img_size, &img_size, sizeof(k_vo_size));

    kd_mpi_vo_set_video_layer_attr((k_vo_layer)config.layer, &video_attr);
    kd_mpi_vo_enable_video_layer((k_vo_layer)config.layer);

    m_layer_cfgs[config.layer]            = config;
    m_layer_cfgs[config.layer].configured = true;

    return 0;
}

int Display::_config_osd_layer(const LayerConfig& config)
{
    if (m_layer_cfgs[config.layer] == config && m_layer_cfgs[config.layer].configured) {
        // printf("layer(%d) config not changed.\n", config.layer);
        return 0;
    }

    disable_layer(config.layer);

    int width  = std::get<2>(config.rect);
    int height = std::get<3>(config.rect);
    if (width == 0)
        width = m_width;
    if (height == 0)
        height = m_height;

    if (width == 0 || height == 0 || (width & 7)) {
        printf("width must be non-zero and an integral multiple of 8 pixels\n");
        return -1;
    }

    k_vo_point offset   = { (k_u32)std::get<0>(config.rect), (k_u32)std::get<1>(config.rect) };
    k_vo_size  img_size = { (k_u32)width, (k_u32)height };

    k_vo_video_osd_attr osd_attr;
    memset(&osd_attr, 0, sizeof(osd_attr));
    osd_attr.global_alptha = config.alpha;
    osd_attr.pixel_format  = (k_pixel_format)config.pix_format;

    switch (config.pix_format) {
    case PIXEL_FORMAT_ARGB_8888:
    case PIXEL_FORMAT_ABGR_8888:
    case PIXEL_FORMAT_BGRA_8888:
        osd_attr.stride = (width * 4) / 8;
        break;
    case PIXEL_FORMAT_RGB_888:
    case PIXEL_FORMAT_BGR_888:
        osd_attr.stride = (width * 3) / 8;
        break;
    case PIXEL_FORMAT_RGB_565_LE:
    case PIXEL_FORMAT_BGR_565_LE:
        osd_attr.stride = (width * 2) / 8;
        break;
    case PIXEL_FORMAT_RGB_MONOCHROME_8BPP:
        osd_attr.stride = width / 8;
        break;
    default:
        printf("osd layer not support pix_format (%d)\n", config.pix_format);
        return -1;
    }

    memcpy(&osd_attr.display_rect, &offset, sizeof(k_vo_point));
    memcpy(&osd_attr.img_size, &img_size, sizeof(k_vo_size));

    kd_mpi_vo_set_video_osd_attr((k_vo_osd)(config.layer - LAYER_OSD0), &osd_attr);

    m_layer_cfgs[config.layer] = config;
    // Note: configured flag is set to true in show() after frame is inserted
    return 0;
}

int Display::_config_layer(int layer, std::tuple<int, int, int, int> rect, int pix_format, int flag, int alpha)
{
    if (layer < LAYER_VIDEO1 || layer > LAYER_OSD3) {
        printf("layer(%d) out of range\n", layer);
        return -1;
    }
    if ((std::get<2>(rect) & 7)) {
        printf("width must be an integral multiple of 8 pixels\n");
        return -1;
    }

    LayerConfig config = { layer, rect, pix_format, flag, alpha, false };
    if (layer <= LAYER_VIDEO2) {
        return _config_video_layer(config);
    } else {
        return _config_osd_layer(config);
    }
}

bool Display::_config_vb(k_u32 blk_size, int blk_cnt, k_vb_remap_mode mode)
{
    k_s32 pool_id = VB_INVALID_POOLID;

    if (VB_INVALID_POOLID == (pool_id = kd_mpi_vb_create_pool_ex(blk_size, blk_cnt, VB_REMAP_MODE_NOCACHE))) {
        return false;
    }
    m_pool_id = pool_id;

    return true;
}

int Display::show(k_video_frame_info& src_frame, std::tuple<int, int> pos, int layer, int alpha, int flag)
{
    if (layer < LAYER_OSD0 || layer > LAYER_OSD3) {
        printf("layer(%d) is out of range for show(). Use OSD layers.\n", layer);
        return -1;
    }

    int   x                = std::get<0>(pos);
    int   y                = std::get<1>(pos);
    int   width            = src_frame.v_frame.width;
    int   height           = src_frame.v_frame.height;
    int   pixelformat      = src_frame.v_frame.pixel_format;
    k_u32 stride_bytes     = 0;
    k_u32 src_stride_bytes = src_frame.v_frame.stride[0];
    k_u32 src_data_size;
    k_u32 dst_data_size;

    k_video_frame_info frame_to_insert;

    switch (pixelformat) {
    case PIXEL_FORMAT_ARGB_8888:
    case PIXEL_FORMAT_ABGR_8888:
    case PIXEL_FORMAT_BGRA_8888:
        stride_bytes = 4;
        break;
    case PIXEL_FORMAT_RGB_888:
    case PIXEL_FORMAT_BGR_888:
        stride_bytes = 3;
        break;
    case PIXEL_FORMAT_RGB_565_LE:
    case PIXEL_FORMAT_BGR_565_LE:
        stride_bytes = 2;
        break;
    case PIXEL_FORMAT_RGB_MONOCHROME_8BPP:
        stride_bytes = 1;
        break;
    default:
        printf("Image format(%d) not support\n", pixelformat);
        return -1;
    }

    if (width & 7) {
        printf("Image width must be an integral multiple of 8 pixels\n");
        return -1;
    }
    src_data_size = src_stride_bytes * height;
    dst_data_size = m_width * m_height * 4;

    if (src_data_size > dst_data_size) {
        printf("Image size is too large\n");
        return -1;
    }

    if (!m_layer_disp_buffers[layer]) {
        m_layer_disp_buffers[layer] = std::make_unique<VbBuffer>();

        if (!m_layer_disp_buffers[layer]->get(dst_data_size, m_pool_id) || !m_layer_disp_buffers[layer]->mmap()) {
            printf("get display buffer failed\n");
            m_layer_disp_buffers[layer].reset();
            return -1;
        }
    }

    if (src_frame.v_frame.virt_addr[0]) {
        kd_mpi_sys_mmz_flush_cache(src_frame.v_frame.phys_addr[0], (void*)src_frame.v_frame.virt_addr[0], src_data_size);
    }

    if (flag != K_ROTATION_0) {
        k_gdma_chn_cfg_t cfg;
        int              rotate_flag = 0;

        VbBuffer* disp_buf = m_layer_disp_buffers[layer].get();
        if (!disp_buf) {
            printf("failed to get disp_buf");

            return -1;
        }

        if (flag & K_ROTATION_0) {
            rotate_flag = GDMA_ROTATE_DEGREE_0;
        } else if (flag & K_ROTATION_90) {
            rotate_flag = GDMA_ROTATE_DEGREE_90;

            std::swap(width, height);
        } else if (flag & K_ROTATION_180) {
            rotate_flag = GDMA_ROTATE_DEGREE_180;
        } else if (flag & K_ROTATION_270) {
            rotate_flag = GDMA_ROTATE_DEGREE_270;

            std::swap(width, height);
        }

        if (flag & K_VO_MIRROR_HOR) {
            rotate_flag |= GDMA_ROTATE_MIRROR_HOR;
        } else if (flag & K_VO_MIRROR_VER) {
            rotate_flag |= GDMA_ROTATE_MIRROR_VER;
        } else if (flag & K_VO_MIRROR_BOTH) {
            rotate_flag |= GDMA_ROTATE_MIRROR_HOR;
            rotate_flag |= GDMA_ROTATE_MIRROR_VER;
        }

        cfg.rotation = static_cast<k_gdma_rotation_e>(rotate_flag);

        if (K_SUCCESS != kd_mpi_gsdma_send_frame(&cfg, &src_frame, &frame_to_insert, disp_buf->handle, 1000)) {
            printf("gdma rotate failed\n");

            return -1;
        }
    } else {
        memcpy(&frame_to_insert, &src_frame, sizeof(frame_to_insert));
    }

    _config_layer(layer, { x, y, width, height }, pixelformat, flag, alpha);

    if (!m_layer_cfgs[layer].configured) {
        kd_mpi_vo_osd_enable((k_vo_osd)(layer - LAYER_OSD0));
        m_layer_cfgs[layer].configured = true;
    }

    kd_mpi_vo_chn_insert_frame((k_u32)layer, &frame_to_insert);

    return 0;
}
