// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++98, c++03, c++11, c++14

// XFAIL: dylib-has-no-bad_variant_access && !libcpp-no-exceptions

// <variant>
// template <class Visitor, class... Variants>
// constexpr see below visit(Visitor&& vis, Variants&&... vars);

#include <cassert>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>

#include "test_macros.h"
#include "type_id.h"
#include "variant_test_helpers.h"

enum CallType : unsigned {
  CT_None,
  CT_NonConst = 1,
  CT_Const = 2,
  CT_LValue = 4,
  CT_RValue = 8
};

inline constexpr CallType operator|(CallType LHS, CallType RHS) {
  return static_cast<CallType>(static_cast<unsigned>(LHS) |
                               static_cast<unsigned>(RHS));
}

struct ForwardingCallObject {

  template <class... Args> bool operator()(Args &&...) & {
    set_call<Args &&...>(CT_NonConst | CT_LValue);
    return true;
  }

  template <class... Args> bool operator()(Args &&...) const & {
    set_call<Args &&...>(CT_Const | CT_LValue);
    return true;
  }

  // Don't allow the call operator to be invoked as an rvalue.
  template <class... Args> bool operator()(Args &&...) && {
    set_call<Args &&...>(CT_NonConst | CT_RValue);
    return true;
  }

  template <class... Args> bool operator()(Args &&...) const && {
    set_call<Args &&...>(CT_Const | CT_RValue);
    return true;
  }

  template <class... Args> static void set_call(CallType type) {
    assert(last_call_type == CT_None);
    assert(last_call_args == nullptr);
    last_call_type = type;
    last_call_args = std::addressof(makeArgumentID<Args...>());
  }

  template <class... Args> static bool check_call(CallType type) {
    bool result = last_call_type == type && last_call_args &&
                  *last_call_args == makeArgumentID<Args...>();
    last_call_type = CT_None;
    last_call_args = nullptr;
    return result;
  }

  static CallType last_call_type;
  static const TypeID *last_call_args;
};

CallType ForwardingCallObject::last_call_type = CT_None;
const TypeID *ForwardingCallObject::last_call_args = nullptr;

void test_call_operator_forwarding() {
  using Fn = ForwardingCallObject;
  Fn obj{};
  const Fn &cobj = obj;
  { // test call operator forwarding - no variant
    std::visit(obj);
    assert(Fn::check_call<>(CT_NonConst | CT_LValue));
    std::visit(cobj);
    assert(Fn::check_call<>(CT_Const | CT_LValue));
    std::visit(std::move(obj));
    assert(Fn::check_call<>(CT_NonConst | CT_RValue));
    std::visit(std::move(cobj));
    assert(Fn::check_call<>(CT_Const | CT_RValue));
  }
  { // test call operator forwarding - single variant, single arg
    using V = std::variant<int>;
    V v(42);
    std::visit(obj, v);
    assert(Fn::check_call<int &>(CT_NonConst | CT_LValue));
    std::visit(cobj, v);
    assert(Fn::check_call<int &>(CT_Const | CT_LValue));
    std::visit(std::move(obj), v);
    assert(Fn::check_call<int &>(CT_NonConst | CT_RValue));
    std::visit(std::move(cobj), v);
    assert(Fn::check_call<int &>(CT_Const | CT_RValue));
  }
  { // test call operator forwarding - single variant, multi arg
    using V = std::variant<int, long, double>;
    V v(42l);
    std::visit(obj, v);
    assert(Fn::check_call<long &>(CT_NonConst | CT_LValue));
    std::visit(cobj, v);
    assert(Fn::check_call<long &>(CT_Const | CT_LValue));
    std::visit(std::move(obj), v);
    assert(Fn::check_call<long &>(CT_NonConst | CT_RValue));
    std::visit(std::move(cobj), v);
    assert(Fn::check_call<long &>(CT_Const | CT_RValue));
  }
  { // test call operator forwarding - multi variant, multi arg
    using V = std::variant<int, long, double>;
    using V2 = std::variant<int *, std::string>;
    V v(42l);
    V2 v2("hello");
    std::visit(obj, v, v2);
    assert((Fn::check_call<long &, std::string &>(CT_NonConst | CT_LValue)));
    std::visit(cobj, v, v2);
    assert((Fn::check_call<long &, std::string &>(CT_Const | CT_LValue)));
    std::visit(std::move(obj), v, v2);
    assert((Fn::check_call<long &, std::string &>(CT_NonConst | CT_RValue)));
    std::visit(std::move(cobj), v, v2);
    assert((Fn::check_call<long &, std::string &>(CT_Const | CT_RValue)));
  }
}

void test_argument_forwarding() {
  using Fn = ForwardingCallObject;
  Fn obj{};
  const auto Val = CT_LValue | CT_NonConst;
  { // single argument - value type
    using V = std::variant<int>;
    V v(42);
    const V &cv = v;
    std::visit(obj, v);
    assert(Fn::check_call<int &>(Val));
    std::visit(obj, cv);
    assert(Fn::check_call<const int &>(Val));
    std::visit(obj, std::move(v));
    assert(Fn::check_call<int &&>(Val));
    std::visit(obj, std::move(cv));
    assert(Fn::check_call<const int &&>(Val));
  }
#if !defined(TEST_VARIANT_HAS_NO_REFERENCES)
  { // single argument - lvalue reference
    using V = std::variant<int &>;
    int x = 42;
    V v(x);
    const V &cv = v;
    std::visit(obj, v);
    assert(Fn::check_call<int &>(Val));
    std::visit(obj, cv);
    assert(Fn::check_call<int &>(Val));
    std::visit(obj, std::move(v));
    assert(Fn::check_call<int &>(Val));
    std::visit(obj, std::move(cv));
    assert(Fn::check_call<int &>(Val));
  }
  { // single argument - rvalue reference
    using V = std::variant<int &&>;
    int x = 42;
    V v(std::move(x));
    const V &cv = v;
    std::visit(obj, v);
    assert(Fn::check_call<int &>(Val));
    std::visit(obj, cv);
    assert(Fn::check_call<int &>(Val));
    std::visit(obj, std::move(v));
    assert(Fn::check_call<int &&>(Val));
    std::visit(obj, std::move(cv));
    assert(Fn::check_call<int &&>(Val));
  }
  { // multi argument - multi variant
    using S = const std::string &;
    using V = std::variant<int, S, long &&>;
    const std::string str = "hello";
    long l = 43;
    V v1(42);
    const V &cv1 = v1;
    V v2(str);
    const V &cv2 = v2;
    V v3(std::move(l));
    const V &cv3 = v3;
    std::visit(obj, v1, v2, v3);
    assert((Fn::check_call<int &, S, long &>(Val)));
    std::visit(obj, cv1, cv2, std::move(v3));
    assert((Fn::check_call<const int &, S, long &&>(Val)));
  }
#endif
}

struct ReturnFirst {
  template <class... Args> constexpr int operator()(int f, Args &&...) const {
    return f;
  }
};

struct ReturnArity {
  template <class... Args> constexpr int operator()(Args &&...) const {
    return sizeof...(Args);
  }
};

void test_constexpr() {
  constexpr ReturnFirst obj{};
  constexpr ReturnArity aobj{};
  {
    using V = std::variant<int>;
    constexpr V v(42);
    static_assert(std::visit(obj, v) == 42, "");
  }
  {
    using V = std::variant<short, long, char>;
    constexpr V v(42l);
    static_assert(std::visit(obj, v) == 42, "");
  }
  {
    using V1 = std::variant<int>;
    using V2 = std::variant<int, char *, long long>;
    using V3 = std::variant<bool, int, int>;
    constexpr V1 v1;
    constexpr V2 v2(nullptr);
    constexpr V3 v3;
    static_assert(std::visit(aobj, v1, v2, v3) == 3, "");
  }
  {
    using V1 = std::variant<int>;
    using V2 = std::variant<int, char *, long long>;
    using V3 = std::variant<void *, int, int>;
    constexpr V1 v1;
    constexpr V2 v2(nullptr);
    constexpr V3 v3;
    static_assert(std::visit(aobj, v1, v2, v3) == 3, "");
  }
}

void test_exceptions() {
#ifndef TEST_HAS_NO_EXCEPTIONS
  ReturnArity obj{};
  auto test = [&](auto &&... args) {
    try {
      std::visit(obj, args...);
    } catch (const std::bad_variant_access &) {
      return true;
    } catch (...) {
    }
    return false;
  };
  {
    using V = std::variant<int, MakeEmptyT>;
    V v;
    makeEmpty(v);
    assert(test(v));
  }
  {
    using V = std::variant<int, MakeEmptyT>;
    using V2 = std::variant<long, std::string, void *>;
    V v;
    makeEmpty(v);
    V2 v2("hello");
    assert(test(v, v2));
  }
  {
    using V = std::variant<int, MakeEmptyT>;
    using V2 = std::variant<long, std::string, void *>;
    V v;
    makeEmpty(v);
    V2 v2("hello");
    assert(test(v2, v));
  }
  {
    using V = std::variant<int, MakeEmptyT>;
    using V2 = std::variant<long, std::string, void *, MakeEmptyT>;
    V v;
    makeEmpty(v);
    V2 v2;
    makeEmpty(v2);
    assert(test(v, v2));
  }
#endif
}

// See https://bugs.llvm.org/show_bug.cgi?id=31916
void test_caller_accepts_nonconst() {
  struct A {};
  struct Visitor {
    void operator()(A&) {}
  };
  std::variant<A> v;
  std::visit(Visitor{}, v);
}

int main(int, char**) {
  test_call_operator_forwarding();
  test_argument_forwarding();
  test_constexpr();
  test_exceptions();
  test_caller_accepts_nonconst();

  return 0;
}
