// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#pragma once
// This header contains all code for the instantiate_algorithms_op_deref* test cases.
// The instantiate_algorithm* tests take too long individually, so must be split into two parts.

#include <instantiate_algorithms.hpp>

// DevDiv-758138 "Several algorithms break with iterators that overload the comma operator."
// DevDiv-758134 "uninitialized_copy and uninitialized_copy_n break with classes that overload operator &."

// ADL will search this namespace for op,().
namespace Meow {

    // This is an evil element type that overloads op&().
    // It supports various operations required by various algorithms.
    struct Evil {
        void operator&() const = delete;

        bool operator<(const Evil&) const {
            return false;
        }
        bool operator==(const Evil&) const {
            return true;
        }

        Evil operator+(const Evil& e) const {
            return e;
        }
        Evil operator-(const Evil& e) const {
            return e;
        }
        Evil operator*(const Evil& e) const {
            return e;
        }

        Evil& operator++() {
            return *this;
        }
    };

    // These iterators point to Evil elements and will activate the op,() below.
    template <bool is_checked>
    struct BasicInIt {
        using iterator_category = std::input_iterator_tag;
        using value_type        = Evil;
        using difference_type   = test_difference_type;
        using pointer           = Evil*;
        using reference         = Evil&;

        BasicInIt(int, int) {}
        BasicInIt(const BasicInIt&)            = default;
        BasicInIt(BasicInIt&&)                 = default;
        BasicInIt& operator=(const BasicInIt&) = default;
        BasicInIt& operator=(BasicInIt&&)      = default;
        ~BasicInIt()                           = default;

        Evil& operator*() const {
            static Evil val{};
            return val;
        }
        Evil* operator->() const {
            return nullptr;
        }
        BasicInIt& operator++() {
            return *this;
        }
        void operator++(int) = delete; // avoid postincrement
        bool operator==(const BasicInIt&) const {
            return true;
        }
        bool operator!=(const BasicInIt&) const {
            return true;
        }

        void _Seek_to(BasicInIt<false>) {}

        BasicInIt<false> _Unwrapped() const {
            return BasicInIt<false>{11, 22};
        }

        static constexpr bool _Unwrap_when_unverified = _ITERATOR_DEBUG_LEVEL == 0;
    };

    template <bool is_checked>
    struct BasicFwdIt {
        using iterator_category = std::forward_iterator_tag;
        using value_type        = Evil;
        using difference_type   = test_difference_type;
        using pointer           = Evil*;
        using reference         = Evil&;

        BasicFwdIt()                             = default;
        BasicFwdIt(const BasicFwdIt&)            = default;
        BasicFwdIt(BasicFwdIt&&)                 = default;
        BasicFwdIt& operator=(const BasicFwdIt&) = default;
        BasicFwdIt& operator=(BasicFwdIt&&)      = default;
        ~BasicFwdIt()                            = default;

        Evil& operator*() const {
            static Evil val{};
            return val;
        }
        Evil* operator->() const {
            return nullptr;
        }
        BasicFwdIt& operator++() {
            return *this;
        }
        void operator++(int) = delete; // avoid postincrement
        bool operator==(const BasicFwdIt&) const {
            return true;
        }
        bool operator!=(const BasicFwdIt&) const {
            return true;
        }

        void _Seek_to(BasicFwdIt<false>) {}

        BasicFwdIt<false> _Unwrapped() const {
            return BasicFwdIt<false>{};
        }

        static constexpr bool _Unwrap_when_unverified = _ITERATOR_DEBUG_LEVEL == 0;
    };

    template <bool is_checked>
    struct BasicBidIt {
        using iterator_category = std::bidirectional_iterator_tag;
        using value_type        = Evil;
        using difference_type   = test_difference_type;
        using pointer           = Evil*;
        using reference         = Evil&;

        BasicBidIt()                             = default;
        BasicBidIt(const BasicBidIt&)            = default;
        BasicBidIt(BasicBidIt&&)                 = default;
        BasicBidIt& operator=(const BasicBidIt&) = default;
        BasicBidIt& operator=(BasicBidIt&&)      = default;
        ~BasicBidIt()                            = default;

        Evil& operator*() const {
            static Evil val{};
            return val;
        }
        Evil* operator->() const {
            return nullptr;
        }
        BasicBidIt& operator++() {
            return *this;
        }
        void operator++(int) = delete; // avoid postincrement
        bool operator==(const BasicBidIt&) const {
            return true;
        }
        bool operator!=(const BasicBidIt&) const {
            return true;
        }

        BasicBidIt& operator--() {
            return *this;
        }
        void operator--(int) = delete; // avoid postdecrement


        void _Seek_to(BasicBidIt<false>) {}

        BasicBidIt<false> _Unwrapped() const {
            return BasicBidIt<false>{};
        }

        static constexpr bool _Unwrap_when_unverified = _ITERATOR_DEBUG_LEVEL == 0;
    };

    template <bool is_checked>
    struct BasicRanIt {
        using iterator_category = std::random_access_iterator_tag;
        using value_type        = Evil;
        using difference_type   = test_difference_type;
        using pointer           = Evil*;
        using reference         = Evil&;

        BasicRanIt()                             = default;
        BasicRanIt(const BasicRanIt&)            = default;
        BasicRanIt(BasicRanIt&&)                 = default;
        BasicRanIt& operator=(const BasicRanIt&) = default;
        BasicRanIt& operator=(BasicRanIt&&)      = default;
        ~BasicRanIt()                            = default;

        Evil& operator*() const {
            static Evil val{};
            return val;
        }
        Evil* operator->() const {
            return nullptr;
        }
        BasicRanIt& operator++() {
            return *this;
        }
        void operator++(int) = delete; // avoid postincrement
        bool operator==(const BasicRanIt&) const {
            return true;
        }
        bool operator!=(const BasicRanIt&) const {
            return true;
        }
        BasicRanIt& operator--() {
            return *this;
        }
        void operator--(int) = delete; // avoid postdecrement

        BasicRanIt& operator+=(difference_type) {
            return *this;
        }
        BasicRanIt& operator-=(difference_type) {
            return *this;
        }
        BasicRanIt operator+(difference_type) const {
            return *this;
        }
        BasicRanIt operator-(difference_type) const {
            return *this;
        }
        difference_type operator-(const BasicRanIt&) const {
            return 0;
        }

        Evil& operator[](difference_type) const = delete; // avoid subscript, N4849 [algorithms.requirements]/8

        bool operator<(const BasicRanIt&) const {
            return true;
        }
        bool operator<=(const BasicRanIt&) const {
            return true;
        }
        bool operator>(const BasicRanIt&) const {
            return true;
        }
        bool operator>=(const BasicRanIt&) const {
            return true;
        }

        void _Seek_to(BasicRanIt<false>) {}

        BasicRanIt<false> _Unwrapped() const {
            return BasicRanIt<false>{};
        }

        static constexpr bool _Unwrap_when_unverified = _ITERATOR_DEBUG_LEVEL == 0;

#if _ITERATOR_DEBUG_LEVEL != 0
        void _Verify_offset(difference_type) const {}
#endif // _ITERATOR_DEBUG_LEVEL != 0
    };

    template <bool is_checked>
    BasicRanIt<is_checked> operator+(test_difference_type, const BasicRanIt<is_checked>& i) {
        return i;
    }

    template <bool is_checked>
    struct BasicOutIt {
        using iterator_category = std::output_iterator_tag;
        using value_type        = void;
        using difference_type   = void;
        using pointer           = void;
        using reference         = void;

        BasicOutIt(int, int) {}
        BasicOutIt(const BasicOutIt&)            = default;
        BasicOutIt(BasicOutIt&&)                 = default;
        BasicOutIt& operator=(const BasicOutIt&) = default;
        BasicOutIt& operator=(BasicOutIt&&)      = default;
        ~BasicOutIt()                            = default;

        Evil& operator*() const {
            static Evil val{};
            return val;
        }
        BasicOutIt& operator++() {
            return *this;
        }
        void operator++(int) = delete; // avoid postincrement


        void _Seek_to(BasicOutIt<false>) {}

        BasicOutIt<false> _Unwrapped() const {
            return BasicOutIt<false>{11, 22};
        }

        static constexpr bool _Unwrap_when_unverified = _ITERATOR_DEBUG_LEVEL == 0;
    };

    // If it's dragged in via ADL, this op,() will absorb anything.
    template <typename T, typename U>
    void operator,(const T&, const U&) = delete;

    // VSO-258601 "std::vector should not rely on operator& in Debug mode"
    // If it's dragged in via ADL, this op&() will absorb anything.
    template <typename T>
    void operator&(const T&) = delete;

} // namespace Meow

void test() {
    // Test with iterators which have overloaded comma operator.
    std_testing::instantiate_std_algorithms_with<Meow::Evil>(Meow::BasicInIt<true>{11, 22}, Meow::BasicFwdIt<true>{},
        Meow::BasicBidIt<true>{}, Meow::BasicRanIt<true>{}, Meow::BasicOutIt<true>{11, 22});
}

// Also test DevDiv-938759 "<type_traits>: is_assignable should tolerate overloaded comma operators [libcxx]".
#define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__)

STATIC_ASSERT(std::is_assignable_v<Meow::Evil&, Meow::Evil>);
STATIC_ASSERT(!std::is_assignable_v<Meow::Evil&, int>);

#undef STATIC_ASSERT

#if _HAS_CXX17
#include <optional>

// Defend against regression of the "," operator in optional::value
void test_optional() {
    using Meow::Evil;
    using std::optional;
    {
        optional<Evil> oe{Evil()};
        (void) oe.value();
    }
    {
        const optional<Evil> oe{Evil()};
        (void) oe.value();
    }
    {
        (void) optional<Evil>{Evil()}.value();
    }
    {
        using T = const optional<Evil>;
        (void) T{Evil()}.value();
    }
}
#endif // _HAS_CXX17
