// Copyright (c) Facebook, Inc. and its affiliates.
// All rights reserved.
//
// Copyright 2019 Google LLC
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

#pragma once

#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <string>

#include <gtest/gtest.h>
#include "xnnpack.h"
#include "xnnpack/common.h"
#include "xnnpack/microfnptr.h"
#include "xnnpack/microkernel-utils.h"
#include "xnnpack/microparams-init.h"
#include "xnnpack/pack.h"
#include "xnnpack/requantization.h"
#include "xnnpack/buffer.h"

class DWConvMicrokernelTester {
 public:
  DWConvMicrokernelTester& width(uint32_t width) {
    assert(width >= 1);
    this->width_ = width;
    return *this;
  }

  uint32_t width() const { return this->width_; }

  DWConvMicrokernelTester& step(uint32_t step) {
    assert(step >= 1);
    this->step_ = step;
    return *this;
  }

  uint32_t step() const { return this->step_; }

  DWConvMicrokernelTester& channels(uint32_t channels) {
    assert(channels >= 1);
    this->channels_ = channels;
    return *this;
  }

  uint32_t channels() const { return this->channels_; }

  DWConvMicrokernelTester& channel_tile(uint32_t channel_tile) {
    assert(channel_tile != 0);
    this->channel_tile_ = channel_tile;
    return *this;
  }

  uint32_t channel_tile() const { return this->channel_tile_; }

  DWConvMicrokernelTester& channel_subtile(uint32_t channel_subtile) {
    assert(channel_subtile != 0);
    this->channel_subtile_ = channel_subtile;
    return *this;
  }

  uint32_t channel_subtile() const { return this->channel_subtile_; }

  DWConvMicrokernelTester& channel_round(uint32_t channel_round) {
    assert(channel_round != 0);
    this->channel_round_ = channel_round;
    return *this;
  }

  uint32_t channel_round() const { return this->channel_round_; }

  DWConvMicrokernelTester& kernel_tile(uint32_t kernel_tile) {
    assert(kernel_tile != 0);
    this->kernel_tile_ = kernel_tile;
    return *this;
  }

  uint32_t kernel_tile() const { return this->kernel_tile_; }

  DWConvMicrokernelTester& kernel_size(uint32_t kernel_size) {
    assert(kernel_size != 0);
    this->kernel_size_ = kernel_size;
    return *this;
  }

  uint32_t kernel_size() const { return this->kernel_size_; }

  uint32_t packed_channels() const {
    return (channels() / channel_tile() + !!(channels() % channel_tile())) *
           channel_tile();
  }

  DWConvMicrokernelTester& output_stride(uint32_t output_stride) {
    assert(output_stride != 0);
    this->output_stride_ = output_stride;
    return *this;
  }

  uint32_t output_stride() const {
    if (this->output_stride_ == 0) {
      return channels();
    } else {
      assert(this->output_stride_ >= channels());
      return this->output_stride_;
    }
  }

  DWConvMicrokernelTester& input_zero_point(uint8_t input_zero_point) {
    this->input_zero_point_ = input_zero_point;
    return *this;
  }

  uint8_t input_zero_point() const { return this->input_zero_point_; }

  DWConvMicrokernelTester& kernel_zero_point(uint8_t kernel_zero_point) {
    this->kernel_zero_point_ = kernel_zero_point;
    return *this;
  }

  uint8_t kernel_zero_point() const { return this->kernel_zero_point_; }

  DWConvMicrokernelTester& qmin(uint8_t qmin) {
    this->qmin_ = qmin;
    return *this;
  }

  uint8_t qmin() const { return this->qmin_; }

  DWConvMicrokernelTester& qmax(uint8_t qmax) {
    this->qmax_ = qmax;
    return *this;
  }

  uint8_t qmax() const { return this->qmax_; }

  DWConvMicrokernelTester& input_offset(size_t input_offset) {
    this->input_offset_ = input_offset;
    return *this;
  }

  size_t input_offset() const { return this->input_offset_; }

  DWConvMicrokernelTester& zero_index(size_t zero_index) {
    this->zero_index_ = zero_index;
    return *this;
  }

  size_t zero_index() const { return this->zero_index_; }

  DWConvMicrokernelTester& iterations(size_t iterations) {
    this->iterations_ = iterations;
    return *this;
  }

  size_t iterations() const { return this->iterations_; }

  DWConvMicrokernelTester& first_pass_tile(size_t first_pass_tile) {
    this->first_pass_tile_ = first_pass_tile;
    return *this;
  }

  size_t first_pass_tile() const { return this->first_pass_tile_; }

  DWConvMicrokernelTester& middle_pass_tile(size_t middle_pass_tile) {
    this->middle_pass_tile_ = middle_pass_tile;
    return *this;
  }

  size_t middle_pass_tile() const { return this->middle_pass_tile_; }

  DWConvMicrokernelTester& last_pass_tile(size_t last_pass_tile) {
    this->last_pass_tile_ = last_pass_tile;
    return *this;
  }

  size_t last_pass_tile() const { return this->last_pass_tile_; }

  void Test(xnn_qu8_dwconv_minmax_unipass_ukernel_fn dwconv_minmax,
            xnn_init_qu8_conv_minmax_params_fn init_params,
            xnn_qu8_requantize_fn requantize) const;

  void Test(xnn_qu8_dwconv_minmax_multipass_ukernel_fn dwconv_minmax,
            xnn_init_qu8_conv_minmax_params_fn init_params,
            xnn_qu8_requantize_fn requantize) const;

  void Test(xnn_qs8_qc8w_dwconv_minmax_unipass_ukernel_fn dwconv_minmax,
            xnn_init_qs8_qc8w_conv_minmax_params_fn init_params,
            xnn_qs8_requantize_fn requantize) const;

  void Test(xnn_qs8_qc8w_dwconv_minmax_multipass_ukernel_fn dwconv_minmax,
            xnn_init_qs8_qc8w_conv_minmax_params_fn init_params,
            xnn_qs8_requantize_fn requantize) const;

  void Test(xnn_qs8_dwconv_minmax_unipass_ukernel_fn dwconv_minmax,
            xnn_init_qs8_conv_minmax_params_fn init_params,
            xnn_qs8_requantize_fn requantize) const;

  void Test(xnn_qs8_dwconv_minmax_multipass_ukernel_fn dwconv_minmax,
            xnn_init_qs8_conv_minmax_params_fn init_params,
            xnn_qs8_requantize_fn requantize) const;

  void Test(xnn_f16_dwconv_minmax_unipass_ukernel_fn dwconv_minmax,
            xnn_init_f16_minmax_params_fn init_params) const;

  void Test(xnn_f16_dwconv_minmax_multipass_ukernel_fn dwconv_minmax,
            xnn_init_f16_minmax_params_fn init_params) const;

  void Test(xnn_f32_dwconv_unipass_ukernel_fn dwconv, const void* = nullptr) const;

  void Test(xnn_f32_dwconv_minmax_unipass_ukernel_fn dwconv_minmax,
            xnn_init_f32_minmax_params_fn init_params) const;

  void Test(xnn_f32_dwconv_multipass_ukernel_fn dwconv, const void* = nullptr) const;

  void Test(xnn_f32_dwconv_minmax_multipass_ukernel_fn dwconv_minmax,
            xnn_init_f32_minmax_params_fn init_params) const;

 private:
  uint32_t channels_{1};
  uint32_t channel_tile_{1};
  uint32_t channel_subtile_{1};
  uint32_t channel_round_{1};
  uint32_t kernel_tile_{1};
  uint32_t kernel_size_{1};
  uint32_t width_{1};
  uint32_t step_{1};
  uint32_t output_stride_{0};
  uint8_t input_zero_point_{127};
  uint8_t kernel_zero_point_{127};
  uint8_t qmin_{0};
  uint8_t qmax_{255};
  size_t input_offset_{0};
  size_t zero_index_{SIZE_MAX};
  size_t iterations_{3};
  size_t first_pass_tile_{0};
  size_t middle_pass_tile_{0};
  size_t last_pass_tile_{0};
};

struct LoopParams {
  LoopParams() = default;
  explicit LoopParams(size_t from, size_t to, size_t step)
      : is_set(true), from(from), to(to), step(step) {}
  bool is_set = false;
  size_t from = 1;
  size_t to = 1;
  size_t step = 1;
};

struct DWConvTestParams {
  DWConvTestParams(std::string test_name, DWConvMicrokernelTester tester,
                 std::function<void(DWConvMicrokernelTester& tester)> test_func,
                 std::function<void(void)> isa_check = nullptr)
      : test_name(test_name),
        tester(tester),
        test_func(test_func),
        isa_check(isa_check) {}

  // Setters for the loops over `k`, `m`, and `n`.
  DWConvTestParams& loop_kernel_size(size_t from, size_t to, size_t step = 1) {
    loop_kernel_size_ = LoopParams(from, to, step);
    return *this;
  }
  DWConvTestParams& loop_channels(size_t from, size_t to, size_t step = 1) {
    loop_channels_ = LoopParams(from, to, step);
    return *this;
  }
  DWConvTestParams& loop_step(size_t from, size_t to, size_t step = 1) {
    loop_step_ = LoopParams(from, to, step);
    return *this;
  }
  DWConvTestParams& loop_zi(size_t from, size_t to, size_t step = 1) {
    loop_zi_ = LoopParams(from, to, step);
    return *this;
  }

  std::string test_name;
  DWConvMicrokernelTester tester;
  std::function<void(DWConvMicrokernelTester& tester)> test_func;
  std::function<void(void)> isa_check;
  LoopParams loop_kernel_size_;
  LoopParams loop_channels_;
  LoopParams loop_step_;
  LoopParams loop_zi_;
};

using DWConvTest = testing::TestWithParam<DWConvTestParams>;
