/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Distributed under BSD 3-Clause license.                                   *
 * Copyright by The HDF Group.                                               *
 * Copyright by the Illinois Institute of Technology.                        *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of Hermes. The full Hermes copyright notice, including  *
 * terms governing use, modification, and redistribution, is contained in    *
 * the COPYING file, which can be found at the top directory. If you do not  *
 * have access to the file, you may request a copy from help@hdfgroup.org.   *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <utility>

#include "basic_test.h"
#include "hermes_shm/data_structures/ipc/tuple_base.h"

void test_argpack0_pass() { std::cout << "HERE0" << std::endl; }

void test_argpack0() {
  hshm::PassArgPack::Call(hshm::ArgPack<>(), test_argpack0_pass);
}

template <typename T1, typename T2, typename T3>
void test_argpack3_pass(T1 x, T2 y, T3 z) {
  REQUIRE(x == 0);
  REQUIRE(y == 1);
  REQUIRE(z == 0);
  std::cout << "HERE3" << std::endl;
}

void test_product1(int b, int c) {
  REQUIRE(b == 1);
  REQUIRE(c == 2);
}

void test_product2(double d, double e) {
  REQUIRE(d == 3);
  REQUIRE(e == 4);
}

template <typename Pack1, typename Pack2>
void test_product(int a, Pack1 &&pack1, int a2, Pack2 &&pack2) {
  REQUIRE(a == 0);
  REQUIRE(a2 == 0);
  hshm::PassArgPack::Call(std::forward<Pack1>(pack1), test_product1);
  hshm::PassArgPack::Call(std::forward<Pack2>(pack2), test_product2);
}

template <typename T1, typename T2, typename T3>
void verify_tuple3(hshm::tuple<T1, T2, T3> &x) {
  REQUIRE(x.Size() == 3);
  REQUIRE(x.template Get<0>() == 0);
  REQUIRE(x.template Get<1>() == 1);
  REQUIRE(x.template Get<2>() == 0);
#ifdef TEST_COMPILER_ERROR
  std::cout << x.Get<3>() << std::endl;
#endif
}

template <typename T1, typename T2, typename T3>
void test_argpack3() {
  // Pass an argpack to a function
  PAGE_DIVIDE("") {
    hshm::PassArgPack::Call(hshm::make_argpack(T1(0), T2(1), T3(0)),
                            test_argpack3_pass<T1, T2, T3>);
  }

  // Pass an argpack containing references to a function
  PAGE_DIVIDE("") {
    T2 y = 1;
    hshm::PassArgPack::Call(hshm::make_argpack(T1(0), T2(y), T3(0)),
                            test_argpack3_pass<T1, T2, T3>);
  }

  // Create a 3-tuple
  PAGE_DIVIDE("") {
    hshm::tuple<T1, T2, T3> x(T1(0), T2(1), T3(0));
    verify_tuple3(x);
  }

  // Copy a tuple
  PAGE_DIVIDE("") {
    hshm::tuple<T1, T2, T3> y(T1(0), T2(1), T3(0));
    hshm::tuple<T1, T2, T3> x(y);
    verify_tuple3(x);
  }

  // Copy assign tuple
  PAGE_DIVIDE("") {
    hshm::tuple<T1, T2, T3> y(T1(0), T2(1), T3(0));
    hshm::tuple<T1, T2, T3> x;
    x = y;
    verify_tuple3(x);
  }

  // Move tuple
  PAGE_DIVIDE("") {
    hshm::tuple<T1, T2, T3> y(T1(0), T2(1), T3(0));
    hshm::tuple<T1, T2, T3> x(std::move(y));
    verify_tuple3(x);
  }

  // Move assign tuple
  PAGE_DIVIDE("") {
    hshm::tuple<T1, T2, T3> y(T1(0), T2(1), T3(0));
    hshm::tuple<T1, T2, T3> x;
    x = std::move(y);
    verify_tuple3(x);
  }

  // Iterate over a tuple
  PAGE_DIVIDE("") {
    hshm::tuple<T1, T2, T3> x(T1(0), T2(1), T3(0));
    hshm::ForwardIterateTuple::Apply(x, [](auto i, auto &arg) constexpr {
      std::cout << "lambda: " << i.Get() << std::endl;
    });
  }

  // Merge two argpacks into a single pack
  PAGE_DIVIDE("") {
    size_t y = hshm::MergeArgPacks::Merge(hshm::make_argpack(T1(0)),
                                          hshm::make_argpack(T2(1), T2(0)))
                   .Size();
    REQUIRE(y == 3);
  }

  // Pass a merged argpack to a function
  PAGE_DIVIDE("") {
    hshm::PassArgPack::Call(
        hshm::MergeArgPacks::Merge(hshm::make_argpack(T1(0)),
                                   hshm::make_argpack(T2(1), T3(0))),
        test_argpack3_pass<T1, T2, T3>);
  }

  // Construct tuple from argpack
  PAGE_DIVIDE("") {
    hshm::tuple<int, int, int> x(hshm::make_argpack(int(10), int(11), int(12)));
    REQUIRE(x.Get<0>() == 10);
    REQUIRE(x.Get<1>() == 11);
    REQUIRE(x.Get<2>() == 12);
  }

  // Product an argpack
  PAGE_DIVIDE("") {
    auto &&pack = hshm::ProductArgPacks::Product(
        0, hshm::make_argpack(1, 2), hshm::make_argpack<double, double>(3, 4));
    REQUIRE(pack.Size() == 4);
  }

  // Product an argpack
  PAGE_DIVIDE("") {
    hshm::PassArgPack::Call(
        hshm::ProductArgPacks::Product(0, hshm::make_argpack(1, 2),
                                       hshm::make_argpack(3.0, 4.0)),
        test_product<hshm::ArgPack<int &&, int &&>,
                     hshm::ArgPack<double &&, double &&>>);
  }
}

TEST_CASE("TestArgpack") {
  test_argpack0();
  test_argpack3<int, double, float>();
}

class DetectCopy {
 public:
  bool is_copied_ = false;
  bool is_moved_ = false;

 public:
  DetectCopy() = default;
  DetectCopy(const DetectCopy &) {
    is_copied_ = true;
    std::cout << "Copy" << std::endl;
  }
  DetectCopy(DetectCopy &&) {
    is_moved_ = true;
    std::cout << "Move" << std::endl;
  }
};

template <typename ArgPackT>
void test_argpack_copy(ArgPackT &&pack) {
  REQUIRE(pack.template Forward<0>().is_copied_ == false);
  REQUIRE(pack.template Forward<0>().is_moved_ == false);
  REQUIRE(pack.template Forward<1>().is_copied_ == false);
  REQUIRE(pack.template Forward<1>().is_moved_ == false);
}

TEST_CASE("TestArgpackCopy") {
  DetectCopy x;
  test_argpack_copy(hshm::make_argpack(x, DetectCopy()));
}