// 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/dsp.h"

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

#include "absl/strings/str_cat.h"
#include "gtest/gtest.h"
#include "src/dsp/constants.h"
#include "src/utils/constants.h"
#include "src/utils/cpu.h"

#if LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS
#include "tests/utils.h"
#endif

namespace libgav1 {
namespace dsp {
namespace {

// Maps 1D transform to the maximum valid size for the corresponding transform.
constexpr int kMaxTransform1dSize[kNumTransform1ds] = {
    kTransform1dSize64,  // Dct.
    kTransform1dSize16,  // Adst.
    kTransform1dSize32,  // Identity.
    kTransform1dSize4,   // Wht.
};

void CheckTables(bool c_only) {
#if LIBGAV1_MAX_BITDEPTH >= 10
  static constexpr int kBitdepths[] = {kBitdepth8, kBitdepth10};
#else
  static constexpr int kBitdepths[] = {kBitdepth8};
#endif

  for (const auto& bitdepth : kBitdepths) {
    const Dsp* const dsp = GetDspTable(bitdepth);
    ASSERT_NE(dsp, nullptr);
    SCOPED_TRACE(absl::StrCat("bitdepth: ", bitdepth));
    for (int i = 0; i < kNumTransformSizes; ++i) {
      for (int j = 0; j < kNumIntraPredictors; ++j) {
        EXPECT_NE(dsp->intra_predictors[i][j], nullptr)
            << "index [" << i << "][" << j << "]";
      }
    }
    EXPECT_NE(dsp->directional_intra_predictor_zone1, nullptr);
    EXPECT_NE(dsp->directional_intra_predictor_zone2, nullptr);
    EXPECT_NE(dsp->directional_intra_predictor_zone3, nullptr);
    EXPECT_NE(dsp->filter_intra_predictor, nullptr);
    for (int i = 0; i < kNumTransformSizes; ++i) {
      if (std::max(kTransformWidth[i], kTransformHeight[i]) == 64) {
        EXPECT_EQ(dsp->cfl_intra_predictors[i], nullptr)
            << "index [" << i << "]";
        for (int j = 0; j < kNumSubsamplingTypes; ++j) {
          EXPECT_EQ(dsp->cfl_subsamplers[i][j], nullptr)
              << "index [" << i << "][" << j << "]";
        }
      } else {
        EXPECT_NE(dsp->cfl_intra_predictors[i], nullptr)
            << "index [" << i << "]";
        for (int j = 0; j < kNumSubsamplingTypes; ++j) {
          EXPECT_NE(dsp->cfl_subsamplers[i][j], nullptr)
              << "index [" << i << "][" << j << "]";
        }
      }
    }
    EXPECT_NE(dsp->intra_edge_filter, nullptr);
    EXPECT_NE(dsp->intra_edge_upsampler, nullptr);
    for (int i = 0; i < kNumTransform1ds; ++i) {
      for (int j = 0; j < kNumTransform1dSizes; ++j) {
        for (int k = 0; k < 2; ++k) {
          if (j <= kMaxTransform1dSize[i]) {
            EXPECT_NE(dsp->inverse_transforms[i][j][k], nullptr)
                << "index [" << i << "][" << j << "][" << k << "]";
          } else {
            EXPECT_EQ(dsp->inverse_transforms[i][j][k], nullptr)
                << "index [" << i << "][" << j << "][" << k << "]";
          }
        }
      }
    }
    for (int i = 0; i < kNumLoopFilterSizes; ++i) {
      for (int j = 0; j < kNumLoopFilterTypes; ++j) {
        EXPECT_NE(dsp->loop_filters[i][j], nullptr)
            << "index [" << i << "][" << j << "]";
      }
    }
    for (int i = 0; i < 2; ++i) {
      EXPECT_NE(dsp->loop_restorations[i], nullptr) << "index [" << i << "]";
    }

    bool super_res_coefficients_is_nonnull = LIBGAV1_ENABLE_NEON;
#if LIBGAV1_ENABLE_SSE4_1
    const uint32_t cpu_features = GetCpuInfo();
    super_res_coefficients_is_nonnull = (cpu_features & kSSE4_1) != 0;
#endif
    if (c_only) super_res_coefficients_is_nonnull = false;
    if (super_res_coefficients_is_nonnull) {
      EXPECT_NE(dsp->super_res_coefficients, nullptr);
    } else {
      EXPECT_EQ(dsp->super_res_coefficients, nullptr);
    }

    EXPECT_NE(dsp->super_res, nullptr);
    EXPECT_NE(dsp->cdef_direction, nullptr);
    for (int i = 0; i < 2; ++i) {
      for (int j = 0; j < 3; ++j) {
        EXPECT_NE(dsp->cdef_filters[i][j], nullptr)
            << "index [" << i << "][" << j << "]";
      }
    }
    for (auto convolve_func : dsp->convolve_scale) {
      EXPECT_NE(convolve_func, nullptr);
    }
    for (int j = 0; j < 2; ++j) {
      for (int k = 0; k < 2; ++k) {
        for (int l = 0; l < 2; ++l) {
          for (int m = 0; m < 2; ++m) {
            if (j == 1 && k == 1) {
              EXPECT_EQ(dsp->convolve[j][k][l][m], nullptr);
            } else {
              EXPECT_NE(dsp->convolve[j][k][l][m], nullptr);
            }
          }
        }
      }
    }
    for (const auto& m : dsp->mask_blend) {
      for (int i = 0; i < 2; ++i) {
        if (i == 0 || bitdepth >= 10) {
          EXPECT_NE(m[i], nullptr);
        } else {
          EXPECT_EQ(m[i], nullptr);
        }
      }
    }
    for (const auto& m : dsp->inter_intra_mask_blend_8bpp) {
      if (bitdepth == 8) {
        EXPECT_NE(m, nullptr);
      } else {
        EXPECT_EQ(m, nullptr);
      }
    }
    for (int i = kBlock4x4; i < kMaxBlockSizes; ++i) {
      const int width_index = k4x4WidthLog2[i] - 1;
      const int height_index = k4x4HeightLog2[i] - 1;
      // Only block sizes >= 8x8 are handled with this function.
      if (width_index < 0 || height_index < 0) continue;

      for (size_t j = 0; j < 2; ++j) {
        EXPECT_NE(dsp->weight_mask[width_index][height_index][j], nullptr)
            << ToString(static_cast<BlockSize>(i)) << " index [" << width_index
            << "]"
            << "[" << height_index << "][" << j << "]";
      }
    }

    EXPECT_NE(dsp->average_blend, nullptr);
    EXPECT_NE(dsp->distance_weighted_blend, nullptr);
    for (int i = 0; i < kNumObmcDirections; ++i) {
      EXPECT_NE(dsp->obmc_blend[i], nullptr)
          << "index [" << ToString(static_cast<ObmcDirection>(i)) << "]";
    }
    EXPECT_NE(dsp->warp, nullptr);
    EXPECT_NE(dsp->warp_compound, nullptr);

    for (int i = 0; i < kNumAutoRegressionLags - 1; ++i) {
      EXPECT_NE(dsp->film_grain.luma_auto_regression[i], nullptr)
          << "index [" << i << "]";
    }
    for (int i = 0; i < 2; ++i) {
      for (int j = 0; j < kNumAutoRegressionLags; ++j) {
        if (i == 0 && j == 0) {
          EXPECT_EQ(dsp->film_grain.chroma_auto_regression[i][j], nullptr)
              << " index [" << i << "]"
              << "[" << j << "]";
        } else {
          EXPECT_NE(dsp->film_grain.chroma_auto_regression[i][j], nullptr)
              << " index [" << i << "]"
              << "[" << j << "]";
        }
      }
      EXPECT_NE(dsp->film_grain.construct_noise_stripes[i], nullptr)
          << "index [" << i << "]";
      EXPECT_NE(dsp->film_grain.blend_noise_chroma[i], nullptr)
          << "index [" << i << "]";
    }
    EXPECT_NE(dsp->film_grain.construct_noise_image_overlap, nullptr);
    EXPECT_NE(dsp->film_grain.initialize_scaling_lut, nullptr);
    EXPECT_NE(dsp->film_grain.blend_noise_luma, nullptr);

    if (bitdepth == 8) {
      EXPECT_NE(dsp->motion_field_projection_kernel, nullptr);
      EXPECT_NE(dsp->mv_projection_compound[0], nullptr);
      EXPECT_NE(dsp->mv_projection_compound[1], nullptr);
      EXPECT_NE(dsp->mv_projection_compound[2], nullptr);
      EXPECT_NE(dsp->mv_projection_single[0], nullptr);
      EXPECT_NE(dsp->mv_projection_single[1], nullptr);
      EXPECT_NE(dsp->mv_projection_single[2], nullptr);
    } else {
      EXPECT_EQ(dsp->motion_field_projection_kernel, nullptr);
      EXPECT_EQ(dsp->mv_projection_compound[0], nullptr);
      EXPECT_EQ(dsp->mv_projection_compound[1], nullptr);
      EXPECT_EQ(dsp->mv_projection_compound[2], nullptr);
      EXPECT_EQ(dsp->mv_projection_single[0], nullptr);
      EXPECT_EQ(dsp->mv_projection_single[1], nullptr);
      EXPECT_EQ(dsp->mv_projection_single[2], nullptr);
    }
  }
}

TEST(Dsp, TablesArePopulated) {
  DspInit();
  CheckTables(/*c_only=*/false);
}

#if LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS
TEST(Dsp, TablesArePopulatedCOnly) {
  test_utils::ResetDspTable(kBitdepth8);
#if LIBGAV1_MAX_BITDEPTH >= 10
  test_utils::ResetDspTable(kBitdepth10);
#endif
  dsp_internal::DspInit_C();
  CheckTables(/*c_only=*/true);
}
#endif  // LIBGAV1_ENABLE_ALL_DSP_FUNCTIONS

TEST(Dsp, GetDspTable) {
  EXPECT_EQ(GetDspTable(1), nullptr);
  EXPECT_NE(GetDspTable(8), nullptr);
  EXPECT_EQ(dsp_internal::GetWritableDspTable(1), nullptr);
  EXPECT_NE(dsp_internal::GetWritableDspTable(8), nullptr);
#if LIBGAV1_MAX_BITDEPTH >= 10
  EXPECT_NE(GetDspTable(10), nullptr);
  EXPECT_NE(dsp_internal::GetWritableDspTable(10), nullptr);
#else
  EXPECT_EQ(GetDspTable(10), nullptr);
  EXPECT_EQ(dsp_internal::GetWritableDspTable(10), nullptr);
#endif
}

}  // namespace
}  // namespace dsp
}  // namespace libgav1
