/*
 * Copyright (c) 2020-2023, NVIDIA CORPORATION. All rights reserved.
 *
 * This source code and/or documentation ("Licensed Deliverables") are
 * subject to NVIDIA intellectual property rights under U.S. and
 * international Copyright laws.
 */

#pragma once

#include "approx.hpp"

#include <utils/cudart.hpp>

#include <catch2/catch.hpp>

#include <cmath>
#include <string>
#include <vector>

namespace cugraph::ops::catch2 {

/**
 * @brief Custom matcher class for comparing device arrays
 *
 * @tparam DataT data type of the arrays
 */
template <typename DataT>
class host_device_approx_equals_t : public Catch::MatcherBase<DataT*> {
  compare_approx<DataT> matcher_;
  std::vector<DataT> h_ref_;

 public:
  host_device_approx_equals_t(DataT* h_ref, size_t len, DataT tol) : matcher_(tol)
  {
    h_ref_ = std::vector<DataT>(h_ref, h_ref + len);
  }

  bool match(DataT* const& d_computed) const override
  {
    std::vector<DataT> h_computed(h_ref_.size());
    utils::copy(h_computed.data(), d_computed, h_ref_.size(), cudaMemcpyDeviceToHost);
    for (size_t i = 0; i < h_ref_.size(); ++i) {
      if (!matcher_(h_computed[i], h_ref_[i])) {
        UNSCOPED_INFO("@i=" << i << " val=" << h_computed[i] << " ref=" << h_ref_[i]
                            << " tolerance=" << matcher_.tolerance());
        return false;
      }
    }
    return true;
  }

  [[nodiscard]] std::string describe() const override { return "device array approx matcher"; }
};  // class host_device_approx_equals_t

/**
 * @brief Builder function for the custom device array matcher against reference on host
 *
 * @tparam DataT data type of the arrays
 *
 * @param[in] h_ref reference host array
 * @param[in] len   length of the array
 * @param[in] tol   tolerance for approximate comparison
 *
 * @return the matcher object
 */
template <typename DataT>
host_device_approx_equals_t<DataT> host_device_approx_equals(DataT* h_ref, size_t len, DataT tol)
{
  return host_device_approx_equals_t<DataT>(h_ref, len, tol);
}

}  // namespace cugraph::ops::catch2
