// SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND LGPL-2.1-or-later

#include "mvb_format_utils.h"
#include "mvb_heap_impl.h"
#include "mvb_heap_debug.h"

#include <stdio.h>
#include <stdlib.h>

#define MVB_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
#define FB_SIZE_LUMA_TARGET_PERCENT (87)
#define FB_SIZE_CHROMA_TARGET_PERCENT (66)

typedef int (*FORMAT_SIZE_GET) (struct mvb_format_info_t* info, int width, int height, int plane);

struct mvb_format_info_t {
  enum mvb_format_e mvb_format;
  int mvb_mem_plane;
  int mvb_comp_plane;

  uint8_t bits[MVB_FRAME_PLANE_NUM_MAX];
  uint8_t hdiv[MVB_FRAME_PLANE_NUM_MAX];
  uint8_t vdiv[MVB_FRAME_PLANE_NUM_MAX];

  FORMAT_SIZE_GET fn_get_size;
};

struct mvb_frame_t {
  struct mvb_slot_t* slots[MVB_FRAME_PLANE_NUM_MAX];
  int                slot_count;

  enum mvb_format_e mvb_format;
  uint32_t     mvb_flag;

  int width;
  int height;

  /* private */
  struct mvb_heap_t* parent;
};

#define MVB_ROUND_UP_32(num) (((num)+31)&~31)
#define MVB_ROUND_UP_64(num) (((num)+63)&~63)
#define MTK_ALIGN(x, align) (((x)+(align)-1) & (~(align-1)))

static int
get_size_MVB_FORMAT_ENCODED (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int aw = MVB_ROUND_UP_64 (width);
  int ah = MVB_ROUND_UP_64 (height);
  int cur_size = 0;

  if (aw * ah < MVB_ENCODED_LIMIT_SIZE_HD)
    cur_size = MVB_ENCODED_BUFFER_SIZE_2M;
  else
    cur_size = MVB_ENCODED_BUFFER_SIZE_4M;


  MVB_LOGD ("[mvb] ENCODED width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_M21S (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int aw = MVB_ROUND_UP_64 (width);
  int ah = MVB_ROUND_UP_64 (height);
  int cur_size   = aw * ah * 3 / 2;

  MVB_LOGD ("[mvb] M21S width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_M2CS (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int aw = MVB_ROUND_UP_64 (width);
  int ah = MVB_ROUND_UP_64 (height);
  int cur_y_sz   = MTK_ALIGN(aw * ah, 4096);
  int cur_c_sz   = MTK_ALIGN(cur_y_sz >> 1, 512);
  int cur_yl_sz  = ((((aw * ah + 255) >> 8) + 63 + (16 * 8)) >> 6) << 6;
  int cur_cl_sz  = (((cur_yl_sz >> 1) + 15 + (16 *8)) >> 4) << 4;
  int cur_size   = cur_y_sz + cur_c_sz + cur_yl_sz + cur_cl_sz;

  MVB_LOGD ("[mvb] M2CS width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_MTST (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int aw = MVB_ROUND_UP_64 (width);
  int ah = MVB_ROUND_UP_64 (height);
  int cur_y_sz   = MTK_ALIGN((aw * ah * 5) >> 2, 512);
  int cur_c_sz   = MTK_ALIGN(cur_y_sz >> 1, 512);
  int cur_size   = cur_y_sz + cur_c_sz;

  MVB_LOGD ("[mvb] MTST width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_MCST (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int aw = MVB_ROUND_UP_64 (width);
  int ah = MVB_ROUND_UP_64 (height);
  int cur_y_sz   = MTK_ALIGN((aw * ah * 5) >> 2, 4096);
  int cur_c_sz   = MTK_ALIGN(cur_y_sz >> 1, 512);
  int cur_yl_sz  = ((((aw * ah + 255) >> 8) + 63 + (16 * 8)) >> 6) << 6;
  int cur_cl_sz  = (((cur_yl_sz >> 1) + 15 + (16 *8)) >> 4) << 4;
  int cur_size   = cur_y_sz + cur_c_sz + cur_yl_sz + cur_cl_sz;

  MVB_LOGD ("[mvb] MCST width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_MTSR (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int aw = MVB_ROUND_UP_64 (width);
  int ah = MVB_ROUND_UP_64 (height);
  int cur_y_sz   = MTK_ALIGN((aw * ah * 5) >> 2, 4096);
  int cur_c_sz   = MTK_ALIGN(cur_y_sz >> 1, 512);
  int cur_size   = cur_y_sz + cur_c_sz;

  MVB_LOGD ("[mvb] MTSR width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_MCSR (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int aw = MVB_ROUND_UP_64 (width);
  int ah = MVB_ROUND_UP_64 (height);
  int cur_y_sz   = MTK_ALIGN((aw * ah * 5) >> 2, 4096);
  int cur_c_sz   = MTK_ALIGN(cur_y_sz >> 1, 512);
  int cur_yl_sz  = ((((aw * ah + 255) >> 8) + 63 + (16 * 8)) >> 6) << 6;
  int cur_cl_sz  = (((cur_yl_sz >> 1) + 15 + (16 *8)) >> 4) << 4;
  int cur_size   = cur_y_sz + cur_c_sz + cur_yl_sz + cur_cl_sz;

  MVB_LOGD ("[mvb] MCSR width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_MFCS (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int aw = MTK_ALIGN(width, 128);
  int ah = MTK_ALIGN(height, 128);
  int pic_h_64 = MTK_ALIGN(ah + 8 * 2, 64);
  int y_data_sz = 0;
  int y_data_sz_compact = 0;
  int c_data_sz = 0;
  int c_data_sz_compact = 0;
  int y_header_sz = 0;
  int c_header_sz = 0;
  int cur_mfc_sz = 0;

  y_data_sz = MTK_ALIGN((aw * ah), 1024);
  y_data_sz_compact =
      MTK_ALIGN(MTK_ALIGN(y_data_sz * FB_SIZE_LUMA_TARGET_PERCENT, 100) / 100, 1024);
  c_data_sz = MTK_ALIGN(y_data_sz / 2, 1024);
  c_data_sz_compact =
      MTK_ALIGN(MTK_ALIGN(c_data_sz * FB_SIZE_CHROMA_TARGET_PERCENT, 100) / 100, 1024);
  y_header_sz = MTK_ALIGN(3 * aw / 32 * ah / 4, 1024);
  c_header_sz = MTK_ALIGN(2 * aw / 32 * ah / 2 / 4, 1024);
  cur_mfc_sz = MTK_ALIGN(aw * pic_h_64 / 32, 512);

  //MFC without compact
  int cur_y_sz = y_data_sz_compact + y_header_sz;
  int cur_c_sz = c_data_sz_compact + c_header_sz;
  int cur_size = cur_y_sz + cur_c_sz + cur_mfc_sz;

  MVB_LOGD ("[mvb] MFCS width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_MFST (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int aw = MTK_ALIGN(width, 128);
  int ah = MTK_ALIGN(height, 128);
  int pic_h_64 = MTK_ALIGN(ah + 8 * 2, 64);
  int y_data_sz = 0;
  int y_data_sz_compact = 0;
  int c_data_sz = 0;
  int c_data_sz_compact = 0;
  int y_header_sz = 0;
  int c_header_sz = 0;
  int cur_mfc_sz = 0;

  y_data_sz = MTK_ALIGN((aw * ah * 5) / 4, 1024);
  y_data_sz_compact =
      MTK_ALIGN(MTK_ALIGN(y_data_sz * FB_SIZE_LUMA_TARGET_PERCENT, 100) / 100, 1024);
  c_data_sz = MTK_ALIGN(y_data_sz / 2, 1024);
  c_data_sz_compact =
      MTK_ALIGN(MTK_ALIGN(c_data_sz * FB_SIZE_CHROMA_TARGET_PERCENT, 100) / 100, 1024);
  y_header_sz = MTK_ALIGN(3 * aw / 32 * ah / 4, 1024);
  c_header_sz = MTK_ALIGN(2 * aw / 32 * ah / 2 / 4, 1024);
  cur_mfc_sz = MTK_ALIGN(aw * pic_h_64 / 32, 512);

  //MFC without compact
  int cur_y_sz = y_data_sz_compact + y_header_sz;
  int cur_c_sz = c_data_sz_compact + c_header_sz;
  int cur_size = cur_y_sz + cur_c_sz + cur_mfc_sz;

  MVB_LOGD ("[mvb] MFST width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_MTSP (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int cur_y_sz   = width * height * 5 >> 2;
  int cur_c_sz   = cur_y_sz >> 1;
  int cur_size = cur_y_sz + cur_c_sz;

  MVB_LOGD ("[mvb] MTSP width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_MM21 (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int aw = MVB_ROUND_UP_64 (width);
  int ah = MVB_ROUND_UP_64 (height);
  int cur_size = 0;

  if (plane == 0)
    cur_size = aw*ah;
  else if (plane == 1)
    cur_size = aw * ah / 2;

  MVB_LOGD ("[mvb] MM21 width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_RGB (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int cur_size   = MTK_ALIGN (width * 3, 4) * height;

  MVB_LOGD ("[mvb] RGB width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_RGBA (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int cur_size =  width * 4 * height;

  MVB_LOGD ("[mvb] RGBA width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static int
get_size_MVB_FORMAT_BGRA (struct mvb_format_info_t* info, int width, int height, int plane)
{
  int cur_size =  width * 4 * height;

  MVB_LOGD ("[mvb] BGRA width: %d height: %d plane_index: %d, size: %d", width, height, plane, cur_size);
  return cur_size;
}

static struct mvb_format_info_t*
mvb_format_utils_get_info (enum mvb_format_e mvb_format)
{
  static struct mvb_format_info_t _infos_[] = {
    {.mvb_format = MVB_FORMAT_NV12,    .mvb_mem_plane = 1, .mvb_comp_plane = 2,
        .bits = {1, 2, }, .hdiv = {1, 2, }, .vdiv = {1, 2, }, .fn_get_size = NULL, },
    {.mvb_format = MVB_FORMAT_NV12M,   .mvb_mem_plane = 2, .mvb_comp_plane = 2,
        .bits = {1, 2, }, .hdiv = {1, 2, }, .vdiv = {1, 2, }, .fn_get_size = NULL, },
    {.mvb_format = MVB_FORMAT_YUYV,    .mvb_mem_plane = 1, .mvb_comp_plane = 1,
        .bits = {2, }, .hdiv = {1, },   .vdiv = {1, },   .fn_get_size = NULL, },
    {.mvb_format = MVB_FORMAT_M21S,    .mvb_mem_plane = 1, .mvb_comp_plane = 4,
        .fn_get_size = get_size_MVB_FORMAT_M21S, },
    {.mvb_format = MVB_FORMAT_RGB,     .mvb_mem_plane = 1, .mvb_comp_plane = 1,
        .fn_get_size = get_size_MVB_FORMAT_RGB, },
    {.mvb_format = MVB_FORMAT_RGBA,    .mvb_mem_plane = 1, .mvb_comp_plane = 1,
        .fn_get_size = get_size_MVB_FORMAT_RGBA, },
    {.mvb_format = MVB_FORMAT_BGRA,    .mvb_mem_plane = 1, .mvb_comp_plane = 1,
        .fn_get_size = get_size_MVB_FORMAT_BGRA, },
    {.mvb_format = MVB_FORMAT_M2CS,    .mvb_mem_plane = 1, .mvb_comp_plane = 4,
        .fn_get_size = get_size_MVB_FORMAT_M2CS, },
    {.mvb_format = MVB_FORMAT_MTST,    .mvb_mem_plane = 1, .mvb_comp_plane = 2,
        .fn_get_size = get_size_MVB_FORMAT_MTST, },
    {.mvb_format = MVB_FORMAT_MCST,    .mvb_mem_plane = 1, .mvb_comp_plane = 4,
        .fn_get_size = get_size_MVB_FORMAT_MCST, },
    {.mvb_format = MVB_FORMAT_MTSR,    .mvb_mem_plane = 1, .mvb_comp_plane = 4,
        .fn_get_size = get_size_MVB_FORMAT_MTSR, },
    {.mvb_format = MVB_FORMAT_MCSR,    .mvb_mem_plane = 1, .mvb_comp_plane = 4,
        .fn_get_size = get_size_MVB_FORMAT_MCSR, },
    {.mvb_format = MVB_FORMAT_MFCS,    .mvb_mem_plane = 1, .mvb_comp_plane = 4,
        .fn_get_size = get_size_MVB_FORMAT_MFCS, },
    {.mvb_format = MVB_FORMAT_MFST,    .mvb_mem_plane = 1, .mvb_comp_plane = 4,
        .fn_get_size = get_size_MVB_FORMAT_MFST, },
    {.mvb_format = MVB_FORMAT_MTSP,    .mvb_mem_plane = 1, .mvb_comp_plane = 4,
        .fn_get_size = get_size_MVB_FORMAT_MTSP, },
    {.mvb_format = MVB_FORMAT_MM21,    .mvb_mem_plane = 2, .mvb_comp_plane = 4,
        .fn_get_size = get_size_MVB_FORMAT_MM21, },
    {.mvb_format = MVB_FORMAT_YUV420,  .mvb_mem_plane = 1, .mvb_comp_plane = 3,
        .bits = {1, 1, 1, }, .hdiv = {1, 4, 4, }, .vdiv = {1, 4, 4, }, .fn_get_size = NULL, },
    {.mvb_format = MVB_FORMAT_YUV420M, .mvb_mem_plane = 3, .mvb_comp_plane = 3,
        .bits = {1, 1, 1, }, .hdiv = {1, 4, 4, }, .vdiv = {1, 4, 4, }, .fn_get_size = NULL, },
    {.mvb_format = MVB_FORMAT_ENCODED, .mvb_mem_plane = 1, .mvb_comp_plane = 1,
        .fn_get_size = get_size_MVB_FORMAT_ENCODED, },
  };

  MVB_LOGD ("[mvb] try to find info for fmt: %d", mvb_format);

  for (int i = 0; i < (sizeof (_infos_) / sizeof (struct mvb_format_info_t)); i++)
  {
    if (_infos_[i].mvb_format == mvb_format)
    {
      return &(_infos_[i]);
    }
  }

  MVB_LOGD ("[mvb] can not find match format for %d", mvb_format);

  return NULL;
}

static int
mvb_format_utils_get_plane_size (enum mvb_format_e mvb_format, int width, int height, int plane_index)
{
  struct mvb_format_info_t* info = mvb_format_utils_get_info (mvb_format);
  int plane_size = 0;

  if (plane_index < 0 || plane_index >= info->mvb_mem_plane)
    goto err;

  if (info->fn_get_size != NULL)
  {
    MVB_LOGD ("[mvb] cal by fn_get_size");
    plane_size = info->fn_get_size (info, width, height, plane_index);
  }
  else
  {
    if (info->mvb_mem_plane == 1) {
      if (plane_index != 0)
        goto err;

      for (int i = 0; i < info->mvb_comp_plane; i++) {
        plane_size += info->bits[i]
                      * MVB_DIV_ROUND_UP (width, info->hdiv[i])
                      * MVB_DIV_ROUND_UP (height, info->vdiv[i]);
      }
    } else {
      plane_size = info->bits[plane_index]
                     * MVB_DIV_ROUND_UP (width, info->hdiv[plane_index])
                     * MVB_DIV_ROUND_UP (height, info->vdiv[plane_index]);
    }
  }
  MVB_LOGD ("[mvb] format: %d width: %d height: %d plane_index: %d, size: %d",
      mvb_format, width, height, plane_index, plane_size);
  return plane_size;

err:
  MVB_LOGE ("[mvb] invalid param");
  return -1;
}

static int
mvb_format_utils_get_mem_plane_num (enum mvb_format_e mvb_format)
{
  struct mvb_format_info_t* info = mvb_format_utils_get_info (mvb_format);
  return info->mvb_mem_plane;
}

struct mvb_frame_t*
mvb_format_utils_alloc_frame (struct mvb_heap_t* p_mvb_heap, int width, int height,
    enum mvb_format_e mvb_format, uint32_t mvb_flag)
{
  struct mvb_frame_t* frame = (struct mvb_frame_t*) malloc (sizeof (struct mvb_frame_t));

  frame->height     = height;
  frame->width      = width;
  frame->mvb_flag   = mvb_flag;
  frame->mvb_format = mvb_format;

  MVB_LOGD ("[mvb] try to alloc frame");

  frame->parent = p_mvb_heap;
  frame->slot_count = mvb_format_utils_get_mem_plane_num (frame->mvb_format);

  MVB_LOGD ("[mvb] cal slot_count: %d", frame->slot_count);

  for (int i = 0; i < frame->slot_count; i++)
  {
    int plane_size = mvb_format_utils_get_plane_size (frame->mvb_format, frame->width, frame->height, i);

    MVB_LOGD ("[mvb] cal slot_size: %d", plane_size);

    frame->slots[i] = mvb_heap_alloc_slot (p_mvb_heap, plane_size, frame->mvb_flag);
    MVB_LOGD ("[mvb] alloc slot %p", frame->slots[i]);
  }

  MVB_LOGD ("alloc frame: %p", frame);
  return frame;
}

void
mvb_format_utils_free_frame (struct mvb_frame_t* mvb_frame)
{
  MVB_LOGD ("[mvb] try to free frame %p", mvb_frame);

  if (mvb_frame == NULL)
    return;

  for (int i = 0; i < mvb_frame->slot_count; i++)
  {
    mvb_heap_free_slot (mvb_frame->slots[i]);
  }

  mvb_frame->slot_count = 0;
  mvb_frame->parent = NULL;

  free (mvb_frame);

  MVB_LOGD ("free frame done");
}

int
mvb_format_utils_frame_get_plane_fd (struct mvb_frame_t* frame, int plane_index)
{
  if (frame == NULL)
    return -1;

  if (plane_index < 0 || plane_index >= frame->slot_count)
    return -1;

  return mvb_slot_get_fd (frame->slots[plane_index]);
}


int
mvb_format_utils_frame_get_plane_size (struct mvb_frame_t* frame, int plane_index)
{
  if (frame == NULL)
    return -1;

  if (plane_index < 0 || plane_index >= frame->slot_count)
    return -1;

  return mvb_slot_get_size (frame->slots[plane_index]);
}

int
mvb_format_utils_frame_get_plane_num (struct mvb_frame_t* frame)
{
  if (frame == NULL)
    return -1;

  return frame->slot_count;
}

uint32_t
mvb_format_utils_frame_get_flag (struct mvb_frame_t* frame)
{
  if (frame == NULL)
    return -1;

  return frame->mvb_flag;
}

int
mvb_format_utils_frame_get_width (struct mvb_frame_t* frame)
{
  if (frame == NULL)
    return -1;

  return frame->width;
}

int
mvb_format_utils_frame_get_height (struct mvb_frame_t* frame)
{
  if (frame == NULL)
    return -1;

  return frame->height;
}

