/*
 * RGA 硬件缩放封装 (RV1106)
 *
 * 用 RV1106 SDK 自带的 librga (im2d API) 实现 NV12 / SBGGR10 -> NV12 的硬件缩放.
 *
 * 缩放策略: 直接拉伸 (无裁剪, 无 letterbox). 对 SC3336 2304x1296 -> 1280x720:
 *   src 16:9.0 -> dst 16:9.0, 视觉比例基本一致, 拉伸失真可忽略.
 *
 * 注: SBGGR10 模式需要先在 ISP 完成 demosaic -> NV12, RGA 直接处理 Bayer 效率不佳.
 *     实际部署中走 rockit ISP 链路得到 NV12 后再喂给本类 (src_fmt=NV12).
 *     本类保留 SBGGR10 接口用于离线测试或单帧调试.
 */
#include "hw_scaler.h"

#include <RgaApi.h>
#include <im2d.h>
#include <im2d_mpi.h>
#include <im2d_single.h>

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <fcntl.h>
#include <iostream>
#include <memory>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>

namespace webrtcpush {

#define ALIGN16(x) (((x) + 15) & ~15)
#define RV1106_CMA_HEAP_PATH "/dev/rk_dma_heap/rk-dma-heap-cma"

struct dma_heap_allocation_data {
    uint64_t len;
    uint32_t fd;
    uint32_t fd_flags;
    uint64_t heap_flags;
};

#define DMA_HEAP_IOC_MAGIC 'H'
#define DMA_HEAP_IOCTL_ALLOC \
    _IOWR(DMA_HEAP_IOC_MAGIC, 0x0, struct dma_heap_allocation_data)

static int alloc_cma_buffer(size_t size, int *out_fd, void **out_addr){
    int heap_fd = open(RV1106_CMA_HEAP_PATH, O_RDWR | O_CLOEXEC);
    if(heap_fd < 0){
        std::cerr << "[rga] open " << RV1106_CMA_HEAP_PATH
                  << " failed: " << std::strerror(errno) << "\n";
        return -1;
    }

    dma_heap_allocation_data data{};
    data.len = size;
    data.fd_flags = O_RDWR | O_CLOEXEC;
    if(ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &data) < 0){
        std::cerr << "[rga] CMA allocation failed (" << size
                  << " bytes): " << std::strerror(errno) << "\n";
        close(heap_fd);
        return -1;
    }
    close(heap_fd);

    void *addr = mmap(nullptr, size, PROT_READ | PROT_WRITE,
                      MAP_SHARED, (int)data.fd, 0);
    if(addr == MAP_FAILED){
        std::cerr << "[rga] CMA mmap failed: " << std::strerror(errno) << "\n";
        close((int)data.fd);
        return -1;
    }

    *out_fd = (int)data.fd;
    *out_addr = addr;
    return 0;
}

static void free_cma_buffer(size_t size, int *fd, void **addr){
    if(*addr && *addr != MAP_FAILED) munmap(*addr, size);
    if(*fd >= 0) close(*fd);
    *addr = nullptr;
    *fd = -1;
}

static rga_buffer_t make_buffer(void *va, int w, int h, PixelFmt fmt,
                                int hor_stride, int ver_stride){
    rga_buffer_t b{};
    b.width = w;
    b.height = h;
    b.wstride = hor_stride > 0 ? hor_stride : w;
    b.hstride = ver_stride > 0 ? ver_stride : h;
    b.vir_addr = va;
    switch(fmt){
        case PixelFmt::NV12:    b.format = RK_FORMAT_YCbCr_420_SP;      break;
        case PixelFmt::YUYV:    b.format = RK_FORMAT_YCbCr_422_SP;      break;
        case PixelFmt::SBGGR10:
            /* RGA 不直接支持 10-bit Bayer, 用 YUV422P 占位. 实际走 ISP */
            b.format = RK_FORMAT_YCbCr_420_SP;
            break;
    }
    return b;
}

struct HwScaler::Impl {
    ScalerCfg cfg;
    int  dst_hs = 0;
    int  dst_vs = 0;
    rga_buffer_t src_buf{};
    rga_buffer_t dst_buf{};
    rga_buffer_handle_t src_handle = 0;
    rga_buffer_handle_t dst_handle = 0;
    int src_fd = -1;
    int dst_fd = -1;
    void *src_addr = nullptr;
    void *dst_addr = nullptr;
    size_t src_size = 0;
    size_t dst_size = 0;
    bool ok = false;

    ~Impl(){
        if(src_handle) releasebuffer_handle(src_handle);
        if(dst_handle) releasebuffer_handle(dst_handle);
        free_cma_buffer(src_size, &src_fd, &src_addr);
        free_cma_buffer(dst_size, &dst_fd, &dst_addr);
    }

};

HwScaler::HwScaler(const ScalerCfg &cfg)
    : impl_(std::make_unique<Impl>()) {
    impl_->cfg = cfg;
    impl_->dst_hs = cfg.dst_hor_stride > 0 ? cfg.dst_hor_stride : ALIGN16(cfg.dst_w);
    impl_->dst_vs = cfg.dst_ver_stride > 0 ? cfg.dst_ver_stride : ALIGN16(cfg.dst_h);

    int src_hs = cfg.src_hor_stride > 0 ? cfg.src_hor_stride : ALIGN16(cfg.src_w);
    int src_vs = cfg.src_ver_stride > 0 ? cfg.src_ver_stride : ALIGN16(cfg.src_h);
    impl_->src_buf = make_buffer(nullptr, cfg.src_w, cfg.src_h, cfg.src_fmt, src_hs, src_vs);
    impl_->dst_buf = make_buffer(nullptr, cfg.dst_w, cfg.dst_h, cfg.dst_fmt, impl_->dst_hs, impl_->dst_vs);

    impl_->src_size = (size_t)src_hs * src_vs * 3 / 2;
    impl_->dst_size = (size_t)impl_->dst_hs * impl_->dst_vs * 3 / 2;
    if(alloc_cma_buffer(impl_->src_size, &impl_->src_fd, &impl_->src_addr) < 0 ||
       alloc_cma_buffer(impl_->dst_size, &impl_->dst_fd, &impl_->dst_addr) < 0)
        return;

    impl_->src_handle = importbuffer_fd(impl_->src_fd, (int)impl_->src_size);
    impl_->dst_handle = importbuffer_fd(impl_->dst_fd, (int)impl_->dst_size);
    if(!impl_->src_handle || !impl_->dst_handle){
        std::cerr << "[rga] importbuffer_fd failed"
                  << " src_handle=" << impl_->src_handle
                  << " dst_handle=" << impl_->dst_handle << "\n";
        return;
    }

    impl_->src_buf = wrapbuffer_handle(
        impl_->src_handle, cfg.src_w, cfg.src_h,
        impl_->src_buf.format, src_hs, src_vs);
    impl_->dst_buf = wrapbuffer_handle(
        impl_->dst_handle, cfg.dst_w, cfg.dst_h,
        impl_->dst_buf.format, impl_->dst_hs, impl_->dst_vs);

    im_rect src_rect{};
    im_rect dst_rect{};
    IM_STATUS check = imcheck(impl_->src_buf, impl_->dst_buf, src_rect, dst_rect);
    if(check != IM_STATUS_NOERROR){
        std::cerr << "[rga] imcheck failed: " << check
                  << " (" << imStrError(check) << ")\n";
        return;
    }

    impl_->ok = true;
    std::cerr << "[rga] CMA buffers ready src=" << impl_->src_size
              << " dst=" << impl_->dst_size << "\n";
}

HwScaler::~HwScaler() = default;

bool HwScaler::valid() const{ return impl_->ok; }

int HwScaler::scale(){
    if(!impl_->ok) return -1;
    IM_STATUS status = imresize(impl_->src_buf, impl_->dst_buf);
    if(status != IM_STATUS_SUCCESS){
        std::cerr << "[rga] imresize failed: " << status << " (" << imStrError(status) << ")\n";
        return -1;
    }
    return 0;
}

uint8_t *HwScaler::src_data() const{
    return static_cast<uint8_t*>(impl_->src_addr);
}

uint8_t *HwScaler::dst_data() const{
    return static_cast<uint8_t*>(impl_->dst_addr);
}

size_t HwScaler::src_frame_size() const{
    return impl_->src_size;
}

void HwScaler::get_dst_stride(int *hs, int *vs) const{
    if(hs) *hs = impl_->dst_hs;
    if(vs) *vs = impl_->dst_vs;
}

size_t HwScaler::dst_frame_size() const{
    return impl_->dst_size;
}

} // namespace webrtcpush
