/*
 * Copyright (c) 2016, Alliance for Open Media. All rights reserved
 *
 * This source code is subject to the terms of the BSD 2 Clause License and
 * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
 * was not distributed with this source code in the LICENSE file, you can
 * obtain it at www.aomedia.org/license/software. 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 www.aomedia.org/license/patent.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <memory.h>
#include <math.h>
#include <assert.h>

#include "config/aom_dsp_rtcd.h"

#include "av1/encoder/global_motion.h"

#include "av1/common/convolve.h"
#include "av1/common/warped_motion.h"

#include "av1/encoder/segmentation.h"

#define MIN_TRANS_THRESH (1 * GM_TRANS_DECODE_FACTOR)

// Border over which to compute the global motion
#define ERRORADV_BORDER 0

/* clang-format off */
// Error metric used for global motion evaluation.
// For 8-bit input, the pixel error used to index this table will always
// be between -255 and +255. But for 10- and 12-bit input, we use interpolation
// which means that we need to support indices of -256 and +256 as well.
// Therefore, the table is offset so that logical index 0 corresponds to
// error_measure_lut[256].
const int error_measure_lut[513] = {
    // pow 0.7
    16384, 16384, 16339, 16294, 16249, 16204, 16158, 16113,
    16068, 16022, 15977, 15932, 15886, 15840, 15795, 15749,
    15703, 15657, 15612, 15566, 15520, 15474, 15427, 15381,
    15335, 15289, 15242, 15196, 15149, 15103, 15056, 15010,
    14963, 14916, 14869, 14822, 14775, 14728, 14681, 14634,
    14587, 14539, 14492, 14445, 14397, 14350, 14302, 14254,
    14206, 14159, 14111, 14063, 14015, 13967, 13918, 13870,
    13822, 13773, 13725, 13676, 13628, 13579, 13530, 13481,
    13432, 13383, 13334, 13285, 13236, 13187, 13137, 13088,
    13038, 12988, 12939, 12889, 12839, 12789, 12739, 12689,
    12639, 12588, 12538, 12487, 12437, 12386, 12335, 12285,
    12234, 12183, 12132, 12080, 12029, 11978, 11926, 11875,
    11823, 11771, 11719, 11667, 11615, 11563, 11511, 11458,
    11406, 11353, 11301, 11248, 11195, 11142, 11089, 11036,
    10982, 10929, 10875, 10822, 10768, 10714, 10660, 10606,
    10552, 10497, 10443, 10388, 10333, 10279, 10224, 10168,
    10113, 10058, 10002,  9947,  9891,  9835,  9779,  9723,
     9666,  9610,  9553,  9497,  9440,  9383,  9326,  9268,
     9211,  9153,  9095,  9037,  8979,  8921,  8862,  8804,
     8745,  8686,  8627,  8568,  8508,  8449,  8389,  8329,
     8269,  8208,  8148,  8087,  8026,  7965,  7903,  7842,
     7780,  7718,  7656,  7593,  7531,  7468,  7405,  7341,
     7278,  7214,  7150,  7086,  7021,  6956,  6891,  6826,
     6760,  6695,  6628,  6562,  6495,  6428,  6361,  6293,
     6225,  6157,  6089,  6020,  5950,  5881,  5811,  5741,
     5670,  5599,  5527,  5456,  5383,  5311,  5237,  5164,
     5090,  5015,  4941,  4865,  4789,  4713,  4636,  4558,
     4480,  4401,  4322,  4242,  4162,  4080,  3998,  3916,
     3832,  3748,  3663,  3577,  3490,  3402,  3314,  3224,
     3133,  3041,  2948,  2854,  2758,  2661,  2562,  2461,
     2359,  2255,  2148,  2040,  1929,  1815,  1698,  1577,
     1452,  1323,  1187,  1045,   894,   731,   550,   339,
        0,   339,   550,   731,   894,  1045,  1187,  1323,
     1452,  1577,  1698,  1815,  1929,  2040,  2148,  2255,
     2359,  2461,  2562,  2661,  2758,  2854,  2948,  3041,
     3133,  3224,  3314,  3402,  3490,  3577,  3663,  3748,
     3832,  3916,  3998,  4080,  4162,  4242,  4322,  4401,
     4480,  4558,  4636,  4713,  4789,  4865,  4941,  5015,
     5090,  5164,  5237,  5311,  5383,  5456,  5527,  5599,
     5670,  5741,  5811,  5881,  5950,  6020,  6089,  6157,
     6225,  6293,  6361,  6428,  6495,  6562,  6628,  6695,
     6760,  6826,  6891,  6956,  7021,  7086,  7150,  7214,
     7278,  7341,  7405,  7468,  7531,  7593,  7656,  7718,
     7780,  7842,  7903,  7965,  8026,  8087,  8148,  8208,
     8269,  8329,  8389,  8449,  8508,  8568,  8627,  8686,
     8745,  8804,  8862,  8921,  8979,  9037,  9095,  9153,
     9211,  9268,  9326,  9383,  9440,  9497,  9553,  9610,
     9666,  9723,  9779,  9835,  9891,  9947, 10002, 10058,
    10113, 10168, 10224, 10279, 10333, 10388, 10443, 10497,
    10552, 10606, 10660, 10714, 10768, 10822, 10875, 10929,
    10982, 11036, 11089, 11142, 11195, 11248, 11301, 11353,
    11406, 11458, 11511, 11563, 11615, 11667, 11719, 11771,
    11823, 11875, 11926, 11978, 12029, 12080, 12132, 12183,
    12234, 12285, 12335, 12386, 12437, 12487, 12538, 12588,
    12639, 12689, 12739, 12789, 12839, 12889, 12939, 12988,
    13038, 13088, 13137, 13187, 13236, 13285, 13334, 13383,
    13432, 13481, 13530, 13579, 13628, 13676, 13725, 13773,
    13822, 13870, 13918, 13967, 14015, 14063, 14111, 14159,
    14206, 14254, 14302, 14350, 14397, 14445, 14492, 14539,
    14587, 14634, 14681, 14728, 14775, 14822, 14869, 14916,
    14963, 15010, 15056, 15103, 15149, 15196, 15242, 15289,
    15335, 15381, 15427, 15474, 15520, 15566, 15612, 15657,
    15703, 15749, 15795, 15840, 15886, 15932, 15977, 16022,
    16068, 16113, 16158, 16204, 16249, 16294, 16339, 16384,
    16384,
};
/* clang-format on */

int av1_is_enough_erroradvantage(double best_erroradvantage, int params_cost) {
  return best_erroradvantage < erroradv_tr &&
         best_erroradvantage * params_cost < erroradv_prod_tr;
}

static void convert_to_params(const double *params, int32_t *model) {
  int i;
  model[0] = (int32_t)floor(params[0] * (1 << GM_TRANS_PREC_BITS) + 0.5);
  model[1] = (int32_t)floor(params[1] * (1 << GM_TRANS_PREC_BITS) + 0.5);
  model[0] = (int32_t)clamp(model[0], GM_TRANS_MIN, GM_TRANS_MAX) *
             GM_TRANS_DECODE_FACTOR;
  model[1] = (int32_t)clamp(model[1], GM_TRANS_MIN, GM_TRANS_MAX) *
             GM_TRANS_DECODE_FACTOR;

  for (i = 2; i < 6; ++i) {
    const int diag_value = ((i == 2 || i == 5) ? (1 << GM_ALPHA_PREC_BITS) : 0);
    model[i] = (int32_t)floor(params[i] * (1 << GM_ALPHA_PREC_BITS) + 0.5);
    model[i] =
        (int32_t)clamp(model[i] - diag_value, GM_ALPHA_MIN, GM_ALPHA_MAX);
    model[i] = (model[i] + diag_value) * GM_ALPHA_DECODE_FACTOR;
  }
}

void av1_convert_model_to_params(const double *params,
                                 WarpedMotionParams *model) {
  convert_to_params(params, model->wmmat);
  model->wmtype = get_wmtype(model);
  model->invalid = 0;
}

// Adds some offset to a global motion parameter and handles
// all of the necessary precision shifts, clamping, and
// zero-centering.
static int32_t add_param_offset(int param_index, int32_t param_value,
                                int32_t offset) {
  const int scale_vals[2] = { GM_TRANS_PREC_DIFF, GM_ALPHA_PREC_DIFF };
  const int clamp_vals[2] = { GM_TRANS_MAX, GM_ALPHA_MAX };
  // type of param: 0 - translation, 1 - affine
  const int param_type = (param_index < 2 ? 0 : 1);
  const int is_one_centered = (param_index == 2 || param_index == 5);

  // Make parameter zero-centered and offset the shift that was done to make
  // it compatible with the warped model
  param_value = (param_value - (is_one_centered << WARPEDMODEL_PREC_BITS)) >>
                scale_vals[param_type];
  // Add desired offset to the rescaled/zero-centered parameter
  param_value += offset;
  // Clamp the parameter so it does not overflow the number of bits allotted
  // to it in the bitstream
  param_value = (int32_t)clamp(param_value, -clamp_vals[param_type],
                               clamp_vals[param_type]);
  // Rescale the parameter to WARPEDMODEL_PRECISION_BITS so it is compatible
  // with the warped motion library
  param_value *= (1 << scale_vals[param_type]);

  // Undo the zero-centering step if necessary
  return param_value + (is_one_centered << WARPEDMODEL_PREC_BITS);
}

static void force_wmtype(WarpedMotionParams *wm, TransformationType wmtype) {
  switch (wmtype) {
    case IDENTITY:
      wm->wmmat[0] = 0;
      wm->wmmat[1] = 0;
      AOM_FALLTHROUGH_INTENDED;
    case TRANSLATION:
      wm->wmmat[2] = 1 << WARPEDMODEL_PREC_BITS;
      wm->wmmat[3] = 0;
      AOM_FALLTHROUGH_INTENDED;
    case ROTZOOM:
      wm->wmmat[4] = -wm->wmmat[3];
      wm->wmmat[5] = wm->wmmat[2];
      AOM_FALLTHROUGH_INTENDED;
    case AFFINE: break;
    default: assert(0);
  }
  wm->wmtype = wmtype;
}

#if CONFIG_AV1_HIGHBITDEPTH
static INLINE int generic_sad_highbd(const uint16_t *const ref, int ref_stride,
                                     const uint16_t *const dst, int dst_stride,
                                     int p_width, int p_height) {
  // This function should only be called for patches smaller than
  // WARP_ERROR_BLOCK x WARP_ERROR_BLOCK. This keeps the number of pixels
  // small enough that we don't need a 64-bit accumulator
  assert(p_width <= WARP_ERROR_BLOCK && p_height <= WARP_ERROR_BLOCK);

  int sad = 0;
  for (int i = 0; i < p_height; ++i) {
    for (int j = 0; j < p_width; ++j) {
      sad += abs(dst[j + i * dst_stride] - ref[j + i * ref_stride]);
    }
  }
  return sad;
}

#if WARP_ERROR_BLOCK != 32
#error "Need to change SAD call size in highbd_segmented_frame_error"
#endif  // WARP_ERROR_BLOCK != 32
static int64_t highbd_segmented_frame_error(
    const uint16_t *const ref, int ref_stride, const uint16_t *const dst,
    int dst_stride, int p_width, int p_height, int bd, uint8_t *segment_map,
    int segment_map_stride) {
  (void)bd;
  int patch_w, patch_h;
  const int error_bsize_w = AOMMIN(p_width, WARP_ERROR_BLOCK);
  const int error_bsize_h = AOMMIN(p_height, WARP_ERROR_BLOCK);
  int64_t sum_error = 0;
  for (int i = 0; i < p_height; i += WARP_ERROR_BLOCK) {
    for (int j = 0; j < p_width; j += WARP_ERROR_BLOCK) {
      int seg_x = j >> WARP_ERROR_BLOCK_LOG;
      int seg_y = i >> WARP_ERROR_BLOCK_LOG;
      // Only compute the error if this block contains inliers from the motion
      // model
      if (!segment_map[seg_y * segment_map_stride + seg_x]) continue;

      // avoid computing error into the frame padding
      patch_w = AOMMIN(error_bsize_w, p_width - j);
      patch_h = AOMMIN(error_bsize_h, p_height - i);

      if (patch_w == WARP_ERROR_BLOCK && patch_h == WARP_ERROR_BLOCK) {
        sum_error += aom_highbd_sad32x32(
            CONVERT_TO_BYTEPTR(ref + j + i * ref_stride), ref_stride,
            CONVERT_TO_BYTEPTR(dst + j + i * dst_stride), dst_stride);
      } else {
        sum_error += generic_sad_highbd(ref + j + i * ref_stride, ref_stride,
                                        dst + j + i * dst_stride, dst_stride,
                                        patch_w, patch_h);
      }
    }
  }
  return sum_error;
}

#if WARP_ERROR_BLOCK != 32
#error "Need to change SAD call size in highbd_warp_error"
#endif  // WARP_ERROR_BLOCK != 32
static int64_t highbd_warp_error(WarpedMotionParams *wm,
                                 const uint16_t *const ref, int ref_width,
                                 int ref_height, int ref_stride,
                                 const uint16_t *const dst, int dst_stride,
                                 int p_col, int p_row, int p_width,
                                 int p_height, int subsampling_x,
                                 int subsampling_y, int bd, int64_t best_error,
                                 uint8_t *segment_map, int segment_map_stride) {
  int64_t gm_sumerr = 0;
  const int error_bsize_w = AOMMIN(p_width, WARP_ERROR_BLOCK);
  const int error_bsize_h = AOMMIN(p_height, WARP_ERROR_BLOCK);
  DECLARE_ALIGNED(32, uint16_t, tmp[WARP_ERROR_BLOCK * WARP_ERROR_BLOCK]);

  ConvolveParams conv_params = get_conv_params(0, 0, bd);
  conv_params.use_dist_wtd_comp_avg = 0;
  for (int i = p_row; i < p_row + p_height; i += WARP_ERROR_BLOCK) {
    for (int j = p_col; j < p_col + p_width; j += WARP_ERROR_BLOCK) {
      int seg_x = j >> WARP_ERROR_BLOCK_LOG;
      int seg_y = i >> WARP_ERROR_BLOCK_LOG;
      // Only compute the error if this block contains inliers from the motion
      // model
      if (!segment_map[seg_y * segment_map_stride + seg_x]) continue;
      // avoid warping extra 8x8 blocks in the padded region of the frame
      // when p_width and p_height are not multiples of WARP_ERROR_BLOCK
      const int warp_w = AOMMIN(error_bsize_w, p_col + ref_width - j);
      const int warp_h = AOMMIN(error_bsize_h, p_row + ref_height - i);
      highbd_warp_plane(wm, ref, ref_width, ref_height, ref_stride, tmp, j, i,
                        warp_w, warp_h, WARP_ERROR_BLOCK, subsampling_x,
                        subsampling_y, bd, &conv_params);

      if (warp_w == WARP_ERROR_BLOCK && warp_h == WARP_ERROR_BLOCK) {
        gm_sumerr += aom_highbd_sad32x32(
            CONVERT_TO_BYTEPTR(tmp), WARP_ERROR_BLOCK,
            CONVERT_TO_BYTEPTR(dst + j + i * dst_stride), dst_stride);
      } else {
        gm_sumerr +=
            generic_sad_highbd(tmp, WARP_ERROR_BLOCK, dst + j + i * dst_stride,
                               dst_stride, warp_w, warp_h);
      }

      if (gm_sumerr > best_error) return INT64_MAX;
    }
  }
  return gm_sumerr;
}
#endif

static INLINE int generic_sad(const uint8_t *const ref, int ref_stride,
                              const uint8_t *const dst, int dst_stride,
                              int p_width, int p_height) {
  // This function should only be called for patches smaller than
  // WARP_ERROR_BLOCK x WARP_ERROR_BLOCK. This keeps the number of pixels
  // small enough that we don't need a 64-bit accumulator
  assert(p_width <= WARP_ERROR_BLOCK && p_height <= WARP_ERROR_BLOCK);

  int sad = 0;
  for (int i = 0; i < p_height; ++i) {
    for (int j = 0; j < p_width; ++j) {
      sad += abs(dst[j + i * dst_stride] - ref[j + i * ref_stride]);
    }
  }
  return sad;
}

#if WARP_ERROR_BLOCK != 32
#error "Need to change SAD call size in segmented_warp_error"
#endif  // WARP_ERROR_BLOCK != 32
static int64_t segmented_frame_error(const uint8_t *const ref, int ref_stride,
                                     const uint8_t *const dst, int dst_stride,
                                     int p_width, int p_height,
                                     uint8_t *segment_map,
                                     int segment_map_stride) {
  int patch_w, patch_h;
  const int error_bsize_w = AOMMIN(p_width, WARP_ERROR_BLOCK);
  const int error_bsize_h = AOMMIN(p_height, WARP_ERROR_BLOCK);
  int64_t sum_error = 0;
  for (int i = 0; i < p_height; i += WARP_ERROR_BLOCK) {
    for (int j = 0; j < p_width; j += WARP_ERROR_BLOCK) {
      int seg_x = j >> WARP_ERROR_BLOCK_LOG;
      int seg_y = i >> WARP_ERROR_BLOCK_LOG;
      // Only compute the error if this block contains inliers from the motion
      // model
      if (!segment_map[seg_y * segment_map_stride + seg_x]) continue;

      // avoid computing error into the frame padding
      patch_w = AOMMIN(error_bsize_w, p_width - j);
      patch_h = AOMMIN(error_bsize_h, p_height - i);

      if (patch_w == WARP_ERROR_BLOCK && patch_h == WARP_ERROR_BLOCK) {
        sum_error += aom_sad32x32(ref + j + i * ref_stride, ref_stride,
                                  dst + j + i * dst_stride, dst_stride);
      } else {
        sum_error +=
            generic_sad(ref + j + i * ref_stride, ref_stride,
                        dst + j + i * dst_stride, dst_stride, patch_w, patch_h);
      }
    }
  }
  return sum_error;
}

#if WARP_ERROR_BLOCK != 32
#error "Need to change SAD call size in warp_error"
#endif  // WARP_ERROR_BLOCK != 32
static int64_t warp_error(WarpedMotionParams *wm, const uint8_t *const ref,
                          int ref_width, int ref_height, int ref_stride,
                          const uint8_t *const dst, int dst_stride, int p_col,
                          int p_row, int p_width, int p_height,
                          int subsampling_x, int subsampling_y,
                          int64_t best_error, uint8_t *segment_map,
                          int segment_map_stride) {
  int64_t gm_sumerr = 0;
  int warp_w, warp_h;
  const int error_bsize_w = AOMMIN(p_width, WARP_ERROR_BLOCK);
  const int error_bsize_h = AOMMIN(p_height, WARP_ERROR_BLOCK);
  DECLARE_ALIGNED(16, uint8_t, tmp[WARP_ERROR_BLOCK * WARP_ERROR_BLOCK]);
  ConvolveParams conv_params = get_conv_params(0, 0, 8);
  conv_params.use_dist_wtd_comp_avg = 0;

  for (int i = p_row; i < p_row + p_height; i += WARP_ERROR_BLOCK) {
    for (int j = p_col; j < p_col + p_width; j += WARP_ERROR_BLOCK) {
      int seg_x = j >> WARP_ERROR_BLOCK_LOG;
      int seg_y = i >> WARP_ERROR_BLOCK_LOG;
      // Only compute the error if this block contains inliers from the motion
      // model
      if (!segment_map[seg_y * segment_map_stride + seg_x]) continue;
      // avoid warping extra 8x8 blocks in the padded region of the frame
      // when p_width and p_height are not multiples of WARP_ERROR_BLOCK
      warp_w = AOMMIN(error_bsize_w, p_col + ref_width - j);
      warp_h = AOMMIN(error_bsize_h, p_row + ref_height - i);
      warp_plane(wm, ref, ref_width, ref_height, ref_stride, tmp, j, i, warp_w,
                 warp_h, WARP_ERROR_BLOCK, subsampling_x, subsampling_y,
                 &conv_params);

      if (warp_w == WARP_ERROR_BLOCK && warp_h == WARP_ERROR_BLOCK) {
        gm_sumerr += aom_sad32x32(tmp, WARP_ERROR_BLOCK,
                                  dst + j + i * dst_stride, dst_stride);
      } else {
        gm_sumerr +=
            generic_sad(tmp, WARP_ERROR_BLOCK, dst + j + i * dst_stride,
                        dst_stride, warp_w, warp_h);
      }

      if (gm_sumerr > best_error) return INT64_MAX;
    }
  }
  return gm_sumerr;
}

int64_t av1_segmented_frame_error(int use_hbd, int bd, const uint8_t *ref,
                                  int ref_stride, uint8_t *dst, int dst_stride,
                                  int p_width, int p_height,
                                  uint8_t *segment_map,
                                  int segment_map_stride) {
#if CONFIG_AV1_HIGHBITDEPTH
  if (use_hbd) {
    return highbd_segmented_frame_error(
        CONVERT_TO_SHORTPTR(ref), ref_stride, CONVERT_TO_SHORTPTR(dst),
        dst_stride, p_width, p_height, bd, segment_map, segment_map_stride);
  }
#endif
  (void)use_hbd;
  (void)bd;
  return segmented_frame_error(ref, ref_stride, dst, dst_stride, p_width,
                               p_height, segment_map, segment_map_stride);
}

int64_t av1_warp_error(WarpedMotionParams *wm, int use_hbd, int bd,
                       const uint8_t *ref, int ref_width, int ref_height,
                       int ref_stride, uint8_t *dst, int dst_stride, int p_col,
                       int p_row, int p_width, int p_height, int subsampling_x,
                       int subsampling_y, int64_t best_error,
                       uint8_t *segment_map, int segment_map_stride) {
  if (!av1_get_shear_params(wm)) return INT64_MAX;
#if CONFIG_AV1_HIGHBITDEPTH
  if (use_hbd)
    return highbd_warp_error(wm, CONVERT_TO_SHORTPTR(ref), ref_width,
                             ref_height, ref_stride, CONVERT_TO_SHORTPTR(dst),
                             dst_stride, p_col, p_row, p_width, p_height,
                             subsampling_x, subsampling_y, bd, best_error,
                             segment_map, segment_map_stride);
#endif
  (void)use_hbd;
  (void)bd;
  return warp_error(wm, ref, ref_width, ref_height, ref_stride, dst, dst_stride,
                    p_col, p_row, p_width, p_height, subsampling_x,
                    subsampling_y, best_error, segment_map, segment_map_stride);
}

int64_t av1_refine_integerized_param(
    WarpedMotionParams *wm, TransformationType wmtype, int use_hbd, int bd,
    uint8_t *ref, int r_width, int r_height, int r_stride, uint8_t *dst,
    int d_width, int d_height, int d_stride, int n_refinements,
    int64_t ref_frame_error, uint8_t *segment_map, int segment_map_stride) {
  static const int max_trans_model_params[TRANS_TYPES] = { 0, 2, 4, 6 };
  const int border = ERRORADV_BORDER;
  int i = 0, p;
  int n_params = max_trans_model_params[wmtype];
  int32_t *param_mat = wm->wmmat;
  int64_t step_error, best_error;
  int32_t step;
  int32_t *param;
  int32_t curr_param;
  int32_t best_param;

  force_wmtype(wm, wmtype);
  wm->wmtype = get_wmtype(wm);

  if (n_refinements == 0) {
    // Compute the maximum error value that will be accepted, so that
    // av1_warp_error can terminate early if it proves the model will not
    // be accepted.
    int64_t selection_threshold = (int64_t)lrint(ref_frame_error * erroradv_tr);
    return av1_warp_error(wm, use_hbd, bd, ref, r_width, r_height, r_stride,
                          dst + border * d_stride + border, d_stride, border,
                          border, d_width - 2 * border, d_height - 2 * border,
                          0, 0, selection_threshold, segment_map,
                          segment_map_stride);
  }

  // When refining, use a slightly higher threshold for the initial error
  // calculation - see comment above erroradv_early_tr for why.
  int64_t selection_threshold =
      (int64_t)lrint(ref_frame_error * erroradv_early_tr);
  best_error =
      av1_warp_error(wm, use_hbd, bd, ref, r_width, r_height, r_stride,
                     dst + border * d_stride + border, d_stride, border, border,
                     d_width - 2 * border, d_height - 2 * border, 0, 0,
                     selection_threshold, segment_map, segment_map_stride);

  if (best_error > selection_threshold) {
    return INT64_MAX;
  }

  step = 1 << (n_refinements - 1);
  for (i = 0; i < n_refinements; i++, step >>= 1) {
    for (p = 0; p < n_params; ++p) {
      int step_dir = 0;
      param = param_mat + p;
      curr_param = *param;
      best_param = curr_param;
      // look to the left
      // Note: We have to use force_wmtype() to keep the proper symmetry for
      // ROTZOOM type models
      *param = add_param_offset(p, curr_param, -step);
      force_wmtype(wm, wmtype);
      step_error =
          av1_warp_error(wm, use_hbd, bd, ref, r_width, r_height, r_stride,
                         dst + border * d_stride + border, d_stride, border,
                         border, d_width - 2 * border, d_height - 2 * border, 0,
                         0, best_error, segment_map, segment_map_stride);
      if (step_error < best_error) {
        best_error = step_error;
        best_param = *param;
        step_dir = -1;
      }

      // look to the right
      *param = add_param_offset(p, curr_param, step);
      force_wmtype(wm, wmtype);
      step_error =
          av1_warp_error(wm, use_hbd, bd, ref, r_width, r_height, r_stride,
                         dst + border * d_stride + border, d_stride, border,
                         border, d_width - 2 * border, d_height - 2 * border, 0,
                         0, best_error, segment_map, segment_map_stride);
      if (step_error < best_error) {
        best_error = step_error;
        best_param = *param;
        step_dir = 1;
      }

      // look to the direction chosen above repeatedly until error increases
      // for the biggest step size
      while (step_dir) {
        *param = add_param_offset(p, best_param, step * step_dir);
        force_wmtype(wm, wmtype);
        step_error =
            av1_warp_error(wm, use_hbd, bd, ref, r_width, r_height, r_stride,
                           dst + border * d_stride + border, d_stride, border,
                           border, d_width - 2 * border, d_height - 2 * border,
                           0, 0, best_error, segment_map, segment_map_stride);
        if (step_error < best_error) {
          best_error = step_error;
          best_param = *param;
        } else {
          step_dir = 0;
        }
      }

      // Restore best parameter value so far
      *param = best_param;
      force_wmtype(wm, wmtype);
    }
  }

  wm->wmtype = get_wmtype(wm);
  return best_error;
}

#define FEAT_COUNT_TR 3
#define SEG_COUNT_TR 48
void av1_compute_feature_segmentation_map(uint8_t *segment_map, int width,
                                          int height, int *inliers,
                                          int num_inliers) {
  int seg_count = 0;
  memset(segment_map, 0, sizeof(*segment_map) * width * height);

  for (int i = 0; i < num_inliers; i++) {
    int x = inliers[i * 2];
    int y = inliers[i * 2 + 1];
    int seg_x = x >> WARP_ERROR_BLOCK_LOG;
    int seg_y = y >> WARP_ERROR_BLOCK_LOG;
    segment_map[seg_y * width + seg_x] += 1;
  }

  for (int i = 0; i < height; i++) {
    for (int j = 0; j < width; j++) {
      uint8_t feat_count = segment_map[i * width + j];
      segment_map[i * width + j] = (feat_count >= FEAT_COUNT_TR);
      seg_count += (segment_map[i * width + j]);
    }
  }

  // If this motion does not make up a large enough portion of the frame,
  // use the unsegmented version of the error metric
  if (seg_count < SEG_COUNT_TR)
    memset(segment_map, 1, width * height * sizeof(*segment_map));
}
