#include "mipi_vflow_backend.hpp"

#include <cstring>
#include <iostream>

#if defined(CAMERA_HAVE_HOBOT)
// Hobot multi-pipe / vflow headers (only available on RDK target).
#include "common_utils.h"
#include "hb_media_codec.h"
#include "hb_media_error.h"
#endif

struct MipiVflowBackend::Impl {
#if defined(CAMERA_HAVE_HOBOT)
  int mipi_host_{0};
  int pym_channel_{0};
  int sensor_id_{0};

  pipe_contex_t pipe_contex_{};
  bool opened_{false};
  bool mem_opened_{false};

  bool open_pipeline(int mipi_host, int pym_channel, int sensor_id) {
    close_internal();

    mipi_host_ = mipi_host;
    pym_channel_ = pym_channel;
    sensor_id_ = sensor_id;

    // Map based on your validated multi-pipe config:
    // - pipeline0: mipi_host0 -> isp slot4
    // - pipeline1: mipi_host1 -> isp slot5
    const int isp_hw_id = 1;
    const int isp_slot_id = 4 + mipi_host_;
    const sched_mode_e isp_mode = SCHED_MODE_MANUAL;
    const int ynr_mode = 1; // manual
    const int ynr_slot_id = isp_slot_id;

    const int pym_hw_id = 1;
    const int pym_mode = PYM_MANUAL_MODE;
    const int pym_select_chn = pym_channel_;

    if (pym_channel_ < 0) {
      std::cerr << "[mipi_vflow_backend] invalid pym_channel=" << pym_channel_ << std::endl;
      return false;
    }

    if (!vp_sensor_config_list || sensor_id_ < 0 || sensor_id_ >= vp_get_sensors_list_number()) {
      std::cerr << "[mipi_vflow_backend] invalid sensor_id=" << sensor_id_ << std::endl;
      return false;
    }

    pipe_contex_.sensor_config = vp_sensor_config_list[sensor_id_];
    if (!pipe_contex_.sensor_config) {
      std::cerr << "[mipi_vflow_backend] sensor_config not found, sensor_id=" << sensor_id_ << std::endl;
      return false;
    }

    // Force deterministic mipi_host selection by pre-marking the other host as used.
    // This relies on `vp_sensor_multi_fixed_mipi_host()` using `used_mipi_host` as a bitmap.
    int32_t used_mipi_host = 0;
    if (mipi_host_ == 0) {
      used_mipi_host |= (1 << 1); // mark host1 as used, keep host0 free
    } else {
      used_mipi_host |= (1 << 0); // keep host1 free
    }

    const int32_t ret_host =
        vp_sensor_multi_fixed_mipi_host(pipe_contex_.sensor_config, &used_mipi_host, &pipe_contex_.csi_config);
    if (ret_host < 0) {
      std::cerr << "[mipi_vflow_backend] vp_sensor_multi_fixed_mipi_host failed ret=" << ret_host << std::endl;
      return false;
    }

    hb_mem_module_open();
    mem_opened_ = true;

    // --- Camera node ---
    {
      camera_config_t camera_config_copy = {0};
      camera_config_copy = *pipe_contex_.sensor_config->camera_config;

      if (pipe_contex_.sensor_config->sensor_type == SENSOR_TYPE_NORMAL) {
        camera_config_copy.addr = pipe_contex_.csi_config.sensor_addr;
      } else {
        std::cerr << "[mipi_vflow_backend] Only SENSOR_TYPE_NORMAL is supported currently." << std::endl;
        close_internal();
        return false;
      }

      int ret = hbn_camera_create(&camera_config_copy, &pipe_contex_.cam_fd);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_camera_create failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
    }

    // --- VIN node (offline vin -> ddr_en=1) ---
    {
      vp_sensor_config_t *sensor_config = pipe_contex_.sensor_config;
      vin_node_attr_t *vin_node_attr = sensor_config->vin_node_attr;
      vin_ichn_attr_t *vin_ichn_attr = sensor_config->vin_ichn_attr;
      vin_ochn_attr_t *vin_ochn_attr = sensor_config->vin_ochn_attr;

      vin_node_attr->magicNumber = MAGIC_NUMBER;
      vin_ochn_attr->magicNumber = MAGIC_NUMBER;

      vin_node_attr->cim_attr.mipi_rx = pipe_contex_.csi_config.mipi_rx;
      const uint32_t hw_id = pipe_contex_.csi_config.mipi_rx;

      vin_ochn_attr->ddr_en = 1;
      vin_node_attr->cim_attr.cim_isp_flyby = 0;

      hbn_vnode_handle_t *vin_node_handle = &pipe_contex_.vin_node_handle;
      int ret = hbn_vnode_open(HB_VIN, hw_id, AUTO_ALLOC_ID, vin_node_handle);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_open(HB_VIN) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
      ret = hbn_vnode_set_attr(*vin_node_handle, vin_node_attr);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_attr(HB_VIN) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
      ret = hbn_vnode_set_ichn_attr(*vin_node_handle, 0, vin_ichn_attr);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_ichn_attr(HB_VIN) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
      ret = hbn_vnode_set_ochn_attr(*vin_node_handle, 0, vin_ochn_attr);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_ochn_attr(HB_VIN) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }

      // Offline vin uses DDR buffers; allocate output buffer.
      if (vin_ochn_attr->ddr_en) {
        hbn_buf_alloc_attr_t alloc_attr = {0};
        alloc_attr.buffers_num = 6;
        alloc_attr.is_contig = 1;
        alloc_attr.flags = HB_MEM_USAGE_CPU_READ_OFTEN | HB_MEM_USAGE_CPU_WRITE_OFTEN | HB_MEM_USAGE_CACHED;
        ret = hbn_vnode_set_ochn_buf_attr(*vin_node_handle, 0, &alloc_attr);
        if (ret < 0) {
          std::cerr << "[mipi_vflow_backend] hbn_vnode_set_ochn_buf_attr(HB_VIN) failed ret=" << ret << std::endl;
          close_internal();
          return false;
        }
      }
    }

    // --- ISP node (online stream) ---
    {
      vp_sensor_config_t *sensor_config = pipe_contex_.sensor_config;
      isp_attr_t *isp_attr = sensor_config->isp_attr;
      isp_ichn_attr_t *isp_ichn_attr = sensor_config->isp_ichn_attr;
      isp_ochn_attr_t *isp_ochn_attr = sensor_config->isp_ochn_attr;

      isp_attr->channel.hw_id = isp_hw_id;
      isp_attr->channel.slot_id = isp_slot_id;
      isp_attr->sched_mode = static_cast<sched_mode_e>(isp_mode);

      isp_ochn_attr->stream_output_mode = STREAM_OUTPUT_MODE_ENABLE;
      isp_ochn_attr->axi_output_mode = AXI_OUTPUT_MODE_DISABLE;

      hbn_vnode_handle_t *isp_node_handle = &pipe_contex_.isp_node_handle;
      int ret = hbn_vnode_open(HB_ISP, isp_hw_id, AUTO_ALLOC_ID, isp_node_handle);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_open(HB_ISP) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
      ret = hbn_vnode_set_attr(*isp_node_handle, isp_attr);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_attr(HB_ISP) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
      ret = hbn_vnode_set_ochn_attr(*isp_node_handle, 0, isp_ochn_attr);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_ochn_attr(HB_ISP) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
      ret = hbn_vnode_set_ichn_attr(*isp_node_handle, 0, isp_ichn_attr);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_ichn_attr(HB_ISP) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
    }

    // --- YNR node (online) ---
    {
      vp_sensor_config_t *sensor_config = pipe_contex_.sensor_config;
      ynr_init_attr *attr = sensor_config->ynr_attr;
      hbn_vnode_handle_t *ynr_node_handle = &pipe_contex_.ynr_node_handle;

      attr->work_mode = ynr_mode;
      attr->slot_id = ynr_slot_id;

      const int hw_id = 1;
      int ret = hbn_vnode_open(HB_YNR, hw_id, AUTO_ALLOC_ID, ynr_node_handle);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_open(HB_YNR) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
      ret = hbn_vnode_set_attr(*ynr_node_handle, attr);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_attr(HB_YNR) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }

      hobot_ynr_channel_input_config channel_input_cfg = {0};
      ret = hbn_vnode_set_ichn_attr(*ynr_node_handle, 0, &channel_input_cfg);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_ichn_attr(HB_YNR:0) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
      ret = hbn_vnode_set_ichn_attr(*ynr_node_handle, 1, &channel_input_cfg);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_ichn_attr(HB_YNR:1) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }

      hobot_ynr_channel_output_config channel_output_cfg = {0};
      ret = hbn_vnode_set_ochn_attr(*ynr_node_handle, 0, &channel_output_cfg);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_ochn_attr(HB_YNR) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }

      if (attr->nr3d_en == 1u) {
        hbn_buf_alloc_attr_t alloc_attr = {0};
        alloc_attr.buffers_num = 3;
        alloc_attr.is_contig = 1;
        alloc_attr.flags = static_cast<int64_t>((uint64_t)HB_MEM_USAGE_CPU_READ_OFTEN |
                                                   (uint64_t)HB_MEM_USAGE_CPU_WRITE_OFTEN |
                                                   (uint64_t)HB_MEM_USAGE_CACHED);
        ret = hbn_vnode_set_ochn_buf_attr(*ynr_node_handle, 0, &alloc_attr);
        if (ret != 0) {
          std::cerr << "[mipi_vflow_backend] hbn_vnode_set_ochn_buf_attr(HB_YNR) failed ret=" << ret << std::endl;
          close_internal();
          return false;
        }
      }
    }

    // --- PYM node ---
    {
      vp_sensor_config_t *sensor_config = pipe_contex_.sensor_config;
      int input_width = sensor_config->camera_config->width;
      int input_height = sensor_config->camera_config->height;

      pym_cfg_t pym_cfg = {0};
      pym_cfg.hw_id = pym_hw_id;
      pym_cfg.pym_mode = pym_mode;
      pym_cfg.slot_id = isp_slot_id;

      pym_cfg.output_buf_num = 3;
      pym_cfg.fb_buf_num = 2;
      pym_cfg.layer_num_trans_next = pym_select_chn;
      pym_cfg.layer_num_share_prev = -1;
      pym_cfg.out_buf_noinvalid = 1;
      pym_cfg.out_buf_noncached = 0;
      pym_cfg.in_buf_noclean = 1;
      pym_cfg.in_buf_noncached = 0;
      pym_cfg.magicNumber = MAGIC_NUMBER;

      pym_cfg.chn_ctrl.pixel_num_before_sol = DEF_PIX_NUM_BF_SOL;
      pym_cfg.chn_ctrl.suffix_hb_val = DEF_SUFFIX_HB;
      pym_cfg.chn_ctrl.prefix_hb_val = DEF_PREFIX_HB;
      pym_cfg.chn_ctrl.suffix_vb_val = DEF_SUFFIX_VB;
      pym_cfg.chn_ctrl.prefix_vb_val = DEF_PREFIX_VB;
      pym_cfg.chn_ctrl.bl_max_layer_en = DEF_BL_MAX_EN;

      pym_cfg.chn_ctrl.src_in_width = input_width;
      pym_cfg.chn_ctrl.src_in_height = input_height;
      pym_cfg.chn_ctrl.src_in_stride_y = ALIGN_16(input_width);
      pym_cfg.chn_ctrl.src_in_stride_uv = ALIGN_16(input_width);

      pym_cfg.chn_ctrl.ds_roi_en = 0;
      for (int i = 0; i < MAX_DS_NUM; i++) {
        pym_cfg.chn_ctrl.ds_roi_sel[i] = (i == 0) ? 0 : 1;
        pym_cfg.chn_ctrl.ds_roi_layer[i] = (i == 0) ? 0 : (i - 1);

        int ratio = 1 << i;
        int bl_width = FLOOR_ALIGN_2(input_width / ratio);
        int bl_height = FLOOR_ALIGN_2(input_height / ratio);
        if (bl_width < PYM_MIN_WIDTH || bl_height < PYM_MIN_HEIGHT) {
          continue;
        }

        pym_cfg.chn_ctrl.ds_roi_en |= 1 << i;

        roi_box_t *roi_box = &pym_cfg.chn_ctrl.ds_roi_info[i];
        roi_box->start_left = 0;
        roi_box->start_top = 0;

        roi_box->region_width = bl_width;
        roi_box->region_height = bl_height;

        roi_box->out_width = FLOOR_ALIGN_8(roi_box->region_width);
        roi_box->out_height = FLOOR_ALIGN_8(roi_box->region_height);

        roi_box->wstride_uv = ALIGN_16(roi_box->out_width);
        roi_box->wstride_y = ALIGN_16(roi_box->out_width);
        roi_box->vstride = roi_box->out_height;

        // Optional: keep output info for debugging/consistency.
        node_out_info_t *node_out_info = &pipe_contex_.pym_out_info[i];
        node_out_info->width = roi_box->out_width;
        node_out_info->height = roi_box->out_height;
        node_out_info->wstride = roi_box->wstride_uv;
      }

      hbn_vnode_handle_t *pym_node_handle = &pipe_contex_.pym_node_handle;
      int ret = hbn_vnode_open(HB_PYM, pym_cfg.hw_id, AUTO_ALLOC_ID, pym_node_handle);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_open(HB_PYM) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
      ret = hbn_vnode_set_attr(*pym_node_handle, &pym_cfg);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_attr(HB_PYM) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
      ret = hbn_vnode_set_ichn_attr(*pym_node_handle, 0, &pym_cfg);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_ichn_attr(HB_PYM) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }
      ret = hbn_vnode_set_ochn_attr(*pym_node_handle, 0, &pym_cfg);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vnode_set_ochn_attr(HB_PYM) failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }

      if (pym_cfg.output_buf_num > 0) {
        hbn_buf_alloc_attr_t alloc_attr = {0};
        alloc_attr.buffers_num = pym_cfg.output_buf_num;
        alloc_attr.is_contig = 1;
        alloc_attr.flags = HB_MEM_USAGE_CPU_READ_OFTEN | HB_MEM_USAGE_CPU_WRITE_OFTEN | HB_MEM_USAGE_CACHED;
        ret = hbn_vnode_set_ochn_buf_attr(*pym_node_handle, 0, &alloc_attr);
        if (ret != 0) {
          std::cerr << "[mipi_vflow_backend] hbn_vnode_set_ochn_buf_attr(HB_PYM) failed ret=" << ret << std::endl;
          close_internal();
          return false;
        }
      }
    }

    // --- VFLOW ---
    {
      int ret = hbn_vflow_create(&pipe_contex_.vflow_fd);
      if (ret != 0) {
        std::cerr << "[mipi_vflow_backend] hbn_vflow_create failed ret=" << ret << std::endl;
        close_internal();
        return false;
      }

      ret = hbn_vflow_add_vnode(pipe_contex_.vflow_fd, pipe_contex_.vin_node_handle);
      if (ret != 0) {
        close_internal();
        return false;
      }
      ret = hbn_vflow_add_vnode(pipe_contex_.vflow_fd, pipe_contex_.isp_node_handle);
      if (ret != 0) {
        close_internal();
        return false;
      }
      ret = hbn_vflow_add_vnode(pipe_contex_.vflow_fd, pipe_contex_.ynr_node_handle);
      if (ret != 0) {
        close_internal();
        return false;
      }
      ret = hbn_vflow_add_vnode(pipe_contex_.vflow_fd, pipe_contex_.pym_node_handle);
      if (ret != 0) {
        close_internal();
        return false;
      }

      // Scene: Vin_x(offline) -> ISP(online) -> YNR(online) -> PYM(online)
      // vin->isp : is_online = 0
      ret = hbn_vflow_bind_vnode(pipe_contex_.vflow_fd, pipe_contex_.vin_node_handle, 0, pipe_contex_.isp_node_handle, 0);
      if (ret != 0) {
        close_internal();
        return false;
      }
      // isp->ynr : is_online = 1
      ret = hbn_vflow_bind_vnode(pipe_contex_.vflow_fd, pipe_contex_.isp_node_handle, 1, pipe_contex_.ynr_node_handle, 0);
      if (ret != 0) {
        close_internal();
        return false;
      }
      // ynr->pym : is_online = 1
      ret = hbn_vflow_bind_vnode(pipe_contex_.vflow_fd, pipe_contex_.ynr_node_handle, 1, pipe_contex_.pym_node_handle, 0);
      if (ret != 0) {
        close_internal();
        return false;
      }

      ret = hbn_camera_attach_to_vin(pipe_contex_.cam_fd, pipe_contex_.vin_node_handle);
      if (ret != 0) {
        close_internal();
        return false;
      }
    }

    // Start
    {
      int ret = hbn_vflow_start(pipe_contex_.vflow_fd);
      if (ret != 0) {
        close_internal();
        return false;
      }
    }

    opened_ = true;
    return true;
  }

  bool get_nv12_frame(Nv12Frame & out, int timeout_ms) {
    if (!opened_) {
      return false;
    }

    hbn_vnode_image_group_t out_img_group = {0};
    int ret = hbn_vnode_getframe_group(pipe_contex_.pym_node_handle, 0, timeout_ms, &out_img_group);
    if (ret != 0) {
      return false;
    }

    auto * out_img_for_codec = &out_img_group.buf_group.graph_group[pym_channel_];
    const uint32_t w = static_cast<uint32_t>(out_img_for_codec->width);
    const uint32_t h = static_cast<uint32_t>(out_img_for_codec->height);
    const size_t bytes = static_cast<size_t>(w) * static_cast<size_t>(h) * 3u / 2u;

    if (w == 0 || h == 0 || bytes == 0 || !out_img_for_codec->virt_addr[0]) {
      hbn_vnode_releaseframe_group(pipe_contex_.pym_node_handle, 0, &out_img_group);
      return false;
    }

    out.width = w;
    out.height = h;
    out.data.resize(bytes);
    std::memcpy(out.data.data(), out_img_for_codec->virt_addr[0], bytes);

    hbn_vnode_releaseframe_group(pipe_contex_.pym_node_handle, 0, &out_img_group);
    return true;
  }

  void close_internal() {
    if (opened_) {
      // Stop flow first.
      hbn_vflow_stop(pipe_contex_.vflow_fd);
      hbn_vflow_destroy(pipe_contex_.vflow_fd);
      opened_ = false;
    }
    if (mem_opened_) {
      hb_mem_module_close();
      mem_opened_ = false;
    }
  }
#else
  bool opened_{false};
  bool open_pipeline(int, int, int) { return false; }
  bool get_nv12_frame(Nv12Frame &, int) { return false; }
  void close_internal() {}
#endif
};

MipiVflowBackend::MipiVflowBackend() = default;

MipiVflowBackend::~MipiVflowBackend() {
  close();
}

bool MipiVflowBackend::is_open() const {
  return impl_ != nullptr;
}

bool MipiVflowBackend::open(int mipi_host, int pym_channel, int sensor_id) {
  close();
  impl_ = new Impl();
#if defined(CAMERA_HAVE_HOBOT)
  if (!impl_->open_pipeline(mipi_host, pym_channel, sensor_id)) {
    delete impl_;
    impl_ = nullptr;
    return false;
  }
#else
  (void)mipi_host;
  (void)pym_channel;
  (void)sensor_id;
  delete impl_;
  impl_ = nullptr;
  return false;
#endif
  return true;
}

bool MipiVflowBackend::get_frame(Nv12Frame & out, int timeout_ms) {
  if (!impl_) {
    return false;
  }
#if defined(CAMERA_HAVE_HOBOT)
  return impl_->get_nv12_frame(out, timeout_ms);
#else
  (void)out;
  (void)timeout_ms;
  return false;
#endif
}

void MipiVflowBackend::close() {
  if (impl_) {
#if defined(CAMERA_HAVE_HOBOT)
    impl_->close_internal();
#endif
    delete impl_;
    impl_ = nullptr;
  }
}


