/*
 * 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 "test_buffer.hpp"

#include <utils/cudart.hpp>

#include <catch2/catch.hpp>

#include <raft/random/rng_state.hpp>

#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>

namespace cugraph::ops {

// forward declare generating permutation index here
template <typename DataT>
void generate_perm_index(DataT* perm,
                         device_buffer<DataT>& workspace,
                         raft::random::RngState& r,
                         DataT n,
                         DataT offset        = DataT{0},
                         cudaStream_t stream = nullptr);

template <typename DataT>
double average_error(DataT* d_array1, DataT* d_array2, size_t len)
{
  static constexpr double EPS = 1e-20;
  host_buffer<DataT> h_int1{len}, h_int2{len};
  utils::copy<DataT>(h_int1, d_array1, len, cudaMemcpyDeviceToHost);
  utils::copy<DataT>(h_int2, d_array2, len, cudaMemcpyDeviceToHost);
  double avg_err = 0.;
  size_t n       = len;
  for (size_t i = 0; i < len; ++i) {
    double a = double{h_int1[i]};
    double b = double{h_int2[i]};
    if (a == 0. && b == 0.) {
      n--;
      continue;
    }
    double diff = std::abs(a - b);
    double m    = std::max(std::abs(a), std::abs(b)) + EPS;
    avg_err += diff / m;
  }
  return avg_err / static_cast<double>(n);
}

/**
 * @brief Prints the contents of the host array for debugging purposes
 *
 * @tparam DataT data type of the array
 *
 * @param[in] arr host vector
 * @param[in] msg debug message to help identify this array
 *
 * @note it can cause memory and runtime issues if the input array to be printed
 *       is a really large one!
 */
template <typename DataT>
void print_array(const std::vector<DataT>& arr, const std::string& msg)
{
  std::cout << msg << " array-length=" << arr.size() << " array: " << std::endl;
  for (size_t i = 0; i < arr.size(); ++i) {
    std::cout << i << ": " << arr[i] << std::endl;
  }
}

/**
 * @brief Prints the contents of the array for debugging purposes
 *
 * @tparam DataT data type of the array
 *
 * @param[in] ptr device pointer [on device] [len = `len`]
 * @param[in] len length of the array
 * @param[in] msg debug message to help identify this array
 *
 * @note it can cause memory and runtime issues if the input array to be printed
 *       is a really large one!
 */
template <typename DataT>
void print_array(const DataT* ptr, size_t len, const std::string& msg)
{
  std::vector<DataT> hptr(len);
  utils::copy(hptr.data(), ptr, len, cudaMemcpyDeviceToHost);
  print_array(hptr, msg);
}

/**
 * @brief Prints the contents of the array using UNSCOPED_INFO
 *
 * @tparam DataT data type of the array
 *
 * @param[in] vec std vector
 * @param[in] msg debug message to help identify this array
 *
 * @note it can cause memory and runtime issues if the input array to be printed
 *       is a really large one!
 */
template <typename DataT>
void print_array_unscoped(const std::vector<DataT>& vec, const std::string& msg)
{
  UNSCOPED_INFO(msg << " array-length=" << vec.size() << " array: ");
  for (size_t i = 0; i < vec.size(); ++i) {
    UNSCOPED_INFO(i << ": " << vec[i]);
  }
}

/**
 * @brief Prints the contents of the array using UNSCOPED_INFO
 *
 * @tparam DataT data type of the array
 *
 * @param[in] ptr device pointer [on device] [len = `len`]
 * @param[in] len length of the array
 * @param[in] msg debug message to help identify this array
 *
 * @note it can cause memory and runtime issues if the input array to be printed
 *       is a really large one!
 */
template <typename DataT>
void print_array_unscoped(const DataT* ptr, size_t len, const std::string& msg)
{
  std::vector<DataT> hptr(len);
  utils::copy(hptr.data(), ptr, len, cudaMemcpyDeviceToHost);
  print_array_unscoped(hptr, msg);
}

template <typename IdxT>
void insert_invalid_id(IdxT* in, IdxT len);

}  // namespace cugraph::ops
