//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef TEST_STD_UTILITIES_EXPECTED_TYPES_H
#define TEST_STD_UTILITIES_EXPECTED_TYPES_H

#include <utility>
#include "test_macros.h"

template <bool copyMoveNoexcept, bool convertNoexcept = true>
struct TracedBase {
  struct state {
    bool copyCtorCalled   = false;
    bool copyAssignCalled = false;
    bool moveCtorCalled   = false;
    bool moveAssignCalled = false;
    bool dtorCalled       = false;
  };

  state* state_      = nullptr;
  bool copiedFromInt = false;
  bool movedFromInt  = false;
  bool copiedFromTmp = false;
  bool movedFromTmp  = false;
  int data_;

  constexpr TracedBase(const int& ii) noexcept(convertNoexcept) : data_(ii) { copiedFromInt = true; }
  constexpr TracedBase(int&& ii) noexcept(convertNoexcept) : data_(ii) { movedFromInt = true; }
  constexpr TracedBase(state& s, int ii) noexcept : state_(&s), data_(ii) {}
  constexpr TracedBase(const TracedBase& other) noexcept(copyMoveNoexcept) : state_(other.state_), data_(other.data_) {
    if (state_) {
      state_->copyCtorCalled = true;
    } else {
      copiedFromTmp = true;
    }
  }
  constexpr TracedBase(TracedBase&& other) noexcept(copyMoveNoexcept) : state_(other.state_), data_(other.data_) {
    if (state_) {
      state_->moveCtorCalled = true;
    } else {
      movedFromTmp = true;
    }
  }
  constexpr TracedBase& operator=(const TracedBase& other) noexcept(copyMoveNoexcept) {
    data_                    = other.data_;
    state_->copyAssignCalled = true;
    return *this;
  }
  constexpr TracedBase& operator=(TracedBase&& other) noexcept(copyMoveNoexcept) {
    data_                    = other.data_;
    state_->moveAssignCalled = true;
    return *this;
  }
  constexpr ~TracedBase() {
    if (state_) {
      state_->dtorCalled = true;
    }
  }
};

using Traced         = TracedBase<false>;
using TracedNoexcept = TracedBase<true>;

using MoveThrowConvNoexcept = TracedBase<false, true>;
using MoveNoexceptConvThrow = TracedBase<true, false>;
using BothMayThrow          = TracedBase<false, false>;
using BothNoexcept          = TracedBase<true, true>;

struct ADLSwap {
  int i;
  bool adlSwapCalled = false;
  constexpr ADLSwap(int ii) : i(ii) {}
  constexpr friend void swap(ADLSwap& x, ADLSwap& y) {
    std::swap(x.i, y.i);
    x.adlSwapCalled = true;
    y.adlSwapCalled = true;
  }
};

template <bool Noexcept>
struct TrackedMove {
  int i;
  int numberOfMoves = 0;
  bool swapCalled   = false;

  constexpr TrackedMove(int ii) : i(ii) {}
  constexpr TrackedMove(TrackedMove&& other) noexcept(Noexcept)
      : i(other.i), numberOfMoves(other.numberOfMoves), swapCalled(other.swapCalled) {
    ++numberOfMoves;
  }

  constexpr friend void swap(TrackedMove& x, TrackedMove& y) {
    std::swap(x.i, y.i);
    std::swap(x.numberOfMoves, y.numberOfMoves);
    x.swapCalled = true;
    y.swapCalled = true;
  }
};

#ifndef TEST_HAS_NO_EXCEPTIONS
struct Except {};

struct ThrowOnCopyConstruct {
  ThrowOnCopyConstruct() = default;
  ThrowOnCopyConstruct(const ThrowOnCopyConstruct&) { throw Except{}; }
  ThrowOnCopyConstruct& operator=(const ThrowOnCopyConstruct&) = default;
};

struct ThrowOnMoveConstruct {
  ThrowOnMoveConstruct() = default;
  ThrowOnMoveConstruct(ThrowOnMoveConstruct&&) { throw Except{}; }
  ThrowOnMoveConstruct& operator=(ThrowOnMoveConstruct&&) = default;
};

struct ThrowOnConvert {
  ThrowOnConvert() = default;
  ThrowOnConvert(const int&) { throw Except{}; }
  ThrowOnConvert(int&&) { throw Except{}; }
  ThrowOnConvert(const ThrowOnConvert&) noexcept(false) {}
  ThrowOnConvert& operator=(const ThrowOnConvert&) = default;
  ThrowOnConvert(ThrowOnConvert&&) noexcept(false) {}
  ThrowOnConvert& operator=(ThrowOnConvert&&) = default;
};

struct ThrowOnMove {
  bool* destroyed = nullptr;
  ThrowOnMove()   = default;
  ThrowOnMove(bool& d) : destroyed(&d) {}
  ThrowOnMove(ThrowOnMove&&) { throw Except{}; };
  ThrowOnMove& operator=(ThrowOnMove&&) = default;
  ~ThrowOnMove() {
    if (destroyed) {
      *destroyed = true;
    }
  }
};

#endif // TEST_HAS_NO_EXCEPTIONS

#endif // TEST_STD_UTILITIES_EXPECTED_TYPES_H
