// Copyright 2020 The libgav1 Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "src/dsp/motion_vector_search.h"

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>

#include "src/dsp/dsp.h"
#include "src/utils/common.h"
#include "src/utils/constants.h"
#include "src/utils/types.h"

namespace libgav1 {
namespace dsp {
namespace {

// Silence unused function warnings when the C functions are not used.
#if LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS ||             \
    !defined(LIBGAV1_Dsp8bpp_MotionVectorSearch) || \
    (LIBGAV1_MAX_BITDEPTH >= 10 &&                  \
     !defined(LIBGAV1_Dsp10bpp_MotionVectorSearch))

void MvProjectionCompoundLowPrecision_C(
    const MotionVector* const temporal_mvs,
    const int8_t* const temporal_reference_offsets,
    const int reference_offsets[2], const int count,
    CompoundMotionVector* const candidate_mvs) {
  // To facilitate the compilers, make a local copy of |reference_offsets|.
  const int offsets[2] = {reference_offsets[0], reference_offsets[1]};
  int index = 0;
  do {
    candidate_mvs[index].mv64 = 0;
    for (int i = 0; i < 2; ++i) {
      // |offsets| non-zero check usually equals true and could be ignored.
      if (offsets[i] != 0) {
        GetMvProjection(
            temporal_mvs[index], offsets[i],
            kProjectionMvDivisionLookup[temporal_reference_offsets[index]],
            &candidate_mvs[index].mv[i]);
        for (auto& mv : candidate_mvs[index].mv[i].mv) {
          // The next line is equivalent to:
          // if ((mv & 1) != 0) mv += (mv > 0) ? -1 : 1;
          mv = (mv - (mv >> 15)) & ~1;
        }
      }
    }
  } while (++index < count);
}

void MvProjectionCompoundForceInteger_C(
    const MotionVector* const temporal_mvs,
    const int8_t* const temporal_reference_offsets,
    const int reference_offsets[2], const int count,
    CompoundMotionVector* const candidate_mvs) {
  // To facilitate the compilers, make a local copy of |reference_offsets|.
  const int offsets[2] = {reference_offsets[0], reference_offsets[1]};
  int index = 0;
  do {
    candidate_mvs[index].mv64 = 0;
    for (int i = 0; i < 2; ++i) {
      // |offsets| non-zero check usually equals true and could be ignored.
      if (offsets[i] != 0) {
        GetMvProjection(
            temporal_mvs[index], offsets[i],
            kProjectionMvDivisionLookup[temporal_reference_offsets[index]],
            &candidate_mvs[index].mv[i]);
        for (auto& mv : candidate_mvs[index].mv[i].mv) {
          // The next line is equivalent to:
          // const int value = (std::abs(static_cast<int>(mv)) + 3) & ~7;
          // const int sign = mv >> 15;
          // mv = ApplySign(value, sign);
          mv = (mv + 3 - (mv >> 15)) & ~7;
        }
      }
    }
  } while (++index < count);
}

void MvProjectionCompoundHighPrecision_C(
    const MotionVector* const temporal_mvs,
    const int8_t* const temporal_reference_offsets,
    const int reference_offsets[2], const int count,
    CompoundMotionVector* const candidate_mvs) {
  // To facilitate the compilers, make a local copy of |reference_offsets|.
  const int offsets[2] = {reference_offsets[0], reference_offsets[1]};
  int index = 0;
  do {
    candidate_mvs[index].mv64 = 0;
    for (int i = 0; i < 2; ++i) {
      // |offsets| non-zero check usually equals true and could be ignored.
      if (offsets[i] != 0) {
        GetMvProjection(
            temporal_mvs[index], offsets[i],
            kProjectionMvDivisionLookup[temporal_reference_offsets[index]],
            &candidate_mvs[index].mv[i]);
      }
    }
  } while (++index < count);
}

void MvProjectionSingleLowPrecision_C(
    const MotionVector* const temporal_mvs,
    const int8_t* const temporal_reference_offsets, const int reference_offset,
    const int count, MotionVector* const candidate_mvs) {
  int index = 0;
  do {
    GetMvProjection(
        temporal_mvs[index], reference_offset,
        kProjectionMvDivisionLookup[temporal_reference_offsets[index]],
        &candidate_mvs[index]);
    for (auto& mv : candidate_mvs[index].mv) {
      // The next line is equivalent to:
      // if ((mv & 1) != 0) mv += (mv > 0) ? -1 : 1;
      mv = (mv - (mv >> 15)) & ~1;
    }
  } while (++index < count);
}

void MvProjectionSingleForceInteger_C(
    const MotionVector* const temporal_mvs,
    const int8_t* const temporal_reference_offsets, const int reference_offset,
    const int count, MotionVector* const candidate_mvs) {
  int index = 0;
  do {
    GetMvProjection(
        temporal_mvs[index], reference_offset,
        kProjectionMvDivisionLookup[temporal_reference_offsets[index]],
        &candidate_mvs[index]);
    for (auto& mv : candidate_mvs[index].mv) {
      // The next line is equivalent to:
      // const int value = (std::abs(static_cast<int>(mv)) + 3) & ~7;
      // const int sign = mv >> 15;
      // mv = ApplySign(value, sign);
      mv = (mv + 3 - (mv >> 15)) & ~7;
    }
  } while (++index < count);
}

void MvProjectionSingleHighPrecision_C(
    const MotionVector* const temporal_mvs,
    const int8_t* const temporal_reference_offsets, const int reference_offset,
    const int count, MotionVector* const candidate_mvs) {
  int index = 0;
  do {
    GetMvProjection(
        temporal_mvs[index], reference_offset,
        kProjectionMvDivisionLookup[temporal_reference_offsets[index]],
        &candidate_mvs[index]);
  } while (++index < count);
}

#endif  // LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS ||
        // !defined(LIBGAV1_Dsp8bpp_MotionVectorSearch) ||
        // (LIBGAV1_MAX_BITDEPTH >= 10 &&
        //  !defined(LIBGAV1_Dsp10bpp_MotionVectorSearch))

void Init8bpp() {
#if LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS || \
    !defined(LIBGAV1_Dsp8bpp_MotionVectorSearch)
  Dsp* const dsp = dsp_internal::GetWritableDspTable(8);
  assert(dsp != nullptr);
  dsp->mv_projection_compound[0] = MvProjectionCompoundLowPrecision_C;
  dsp->mv_projection_compound[1] = MvProjectionCompoundForceInteger_C;
  dsp->mv_projection_compound[2] = MvProjectionCompoundHighPrecision_C;
  dsp->mv_projection_single[0] = MvProjectionSingleLowPrecision_C;
  dsp->mv_projection_single[1] = MvProjectionSingleForceInteger_C;
  dsp->mv_projection_single[2] = MvProjectionSingleHighPrecision_C;
#endif
}

#if LIBGAV1_MAX_BITDEPTH >= 10
void Init10bpp() {
#if LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS || \
    !defined(LIBGAV1_Dsp10bpp_MotionVectorSearch)
  Dsp* const dsp = dsp_internal::GetWritableDspTable(10);
  assert(dsp != nullptr);
  dsp->mv_projection_compound[0] = MvProjectionCompoundLowPrecision_C;
  dsp->mv_projection_compound[1] = MvProjectionCompoundForceInteger_C;
  dsp->mv_projection_compound[2] = MvProjectionCompoundHighPrecision_C;
  dsp->mv_projection_single[0] = MvProjectionSingleLowPrecision_C;
  dsp->mv_projection_single[1] = MvProjectionSingleForceInteger_C;
  dsp->mv_projection_single[2] = MvProjectionSingleHighPrecision_C;
#endif
}
#endif

}  // namespace

void MotionVectorSearchInit_C() {
  Init8bpp();
#if LIBGAV1_MAX_BITDEPTH >= 10
  Init10bpp();
#endif
}

}  // namespace dsp
}  // namespace libgav1
