/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed 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 <folly/Overload.h>

#include <optional>
#include <type_traits>
#include <variant>

#include <boost/variant.hpp>

#include <folly/DiscriminatedPtr.h>
#include <folly/portability/GTest.h>

namespace folly {
namespace test {
namespace {

constexpr int functionUnsigned(const unsigned x) {
  return x + 3;
}
struct HasMemberFunctions {
  constexpr int withConst(const int x) const { return x + 4; }
  constexpr int withMutable(const int x) { return x + 5; }
};
struct HasRefQualifiedMemberFunctions {
  constexpr int withConstRef(const int x) const& { return x + 6; }
  constexpr int withMutableRef(const int x) & { return x + 7; }
  constexpr int withMutableRefRef(const int x) && { return x + 8; }
  constexpr int withConstRefRef(const int x) const&& { return x + 9; }
};
struct HasNullaryMemberFunction {
  constexpr int nullary() const { return -1; }
};
struct HasMemberData {
  int member = -2;
};
struct HasNullaryMemberFunctionAndMemberData {
  constexpr int nullary() const { return -3; }
  int member = -4;
};

constexpr auto overloadSet = overload(
    []() { return 1; },
    [](const int x) { return x + 1; },
    [](const long x) { return x + 2; },
    functionUnsigned,
    &HasMemberFunctions::withConst,
    &HasMemberFunctions::withMutable,
    &HasRefQualifiedMemberFunctions::withConstRef,
    &HasRefQualifiedMemberFunctions::withMutableRef,
    &HasRefQualifiedMemberFunctions::withMutableRefRef,
    &HasRefQualifiedMemberFunctions::withConstRefRef,
    &HasNullaryMemberFunction::nullary,
    &HasMemberData::member);

static_assert(overloadSet() == 1);
static_assert(overloadSet(1) == 2);
static_assert(overloadSet(1L) == 3);
static_assert(overloadSet(1U) == 4);

constexpr auto testConstMember() {
  HasMemberFunctions const c;
  return overloadSet(c, 1);
}
static_assert(testConstMember() == 5);
static_assert(
    overload(&HasMemberFunctions::withConst)(HasMemberFunctions(), 1) == 5);

constexpr auto testMutableMember() {
  HasMemberFunctions c;
  return overloadSet(c, 1);
}
static_assert(testMutableMember() == 6);
static_assert(overloadSet(HasMemberFunctions(), 1) == 6);

constexpr auto testConstRefMember() {
  const HasRefQualifiedMemberFunctions c;
  return overloadSet(c, 1);
}
static_assert(testConstRefMember() == 7);

constexpr auto testMutableRefMember() {
  HasRefQualifiedMemberFunctions c;
  return overloadSet(c, 1);
}
static_assert(testMutableRefMember() == 8);

static_assert(overloadSet(HasRefQualifiedMemberFunctions(), 1) == 9);

constexpr auto testMutableRefRefMember() {
  const HasRefQualifiedMemberFunctions c;
  return overloadSet(std::move(c), 1);
}
static_assert(testMutableRefRefMember() == 10);

static_assert(overloadSet(HasNullaryMemberFunction()) == -1);
static_assert(overloadSet(HasMemberData()) == -2);

void isNoexcept(int) noexcept {}
void isNotNoexcept(unsigned) {}

constexpr auto noexceptOverload = overload(isNoexcept, isNotNoexcept);
static_assert(noexcept(noexceptOverload(1)));
static_assert(!noexcept(noexceptOverload(1U)));

constexpr auto ambiguous = overload([] {}, [] { return 5; });
static_assert(!std::is_invocable_v<decltype(ambiguous)>);

constexpr auto emptyOverloadSet = overload();
static_assert(!std::is_invocable_v<decltype(emptyOverloadSet)>);

constexpr auto nullaryMemberAndMemberData = overload(
    &HasNullaryMemberFunctionAndMemberData::nullary,
    &HasNullaryMemberFunctionAndMemberData::member);
static_assert(!std::is_invocable_v<decltype(nullaryMemberAndMemberData)>);

constexpr auto onlyMemberData = overload(&HasMemberData::member);
static_assert(std::is_same_v<
              decltype(onlyMemberData(std::declval<HasMemberData&>())),
              int&>);
static_assert(std::is_same_v<
              decltype(onlyMemberData(std::declval<const HasMemberData&>())),
              const int&>);
static_assert(std::is_same_v<
              decltype(onlyMemberData(std::declval<HasMemberData&&>())),
              int&&>);
static_assert(std::is_same_v<
              decltype(onlyMemberData(std::declval<const HasMemberData&&>())),
              const int&&>);

struct One {
  std::string toString() const { return "One"; }
};
struct Two {
  std::string toString() const { return "Two"; }
};

TEST(Overload, StdVariant) {
  using V = std::variant<One, Two>;
  V one(One{});
  V two(Two{});

  EXPECT_TRUE(variant_match(
      one, [](const One&) { return true; }, [](const Two&) { return false; }));
  EXPECT_TRUE(variant_match(
      two, [](const One&) { return false; }, [](const Two&) { return true; }));
  EXPECT_TRUE(variant_match(
      std::move(one), [](One&&) { return true; }, [](Two&&) { return false; }));
  EXPECT_TRUE(variant_match(
      std::move(two), [](One&&) { return false; }, [](Two&&) { return true; }));

  auto toString = [](const auto& variant) {
    return variant_match(
        variant, [](const auto& value) { return value.toString(); });
  };
  EXPECT_EQ(toString(one), "One");
  EXPECT_EQ(toString(two), "Two");
}

TEST(Overload, BoostVariant) {
  using V = boost::variant<One, Two>;
  V one(One{});
  V two(Two{});

  EXPECT_TRUE(variant_match(
      one, [](const One&) { return true; }, [](const Two&) { return false; }));
  EXPECT_TRUE(variant_match(
      two, [](const One&) { return false; }, [](const Two&) { return true; }));
  EXPECT_TRUE(variant_match(
      std::move(one), [](One&&) { return true; }, [](Two&&) { return false; }));
  EXPECT_TRUE(variant_match(
      std::move(two), [](One&&) { return false; }, [](Two&&) { return true; }));

  auto toString = [](const auto& variant) {
    return variant_match(
        variant, [](const auto& value) { return value.toString(); });
  };
  EXPECT_EQ(toString(one), "One");
  EXPECT_EQ(toString(two), "Two");
}

TEST(Overload, DiscriminatedPtr) {
  using V = DiscriminatedPtr<One, Two>;
  One one_obj;
  Two two_obj;
  V one_ptr(&one_obj);
  V two_ptr(&two_obj);

  EXPECT_TRUE(variant_match(
      one_ptr,
      [](const One*) { return true; },
      [](const Two*) { return false; }));
  EXPECT_TRUE(variant_match(
      two_ptr,
      [](const One*) { return false; },
      [](const Two*) { return true; }));

  auto toString = [](const auto& variant) {
    return variant_match(
        variant, [](const auto* value) { return value->toString(); });
  };
  EXPECT_EQ(toString(one_ptr), "One");
  EXPECT_EQ(toString(two_ptr), "Two");
}

TEST(Overload, StdPattern) {
  using V = std::variant<One, Two>;
  V one(One{});
  V two(Two{});

  auto is_one_overload = overload(
      [](const One&) { return true; }, [](const Two&) { return false; });
  EXPECT_TRUE(std::visit(is_one_overload, one));
  EXPECT_TRUE(variant_match(one, is_one_overload));
  EXPECT_FALSE(variant_match(two, is_one_overload));

  auto is_two_overload = overload(
      [](const One&) { return false; }, [](const Two&) { return true; });
  EXPECT_TRUE(std::visit(is_two_overload, two));
  EXPECT_FALSE(variant_match(one, is_two_overload));
  EXPECT_TRUE(variant_match(two, is_two_overload));

  auto is_one_copy = overload(is_one_overload);
  auto is_one_const_copy =
      overload(static_cast<const decltype(is_one_overload)&>(is_one_overload));
  EXPECT_TRUE(variant_match(one, is_one_copy));
  EXPECT_TRUE(variant_match(one, is_one_const_copy));
  EXPECT_FALSE(variant_match(two, is_one_copy));
  EXPECT_FALSE(variant_match(two, is_one_const_copy));
}

TEST(Overload, BoostPattern) {
  using V = boost::variant<One, Two>;
  V one(One{});
  V two(Two{});

  auto is_one_overload = overload(
      [](const One&) { return true; }, [](const Two&) { return false; });
  EXPECT_TRUE(boost::apply_visitor(is_one_overload, one));
  EXPECT_TRUE(variant_match(one, is_one_overload));
  EXPECT_FALSE(variant_match(two, is_one_overload));

  auto is_two_overload = overload(
      [](const One&) { return false; }, [](const Two&) { return true; });
  EXPECT_TRUE(boost::apply_visitor(is_two_overload, two));
  EXPECT_FALSE(variant_match(one, is_two_overload));
  EXPECT_TRUE(variant_match(two, is_two_overload));

  auto is_one_copy = overload(is_one_overload);
  auto is_one_const_copy =
      overload(static_cast<const decltype(is_one_overload)&>(is_one_overload));
  EXPECT_TRUE(variant_match(one, is_one_copy));
  EXPECT_TRUE(variant_match(one, is_one_const_copy));
  EXPECT_FALSE(variant_match(two, is_one_copy));
  EXPECT_FALSE(variant_match(two, is_one_const_copy));
}

TEST(Overload, ReturnType) {
  using V = std::variant<std::monostate, One>;
  V null(std::monostate{});
  V one(One{});

  using R = std::optional<int>;

  EXPECT_FALSE(variant_match(
      null,
      [](const std::monostate&) -> R { return std::nullopt; },
      [](const One&) -> R { return 1; }));
  EXPECT_EQ(
      1,
      variant_match(
          one,
          [](const std::monostate&) -> R { return std::nullopt; },
          [](const One&) -> R { return 1; }));

  EXPECT_FALSE(variant_match<R>(
      null,
      [](const std::monostate&) { return std::nullopt; },
      [](const One&) { return 1; }));
  EXPECT_EQ(
      1,
      variant_match<R>(
          one,
          [](const std::monostate&) { return std::nullopt; },
          [](const One&) { return 1; }));

  int null_result = -1;
  variant_match<void>(
      null,
      [&null_result](const std::monostate&) {
        null_result = 0;
        return std::nullopt;
      },
      [&null_result](const One&) {
        null_result = 1;
        return 1;
      });
  EXPECT_EQ(0, null_result);

  int one_result = -1;
  variant_match<void>(
      one,
      [&one_result](const std::monostate&) {
        one_result = 0;
        return std::nullopt;
      },
      [&one_result](const One&) {
        one_result = 1;
        return 1;
      });
  EXPECT_EQ(1, one_result);

  EXPECT_FALSE(variant_match<R>(
      std::move(null),
      [](std::monostate&&) { return std::nullopt; },
      [](One&&) { return 1; }));
}

} // namespace
} // namespace test
} // namespace folly
