/*
 * Copyright (c) 2026, Alliance for Open Media. All rights reserved
 *
 * This source code is subject to the terms of the BSD 3-Clause Clear License
 * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
 * License was not distributed with this source code in the LICENSE file, you
 * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/.  If the
 * Alliance for Open Media Patent License 1.0 was not distributed with this
 * source code in the PATENTS file, you can obtain it at
 * aomedia.org/license/patent-license/.
 */

#include "config/avm_dsp_rtcd.h"
#include "av2/common/av2_common_int.h"
#include "av2/encoder/banding_detection.h"
#include "avm_mem/avm_mem.h"

#define SWAP_FLOATS(x, y) \
  {                       \
    float temp = x;       \
    x = y;                \
    y = temp;             \
  }

/* Window size to compute CAMBI: 65 corresponds to approximately 1 degree at 4k
 * scale */
#define CAMBI_DEFAULT_WINDOW_SIZE (65)

/* Encoder banding detection thresholds */
#define CAMBI_DIFF_THRESHOLD_8b 4
#define CAMBI_SOURCE_THRESHOLD_8b 3

#define CAMBI_DIFF_THRESHOLD_10b 3
#define CAMBI_SOURCE_THRESHOLD_10b 2

/* Visibility threshold for luminance ΔL < tvi_threshold*L_mean for BT.1886 */
#define CAMBI_TVI (0.019)

/* Max log contrast luma levels */
#define CAMBI_DEFAULT_MAX_LOG_CONTRAST (2)

/* Window size for CAMBI */
#define CAMBI_MIN_WIDTH (192)
#define CAMBI_MAX_WIDTH (4096)

#define CAMBI_NUM_SCALES (5)

/* Ratio of pixels for computation, must be 0 > topk >= 1.0 */
#define CAMBI_DEFAULT_TOPK_POOLING (0.6)

/* Spatial mask filter size for CAMBI */
#define CAMBI_MASK_FILTER_SIZE (7)

#define CLAMP(x, low, high) \
  (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))

/* CAMBI preprocessing functions */
static void copy_8b_to_10b_buffer(uint16_t *data, int stride, unsigned in_w,
                                  unsigned in_h, uint16_t *out_data,
                                  int out_stride, unsigned out_w,
                                  unsigned out_h) {
  if (in_w != out_w || in_h != out_h) assert(0);

  for (unsigned i = 0; i < out_h; i++)
    for (unsigned j = 0; j < out_w; j++)
      out_data[i * out_stride + j] = data[i * stride + j] << 2;
}

static void copy_10b_buffer(uint16_t *data, int stride, unsigned in_w,
                            unsigned in_h, uint16_t *out_data, int out_stride,
                            unsigned out_w, unsigned out_h) {
  if (in_w != out_w || in_h != out_h) assert(0);

  for (unsigned i = 0; i < out_h; i++) {
    memcpy(out_data, data, in_w * sizeof(uint16_t));
    data += stride;
    out_data += out_stride;
  }
}

static void anti_dithering_filter(uint16_t *data, int stride, int width,
                                  int height) {
  for (int i = 0; i < height - 1; i++) {
    for (int j = 0; j < width - 1; j++) {
      data[i * stride + j] =
          (data[i * stride + j] + data[i * stride + j + 1] +
           data[(i + 1) * stride + j] + data[(i + 1) * stride + j + 1]) >>
          2;
    }

    // Last column
    int j = width - 1;
    data[i * stride + j] =
        (data[i * stride + j] + data[(i + 1) * stride + j]) >> 1;
  }

  // Last row
  int i = height - 1;
  for (int j = 0; j < width - 1; j++) {
    data[i * stride + j] =
        (data[i * stride + j] + data[i * stride + j + 1]) >> 1;
  }
}

void cambi_preprocessing(uint16_t *data, int stride, int in_w, int in_h,
                         int bit_depth, uint16_t *out_data, int out_stride,
                         int out_w, int out_h) {
  if (bit_depth == 8) {
    copy_8b_to_10b_buffer(data, stride, in_w, in_h, out_data, out_stride, out_w,
                          out_h);
    anti_dithering_filter(out_data, out_stride, out_w, out_h);
  } else {
    copy_10b_buffer(data, stride, in_w, in_h, out_data, out_stride, out_w,
                    out_h);
  }
}

/* CAMBI processing functions */
static inline uint16_t get_pixels_in_window(uint16_t window_length) {
  uint16_t odd_length = 2 * (window_length >> 1) + 1;
  return odd_length * odd_length;
}

static inline uint16_t adjust_cambi_window_size(uint16_t size, unsigned width,
                                                unsigned height) {
  // Adjustment with (input_width + input_height) / (4K_WIDTH + 4K_HEIGHT)
  return CLAMP(((size * (width + height)) / 375) >> 4, 5,
               CAMBI_DEFAULT_WINDOW_SIZE);
}

void set_cambi_window(Av2BandDetectInfo *const dbi) {
  dbi->window_size = CAMBI_DEFAULT_WINDOW_SIZE;
  dbi->window_size =
      adjust_cambi_window_size(dbi->window_size, dbi->stride, dbi->height);
  dbi->pixels_in_window = get_pixels_in_window(dbi->window_size);
}

static void cambi_decimate(uint16_t *data, int stride, unsigned width,
                           unsigned height) {
  for (unsigned i = 0; i < height; i++) {
    for (unsigned j = 0; j < width; j++) {
      data[i * stride + j] = data[(i << 1) * stride + (j << 1)];
    }
  }
}

/* Spatial mask functions */
static inline uint16_t ceil_log2(uint32_t num) {
  if (num == 0) return 0;

  uint32_t tmp = num - 1;
  uint16_t shift = 0;
  while (tmp > 0) {
    tmp >>= 1;
    shift += 1;
  }
  return shift;
}

uint16_t cambi_get_mask_index(int input_width, int input_height,
                              uint16_t filter_size) {
  uint32_t shifted_wh = (input_width >> 6) * (input_height >> 6);
  return (filter_size * filter_size + 3 * (ceil_log2(shifted_wh) - 11) - 1) >>
         1;
}

static inline bool get_derivative_data(const uint16_t *data, int i, int j,
                                       int str) {
  return (data[i * str + j] == data[(i + 1) * str + j]) &&
         (data[i * str + j] == data[i * str + j + 1]);
}

/*
 * This function calculates the horizontal and vertical derivatives of the image
 * using 2x1 and 1x2 kernels.
 */
static void get_spatial_mask_for_index(Av2BandDetectInfo *dbi,
                                       uint16_t mask_index,
                                       uint16_t filter_size, int width,
                                       int height) {
  const uint16_t pad_size = filter_size >> 1;
  uint16_t *image_data = dbi->frame;
  uint16_t *mask_data = dbi->mask;
  const int stride = dbi->stride;
  uint32_t *dp = dbi->buffers.mask_dp;

  const int dp_width = width + 2 * pad_size + 1;
  const int dp_height = 2 * pad_size + 2;
  memset(dp, 0, dp_width * dp_height * sizeof(uint32_t));

  // Initial computation: fill dp except for the last row
  for (int i = 0; i < pad_size; i++) {
    int cur_row_start = (i + pad_size + 1) * dp_width;
    int prev_row_start = cur_row_start - dp_width;
    int curr_col = pad_size + 1;
    for (int j = 0; j < width + pad_size; j++, curr_col++) {
      int value = (i < height - 1 && j < width - 1
                       ? get_derivative_data(image_data, i, j, stride)
                       : 0);
      dp[cur_row_start + curr_col] = value + dp[prev_row_start + curr_col] +
                                     dp[cur_row_start + curr_col - 1] -
                                     dp[prev_row_start + curr_col - 1];
    }
  }

  // Start from the last row in the dp matrix
  int curr_row = dp_height - 1;
  int prev_row = dp_height - 2;
  int bottom = 2 * pad_size;
  for (int i = pad_size; i < height + pad_size; i++) {
    // First compute the values of dp for curr_row
    int curr_col = pad_size + 1;
    for (int j = 0; j < width + pad_size; j++, curr_col++) {
      int value = (i < height - 1 && j < width - 1
                       ? get_derivative_data(image_data, i, j, stride)
                       : 0);
      dp[curr_row * dp_width + curr_col] =
          value + dp[prev_row * dp_width + curr_col] +
          dp[curr_row * dp_width + curr_col - 1] -
          dp[prev_row * dp_width + curr_col - 1];
    }
    prev_row = curr_row;
    curr_row = curr_row == (dp_height - 1) ? 0 : curr_row + 1;
    bottom = bottom == (dp_height - 1) ? 0 : bottom + 1;

    // Then use the values to compute the square sum for the curr computed row.
    int right = 2 * pad_size + 1;
    int top = curr_row;
    for (int left = 0; left < width; left++, right++) {
      int result = dp[bottom * dp_width + right] -
                   dp[bottom * dp_width + left] - dp[top * dp_width + right] +
                   dp[top * dp_width + left];
      mask_data[(i - pad_size) * stride + left] = (result > mask_index);
    }
  }
}

void cambi_get_spatial_mask(Av2BandDetectInfo *dbi, int width, int height) {
  const uint16_t filter_size = CAMBI_MASK_FILTER_SIZE;
  const uint16_t mask_index = cambi_get_mask_index(width, height, filter_size);
  get_spatial_mask_for_index(dbi, mask_index, filter_size, width, height);
}

static inline uint16_t min3(uint16_t a, uint16_t b, uint16_t c) {
  if (a <= b && a <= c) return a;
  if (b <= c) return b;
  return c;
}

static inline uint16_t mode3(uint16_t a, uint16_t b, uint16_t c) {
  if (a == b || a == c) return a;
  if (b == c) return b;
  return min3(a, b, c);
}

void cambi_filter_mode(Av2BandDetectInfo *dbi, int width, int height) {
  uint16_t *data = dbi->frame;
  const ptrdiff_t stride = dbi->stride;
  uint16_t *buffer = dbi->buffers.filter_mode_buffer;

  for (int i = 0; i < height; i++) {
    int curr_line = i % 3;
    buffer[curr_line * width + 0] = data[i * stride + 0];
    for (int j = 1; j < width - 1; j++) {
      buffer[curr_line * width + j] =
          mode3(data[i * stride + j - 1], data[i * stride + j],
                data[i * stride + j + 1]);
    }
    buffer[curr_line * width + width - 1] = data[i * stride + width - 1];

    if (i > 1) {
      for (int j = 0; j < width; j++) {
        data[(i - 1) * stride + j] =
            mode3(buffer[0 * width + j], buffer[1 * width + j],
                  buffer[2 * width + j]);
      }
    }
  }
}

static inline void increment_range(uint16_t *arr, int left, int right) {
  for (int i = left; i < right; i++) {
    arr[i]++;
  }
}

static inline void decrement_range(uint16_t *arr, int left, int right) {
  for (int i = left; i < right; i++) {
    arr[i]--;
  }
}

static inline void cambi_histogram_sub_edge(uint16_t *histograms,
                                            uint16_t *image, uint16_t *mask,
                                            int i, int j, int width,
                                            ptrdiff_t stride, uint16_t pad_size,
                                            const uint16_t num_diffs) {
  const long int index = (i - pad_size - 1) * stride + j;
  if (mask[index]) {
    uint16_t val = image[index] + num_diffs;
    decrement_range(&histograms[val * width], AVMMAX(j - pad_size, 0),
                    AVMMIN(j + pad_size + 1, width));
  }
}

static inline void cambi_histogram_sub(uint16_t *histograms,
                                       const uint16_t *image,
                                       const uint16_t *mask, int i, int j,
                                       int width, ptrdiff_t stride,
                                       uint16_t pad_size, uint16_t num_diffs) {
  const long int index = (i - pad_size - 1) * stride + j;
  if (mask[index]) {
    uint16_t val = image[index] + num_diffs;
    decrement_range(&histograms[val * width], j - pad_size, j + pad_size + 1);
  }
}

static inline void cambi_histogram_add_edge(uint16_t *histograms,
                                            uint16_t *image, uint16_t *mask,
                                            int i, int j, int width,
                                            ptrdiff_t stride, uint16_t pad_size,
                                            const uint16_t num_diffs) {
  const long int index = (i + pad_size) * stride + j;
  if (mask[index]) {
    const uint16_t val = image[index] + num_diffs;
    increment_range(&histograms[val * width], AVMMAX(j - pad_size, 0),
                    AVMMIN(j + pad_size + 1, width));
  }
}

static inline void cambi_histogram_add(uint16_t *histograms,
                                       const uint16_t *image,
                                       const uint16_t *mask, int i, int j,
                                       int width, ptrdiff_t stride,
                                       uint16_t pad_size, uint16_t num_diffs) {
  const long int index = (i + pad_size) * stride + j;
  if (mask[index]) {
    const uint16_t val = image[index] + num_diffs;
    increment_range(&histograms[val * width], j - pad_size, j + pad_size + 1);
  }
}

static inline void cambi_histogram_add_edge_first_pass(
    uint16_t *histograms, uint16_t *image, uint16_t *mask, int i, int j,
    int width, ptrdiff_t stride, uint16_t pad_size, const uint16_t num_diffs) {
  const long int index = i * stride + j;
  if (mask[index]) {
    const uint16_t val = image[index] + num_diffs;
    increment_range(&histograms[val * width], AVMMAX(j - pad_size, 0),
                    AVMMIN(j + pad_size + 1, width));
  }
}

static inline void cambi_histogram_add_first_pass(
    uint16_t *histograms, uint16_t *image, uint16_t *mask, int i, int j,
    int width, ptrdiff_t stride, uint16_t pad_size, const uint16_t num_diffs) {
  const long int index = i * stride + j;
  if (mask[index]) {
    const uint16_t val = image[index] + num_diffs;
    increment_range(&histograms[val * width], j - pad_size, j + pad_size + 1);
  }
}

static float c_value_pixel(const uint16_t *histograms, uint16_t value,
                           const int *diff_weights, uint16_t num_diffs,
                           const uint16_t *tvi_thresholds, int histogram_col,
                           int histogram_width) {
  const uint16_t p_0 = histograms[value * histogram_width + histogram_col];
  float val;
  float c_value = (float)0.0;
  for (uint16_t d = 0; d < num_diffs; d++) {
    if (value <= tvi_thresholds[d]) {
      const uint16_t p_1 =
          histograms[(value + d + 1) * histogram_width + histogram_col];
      const uint16_t p_2 =
          histograms[(value - d - 1) * histogram_width + histogram_col];
      if (p_1 > p_2) {
        val = (float)(diff_weights[d] * p_0 * p_1) / (float)(p_1 + p_0);
      } else {
        val = (float)(diff_weights[d] * p_0 * p_2) / (float)(p_2 + p_0);
      }

      if (val > c_value) {
        c_value = val;
      }
    }
  }
  return c_value;
}

static inline void calculate_c_values_row(float *c_values, uint16_t *histograms,
                                          uint16_t *image, const uint16_t *mask,
                                          int row, int width, ptrdiff_t stride,
                                          const uint16_t num_diffs,
                                          const uint16_t *tvi_for_diff,
                                          Av2BandDetectInfo *dbi) {
  for (int col = 0; col < width; col++) {
    if (mask[row * stride + col]) {
      c_values[row * width + col] = c_value_pixel(
          histograms, image[row * stride + col] + num_diffs, dbi->diffs_weights,
          num_diffs, tvi_for_diff, col, width);
    }
  }
}

static void calculate_c_values(Av2BandDetectInfo *dbi, int width, int height) {
  uint16_t *image = dbi->frame;
  uint16_t *mask = dbi->mask;
  const ptrdiff_t stride = dbi->stride;
  float *c_values = dbi->buffers.c_values;
  uint16_t *histograms = dbi->buffers.c_values_histograms;
  const uint16_t window_size = dbi->window_size;
  const uint16_t num_diffs = dbi->num_diffs;
  uint16_t *tvi_for_diff = dbi->tvi_for_diff;
  const uint16_t pad_size = window_size >> 1;
  const uint16_t num_bins = 1024 + 2 * num_diffs;

  memset(c_values, 0.0, sizeof(float) * width * height);

  // Use a histogram for each pixel in width
  // histograms[i * width + j] accesses the j'th histogram, i'th value
  // This is done for cache optimization reasons
  memset(histograms, 0, width * num_bins * sizeof(uint16_t));

  // First pass: first pad_size rows
  for (int i = 0; i < pad_size; i++) {
    for (int j = 0; j < pad_size; j++) {
      cambi_histogram_add_edge_first_pass(histograms, image, mask, i, j, width,
                                          stride, pad_size, num_diffs);
    }
    for (int j = pad_size; j < width - pad_size - 1; j++) {
      cambi_histogram_add_first_pass(histograms, image, mask, i, j, width,
                                     stride, pad_size, num_diffs);
    }
    for (int j = AVMMAX(width - pad_size - 1, pad_size); j < width; j++) {
      cambi_histogram_add_edge_first_pass(histograms, image, mask, i, j, width,
                                          stride, pad_size, num_diffs);
    }
  }

  // Iterate over all rows, unrolled into 3 loops to avoid conditions
  for (int i = 0; i < pad_size + 1; i++) {
    if (i + pad_size < height) {
      for (int j = 0; j < pad_size; j++) {
        cambi_histogram_add_edge(histograms, image, mask, i, j, width, stride,
                                 pad_size, num_diffs);
      }
      for (int j = pad_size; j < width - pad_size - 1; j++) {
        cambi_histogram_add(histograms, image, mask, i, j, width, stride,
                            pad_size, num_diffs);
      }
      for (int j = AVMMAX(width - pad_size - 1, pad_size); j < width; j++) {
        cambi_histogram_add_edge(histograms, image, mask, i, j, width, stride,
                                 pad_size, num_diffs);
      }
    }
    calculate_c_values_row(c_values, histograms, image, mask, i, width, stride,
                           num_diffs, tvi_for_diff, dbi);
  }

  for (int i = pad_size + 1; i < height - pad_size; i++) {
    for (int j = 0; j < pad_size; j++) {
      cambi_histogram_sub_edge(histograms, image, mask, i, j, width, stride,
                               pad_size, num_diffs);
      cambi_histogram_add_edge(histograms, image, mask, i, j, width, stride,
                               pad_size, num_diffs);
    }
    for (int j = pad_size; j < width - pad_size - 1; j++) {
      cambi_histogram_sub(histograms, image, mask, i, j, width, stride,
                          pad_size, num_diffs);
      cambi_histogram_add(histograms, image, mask, i, j, width, stride,
                          pad_size, num_diffs);
    }
    for (int j = AVMMAX(width - pad_size - 1, pad_size); j < width; j++) {
      cambi_histogram_sub_edge(histograms, image, mask, i, j, width, stride,
                               pad_size, num_diffs);
      cambi_histogram_add_edge(histograms, image, mask, i, j, width, stride,
                               pad_size, num_diffs);
    }
    calculate_c_values_row(c_values, histograms, image, mask, i, width, stride,
                           num_diffs, tvi_for_diff, dbi);
  }

  for (int i = height - pad_size; i < height; i++) {
    if (i - pad_size - 1 >= 0) {
      for (int j = 0; j < pad_size; j++) {
        cambi_histogram_sub_edge(histograms, image, mask, i, j, width, stride,
                                 pad_size, num_diffs);
      }
      for (int j = pad_size; j < width - pad_size - 1; j++) {
        cambi_histogram_sub(histograms, image, mask, i, j, width, stride,
                            pad_size, num_diffs);
      }
      for (int j = AVMMAX(width - pad_size - 1, pad_size); j < width; j++) {
        cambi_histogram_sub_edge(histograms, image, mask, i, j, width, stride,
                                 pad_size, num_diffs);
      }
    }
    calculate_c_values_row(c_values, histograms, image, mask, i, width, stride,
                           num_diffs, tvi_for_diff, dbi);
  }
}

static double average_topk_elements(const float *arr, int topk_elements) {
  double sum = 0;
  for (int i = 0; i < topk_elements; i++) sum += arr[i];

  return (double)sum / topk_elements;
}

static void quick_select(float *arr, int n, int k) {
  if (n == k) return;
  int left = 0;
  int right = n - 1;
  while (left < right) {
    float pivot = arr[k];
    int i = left;
    int j = right;
    do {
      while (arr[i] > pivot) {
        i++;
      }
      while (arr[j] < pivot) {
        j--;
      }
      if (i <= j) {
        SWAP_FLOATS(arr[i], arr[j]);
        i++;
        j--;
      }
    } while (i <= j);
    if (j < k) {
      left = i;
    }
    if (k < i) {
      right = j;
    }
  }
}

static double spatial_pooling(float *c_values, double topk, int width,
                              int height) {
  const int num_elements = height * width;
  const int topk_num_elements =
      CLAMP((int)(topk * num_elements), 1, num_elements);
  quick_select(c_values, num_elements, topk_num_elements);
  return average_topk_elements(c_values, topk_num_elements);
}

// Inner product weighting scores for each scale
static inline double weight_scores_per_scale(const int *scale_weights,
                                             const double *scores_per_scale,
                                             uint16_t normalization) {
  double score = 0.0;
  for (unsigned scale = 0; scale < CAMBI_NUM_SCALES; scale++) {
    score += scores_per_scale[scale] * scale_weights[scale];
  }
  return score / normalization;
}

double cambi_score(Av2BandDetectInfo *dbi, int frame_width, int frame_height) {
  double scores_per_scale[CAMBI_NUM_SCALES];
  int scaled_width = frame_width;
  int scaled_height = frame_height;

  for (unsigned scale = 0; scale < CAMBI_NUM_SCALES; scale++) {
    if (scale > 0) {
      scaled_width = (scaled_width + 1) >> 1;
      scaled_height = (scaled_height + 1) >> 1;
      cambi_decimate(dbi->frame, dbi->stride, scaled_width, scaled_height);
      cambi_decimate(dbi->mask, dbi->stride, scaled_width, scaled_height);
    } else {
      cambi_get_spatial_mask(dbi, scaled_width, scaled_height);
    }
    cambi_filter_mode(dbi, scaled_width, scaled_height);
    calculate_c_values(dbi, scaled_width, scaled_height);
    scores_per_scale[scale] = spatial_pooling(dbi->buffers.c_values, dbi->topk,
                                              scaled_width, scaled_height);
  }

  return weight_scores_per_scale(dbi->scale_weights, scores_per_scale,
                                 dbi->pixels_in_window);
}

double avm_compute_cambi(const YV12_BUFFER_CONFIG *frame,
                         Av2BandDetectInfo *dbi, MACROBLOCKD *xd) {
  av2_setup_dst_planes(xd->plane, frame, 0, 0, 0, 1, NULL);
  struct buf_2d pre_buf = xd->plane[0].dst;
  const int src_stride = xd->plane[0].dst.stride;
  const int frame_width = pre_buf.width;
  const int frame_height = pre_buf.height;
  const int bit_depth = xd->bd;
  uint16_t *src16 = pre_buf.buf;

  set_cambi_window(dbi);
  cambi_preprocessing(src16, src_stride, frame_width, frame_height, bit_depth,
                      dbi->frame, dbi->stride, frame_width, frame_height);
  return cambi_score(dbi, frame_width, frame_height);
}

// TODO(Joel): move CAMBI initialization & buffers
static const int cambi_scale_weights[CAMBI_NUM_SCALES] = { 16, 8, 4, 2, 1 };
static const int cambi_contrast_weights[8] = { 1, 2, 3, 4, 4, 5, 5, 6 };

void set_contrast_arrays_cambi(Av2BandDetectInfo *const dbi) {
  const int num_diffs = dbi->num_diffs;

  dbi->diffs_weights = avm_malloc(sizeof(dbi->diffs_weights) * num_diffs);
  for (int d = 0; d < num_diffs; d++) {
    dbi->diffs_weights[d] = cambi_contrast_weights[d];
  }

  for (int scale = 0; scale < CAMBI_NUM_SCALES; scale++) {
    dbi->scale_weights[scale] = cambi_scale_weights[scale];
  }
}

void set_tvi_per_contrast(Av2BandDetectInfo *const dbi, int bitdepth) {
  (void)bitdepth;
  const int num_diffs = dbi->num_diffs;
  dbi->tvi_for_diff = avm_malloc(sizeof(dbi->tvi_for_diff) * num_diffs);
  dbi->tvi_threshold = CAMBI_TVI;

  // Todo(Joel): Set TVI values from encoder input parameters
  for (int d = 0; d < num_diffs; d++) {
    dbi->tvi_for_diff[d] = -1;
  }
}

int avm_band_detection_init(Av2BandDetectInfo *const dbi, const int frame_width,
                            const int frame_height, const int bit_depth) {
  if (frame_width < CAMBI_MIN_WIDTH || frame_height > CAMBI_MAX_WIDTH ||
      !(bit_depth == 8 || bit_depth == 10)) {
    dbi->band_detected = 0;
    dbi->do_band_detection = 0;
    return dbi->do_band_detection;
  }

  dbi->do_band_detection = 1;
  dbi->topk = CAMBI_DEFAULT_TOPK_POOLING;
  dbi->stride = frame_width;
  dbi->height = frame_height;
  dbi->frame = avm_malloc(sizeof(*dbi->frame) * frame_height * dbi->stride);

  dbi->max_log_contrast = CAMBI_DEFAULT_MAX_LOG_CONTRAST;
  dbi->num_diffs = 1 << dbi->max_log_contrast;
  set_contrast_arrays_cambi(dbi);
  set_tvi_per_contrast(dbi, 10);
  dbi->num_bins = (1 << 10) + 2 * dbi->num_diffs;

  dbi->buffers.filter_mode_buffer =
      avm_malloc(3 * frame_width * sizeof(uint16_t));

  int pad_size = CAMBI_MASK_FILTER_SIZE >> 1;
  int dp_width = frame_width + 2 * pad_size + 1;
  int dp_height = 2 * pad_size + 2;
  dbi->buffers.mask_dp = avm_malloc(dp_height * dp_width * sizeof(uint32_t));

  dbi->mask = avm_malloc(sizeof(*dbi->mask) * frame_height * dbi->stride);
  dbi->buffers.c_values =
      avm_malloc(sizeof(float) * frame_height * dbi->stride);
  dbi->buffers.c_values_histograms =
      avm_malloc(frame_width * dbi->num_bins * sizeof(uint16_t));

  return dbi->do_band_detection;
}

void avm_band_detection_close(Av2BandDetectInfo *const dbi) {
  if (!dbi->do_band_detection) {
    return;
  }
  avm_free(dbi->frame);
  avm_free(dbi->mask);

  // set_tvi_per_contrast
  avm_free(dbi->tvi_for_diff);

  avm_free(dbi->buffers.filter_mode_buffer);
  avm_free(dbi->buffers.mask_dp);

  avm_free(dbi->diffs_weights);
  avm_free(dbi->buffers.c_values);
  avm_free(dbi->buffers.c_values_histograms);
}

/*!\brief Assess banding via CAMBI
 *
 * Searches for presence of banding computing CAMBI on refernce and distorted
 *
 * \param[in]      frame        Compressed frame buffer
 * \param[in]      ref          Source frame buffer
 * \param[in,out]  cm           Pointer to top level common structure
 * \param[in]      dbi          Banding detection information structure
 * \param[out]     band_metadata   Banding hints metadata structure to be filled

 *
 * \return Nothing is returned. Instead, presence of banding is stored
 */
void avm_band_detection(const YV12_BUFFER_CONFIG *frame,
                        const YV12_BUFFER_CONFIG *ref,
                        Av2BandDetectInfo *const dbi, MACROBLOCKD *xd,
                        avm_banding_hints_metadata_t *band_metadata) {
  const double cambi_ref = avm_compute_cambi(ref, dbi, xd);
  const double cambi_enc = avm_compute_cambi(frame, dbi, xd);

  const int bit_depth = xd->bd;
  const double diff_threshold =
      bit_depth == 8 ? CAMBI_DIFF_THRESHOLD_8b : CAMBI_DIFF_THRESHOLD_10b;

  dbi->band_detected = (cambi_enc - cambi_ref >= diff_threshold);

  const double src_threshold =
      bit_depth == 8 ? CAMBI_SOURCE_THRESHOLD_8b : CAMBI_SOURCE_THRESHOLD_10b;

  // Initialize and populate the band metadata structure
  if (band_metadata) {
    memset(band_metadata, 0, sizeof(avm_banding_hints_metadata_t));
    band_metadata->coding_banding_present_flag = dbi->band_detected;
    band_metadata->source_banding_present_flag = (cambi_ref > src_threshold);
    // For now, set banding_hints_flag to 0 (no detailed hints)
    // This can be extended in the future to include more detailed information
    band_metadata->banding_hints_flag = 0;
  }
}
