// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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 "arrow/util/variant.h"

#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "arrow/testing/gtest_compat.h"

namespace arrow {

namespace util {
namespace {

using ::testing::Eq;

template <typename H, typename... T>
void AssertDefaultConstruction() {
  using variant_type = Variant<H, T...>;

  static_assert(std::is_nothrow_default_constructible<variant_type>::value, "");

  variant_type v;
  EXPECT_EQ(v.index(), 0);
  EXPECT_EQ(get<H>(v), H{});
}

TEST(Variant, DefaultConstruction) {
  AssertDefaultConstruction<int>();
  AssertDefaultConstruction<int, std::string>();
  AssertDefaultConstruction<std::string, int>();
  AssertDefaultConstruction<std::unique_ptr<int>>();
  AssertDefaultConstruction<std::vector<int>, int>();
  AssertDefaultConstruction<bool, std::string, std::unique_ptr<int>, void*,
                            std::true_type>();
  AssertDefaultConstruction<std::nullptr_t, std::unique_ptr<int>, void*, bool,
                            std::string, std::true_type>();
}

template <typename V, typename T>
struct AssertCopyConstructionOne {
  void operator()(uint8_t index) {
    V v{member_};
    EXPECT_EQ(v.index(), index);
    EXPECT_EQ(get<T>(v), member_);

    V copy{v};
    EXPECT_EQ(copy.index(), v.index());
    EXPECT_EQ(get<T>(copy), get<T>(v));
    EXPECT_EQ(copy, v);

    V assigned;
    assigned = member_;
    EXPECT_EQ(assigned.index(), index);
    EXPECT_EQ(get<T>(assigned), member_);

    assigned = v;
    EXPECT_EQ(assigned.index(), v.index());
    EXPECT_EQ(get<T>(assigned), get<T>(v));
    EXPECT_EQ(assigned, v);
  }

  const T& member_;
};

template <typename... T>
void AssertCopyConstruction(T... member) {
  uint8_t index = 0;
  for (auto Assert : {std::function<void(uint8_t)>(
           AssertCopyConstructionOne<Variant<T...>, T>{member})...}) {
    Assert(index++);
  }
}

template <typename... T>
void AssertCopyConstructionDisabled() {
  static_assert(!std::is_copy_constructible<Variant<T...>>::value,
                "copy construction was not disabled");
}

TEST(Variant, CopyConstruction) {
  // if any member is not copy constructible then Variant is not copy constructible
  AssertCopyConstructionDisabled<std::unique_ptr<int>>();
  AssertCopyConstructionDisabled<std::unique_ptr<int>, std::string>();
  AssertCopyConstructionDisabled<std::string, int, bool, std::unique_ptr<int>>();

  AssertCopyConstruction(32, std::string("hello"), true);
  AssertCopyConstruction(std::string("world"), false, 53);
  AssertCopyConstruction(nullptr, std::true_type{}, std::string("!"));
  AssertCopyConstruction(std::vector<int>{1, 3, 3, 7}, "C string");

  // copy assignment operator is not used
  struct CopyAssignThrows {
    CopyAssignThrows() = default;
    CopyAssignThrows(const CopyAssignThrows&) = default;

    CopyAssignThrows& operator=(const CopyAssignThrows&) { throw 42; }

    CopyAssignThrows(CopyAssignThrows&&) = default;
    CopyAssignThrows& operator=(CopyAssignThrows&&) = default;

    bool operator==(const CopyAssignThrows&) const { return true; }
  };
  EXPECT_NO_THROW(AssertCopyConstruction(CopyAssignThrows{}));
}

TEST(Variant, Emplace) {
  using variant_type = Variant<std::string, std::vector<int>, int>;
  variant_type v;

  v.emplace<int>();
  EXPECT_EQ(v, variant_type{int{}});

  v.emplace<std::string>("hello");
  EXPECT_EQ(v, variant_type{std::string("hello")});

  v.emplace<std::vector<int>>({1, 3, 3, 7});
  EXPECT_EQ(v, variant_type{std::vector<int>({1, 3, 3, 7})});
}

TEST(Variant, MoveConstruction) {
  struct noop_delete {
    void operator()(...) const {}
  };
  using ptr = std::unique_ptr<int, noop_delete>;
  static_assert(!std::is_copy_constructible<ptr>::value, "");

  using variant_type = Variant<int, ptr>;

  int tag = 42;
  auto ExpectIsTag = [&](const variant_type& v) {
    EXPECT_EQ(v.index(), 1);
    EXPECT_EQ(get<ptr>(v).get(), &tag);
  };

  ptr p;

  // move construction from member
  p.reset(&tag);
  variant_type v0{std::move(p)};
  ExpectIsTag(v0);

  // move assignment from member
  p.reset(&tag);
  v0 = std::move(p);
  ExpectIsTag(v0);

  // move construction from other variant
  variant_type v1{std::move(v0)};
  ExpectIsTag(v1);

  // move assignment from other variant
  p.reset(&tag);
  variant_type v2{std::move(p)};
  v1 = std::move(v2);
  ExpectIsTag(v1);

  // type changing move assignment from member
  variant_type v3;
  EXPECT_NE(v3.index(), 1);
  p.reset(&tag);
  v3 = std::move(p);
  ExpectIsTag(v3);

  // type changing move assignment from other variant
  variant_type v4;
  EXPECT_NE(v4.index(), 1);
  v4 = std::move(v3);
  ExpectIsTag(v4);
}

TEST(Variant, ExceptionSafety) {
  struct {
  } actually_throw;

  struct {
  } dont_throw;

  struct ConstructorThrows {
    explicit ConstructorThrows(decltype(actually_throw)) { throw 42; }
    explicit ConstructorThrows(decltype(dont_throw)) {}

    ConstructorThrows(const ConstructorThrows&) { throw 42; }

    ConstructorThrows& operator=(const ConstructorThrows&) = default;
    ConstructorThrows(ConstructorThrows&&) = default;
    ConstructorThrows& operator=(ConstructorThrows&&) = default;
  };

  Variant<int, ConstructorThrows> v;

  // constructor throws during emplacement
  EXPECT_THROW(v.emplace<ConstructorThrows>(actually_throw), int);
  // safely returned to the default state
  EXPECT_EQ(v.index(), 0);

  // constructor throws during copy assignment from member
  EXPECT_THROW(
      {
        const ConstructorThrows throws(dont_throw);
        v = throws;
      },
      int);
  // safely returned to the default state
  EXPECT_EQ(v.index(), 0);
}

template <typename V, typename T>
struct AssertVisitOne {
  void operator()(const T& actual) { EXPECT_EQ(&actual, expected_); }

  void operator()(T* actual) { EXPECT_EQ(actual, expected_); }

  template <typename U>
  void operator()(const U&) {
    FAIL() << "the expected type was not visited.";
  }

  template <typename U>
  void operator()(U*) {
    FAIL() << "the expected type was not visited.";
  }

  explicit AssertVisitOne(T member) : member_(std::move(member)) {}

  void operator()() {
    V v{member_};
    expected_ = &get<T>(v);
    visit(*this, v);
    visit(*this, &v);
  }

  T member_;
  const T* expected_;
};

template <typename... T>
void AssertVisit(T... member) {
  for (auto Assert :
       {std::function<void()>(AssertVisitOne<Variant<T...>, T>{member})...}) {
    Assert();
  }
}

TEST(VariantTest, Visit) {
  AssertVisit(32, std::string("hello"), true);
  AssertVisit(std::string("world"), false, 53);
  AssertVisit(nullptr, std::true_type{}, std::string("!"));
  AssertVisit(std::vector<int>{1, 3, 3, 7}, "C string");

  using int_or_string = Variant<int, std::string>;
  int_or_string v;

  // value returning visit:
  struct {
    int_or_string operator()(int i) { return int_or_string{i * 2}; }
    int_or_string operator()(const std::string& s) { return int_or_string{s + s}; }
  } Double;

  v = 7;
  EXPECT_EQ(visit(Double, v), int_or_string{14});

  v = "lolol";
  EXPECT_EQ(visit(Double, v), int_or_string{"lolollolol"});

  // mutating visit:
  struct {
    void operator()(int* i) { *i *= 2; }
    void operator()(std::string* s) { *s += *s; }
  } DoubleInplace;

  v = 7;
  visit(DoubleInplace, &v);
  EXPECT_EQ(v, int_or_string{14});

  v = "lolol";
  visit(DoubleInplace, &v);
  EXPECT_EQ(v, int_or_string{"lolollolol"});
}

TEST(VariantTest, Equality) {
  using int_or_double = Variant<int, double>;

  auto eq = [](const int_or_double& a, const int_or_double& b) {
    EXPECT_TRUE(a == b);
    EXPECT_FALSE(a != b);
  };
  auto ne = [](const int_or_double& a, const int_or_double& b) {
    EXPECT_TRUE(a != b);
    EXPECT_FALSE(a == b);
  };

  int_or_double u, v;
  u.emplace<int>(1);
  v.emplace<int>(1);
  eq(u, v);
  v.emplace<int>(2);
  ne(u, v);
  v.emplace<double>(1.0);
  ne(u, v);
  u.emplace<double>(1.0);
  eq(u, v);
  u.emplace<double>(2.0);
  ne(u, v);
}

}  // namespace
}  // namespace util
}  // namespace arrow
