/**
 * D header file for interaction with Microsoft C++ <xutility>
 *
 * Copyright: Copyright (c) 2018 D Language Foundation
 * License: Distributed under the
 *      $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
 *    (See accompanying file LICENSE)
 * Authors:   Manu Evans
 * Source:    $(DRUNTIMESRC core/stdcpp/xutility.d)
 */

module core.stdcpp.xutility;

@nogc:

version (CppRuntime_LLVM)
{
    import core.internal.traits : AliasSeq;
    enum StdNamespace = AliasSeq!("std", "__1");
}
else
{
    enum StdNamespace = "std";
}

/**
 * Possible values of the `__cplusplus` macro provided by C++ compilers
 *
 * For foolproofness, use ordering comparison, e.g. `__cplusplus >= CppStdRevision.cpp17`.
 */
enum CppStdRevision : uint
{
    cpp98 = 199711,
    cpp11 = 201103,
    cpp14 = 201402,
    cpp17 = 201703,
    cpp20 = 202002,
}

/**
 * Returns the target C++ version, encoded as C++ compilers do
 *
 * C++ compilers provide a `__cplusplus` macro which returns an integer
 * representing the targetted standard. This manifest provides the same
 * interface, retrieved from the compiler via a `__traits`.
 */
enum __cplusplus = __traits(getTargetInfo, "cppStd");

// wrangle C++ features
enum __cpp_sized_deallocation = __cplusplus >= CppStdRevision.cpp14 || is(typeof(_MSC_VER)) ? 201309 : 0;
enum __cpp_aligned_new = __cplusplus >= CppStdRevision.cpp17 ? 201606 : 0;


version (CppRuntime_Microsoft)
{
    import core.stdcpp.type_traits : is_empty;

    version (_MSC_VER_1200)
        enum _MSC_VER = 1200;
    else version (_MSC_VER_1300)
        enum _MSC_VER = 1300;
    else version (_MSC_VER_1310)
        enum _MSC_VER = 1310;
    else version (_MSC_VER_1400)
        enum _MSC_VER = 1400;
    else version (_MSC_VER_1500)
        enum _MSC_VER = 1500;
    else version (_MSC_VER_1600)
        enum _MSC_VER = 1600;
    else version (_MSC_VER_1700)
        enum _MSC_VER = 1700;
    else version (_MSC_VER_1800)
        enum _MSC_VER = 1800;
    else version (_MSC_VER_1900)
        enum _MSC_VER = 1900;
    else version (_MSC_VER_1910)
        enum _MSC_VER = 1910;
    else version (_MSC_VER_1911)
        enum _MSC_VER = 1911;
    else version (_MSC_VER_1912)
        enum _MSC_VER = 1912;
    else version (_MSC_VER_1913)
        enum _MSC_VER = 1913;
    else version (_MSC_VER_1914)
        enum _MSC_VER = 1914;
    else version (_MSC_VER_1915)
        enum _MSC_VER = 1915;
    else version (_MSC_VER_1916)
        enum _MSC_VER = 1916;
    else version (_MSC_VER_1920)
        enum _MSC_VER = 1920;
    else version (_MSC_VER_1921)
        enum _MSC_VER = 1921;
    else version (_MSC_VER_1922)
        enum _MSC_VER = 1922;
    else version (_MSC_VER_1923)
        enum _MSC_VER = 1923;
    else
        enum _MSC_VER = 1923; // assume most recent compiler version

    // Client code can mixin the set of MSVC linker directives
    mixin template MSVCLinkDirectives(bool failMismatch = false)
    {
        import core.stdcpp.xutility : __CXXLIB__, _ITERATOR_DEBUG_LEVEL;

        static if (__CXXLIB__ == "libcmtd")
        {
            pragma(lib, "libcpmtd");
            static if (failMismatch)
                pragma(linkerDirective, "/FAILIFMISMATCH:RuntimeLibrary=MTd_StaticDebug");
        }
        else static if (__CXXLIB__ == "msvcrtd")
        {
            pragma(lib, "msvcprtd");
            static if (failMismatch)
                pragma(linkerDirective, "/FAILIFMISMATCH:RuntimeLibrary=MDd_DynamicDebug");
        }
        else static if (__CXXLIB__ == "libcmt")
        {
            pragma(lib, "libcpmt");
            static if (failMismatch)
                pragma(linkerDirective, "/FAILIFMISMATCH:RuntimeLibrary=MT_StaticRelease");
        }
        else static if (__CXXLIB__ == "msvcrt")
        {
            pragma(lib, "msvcprt");
            static if (failMismatch)
                pragma(linkerDirective, "/FAILIFMISMATCH:RuntimeLibrary=MD_DynamicRelease");
        }
        static if (failMismatch)
            pragma(linkerDirective, "/FAILIFMISMATCH:_ITERATOR_DEBUG_LEVEL=" ~ ('0' + _ITERATOR_DEBUG_LEVEL));
    }

    // HACK: should we guess _DEBUG for `debug` builds?
    version (NDEBUG) {}
    else debug version = _DEBUG;

    // By specific user request
    version (_ITERATOR_DEBUG_LEVEL_0)
        enum _ITERATOR_DEBUG_LEVEL = 0;
    else version (_ITERATOR_DEBUG_LEVEL_1)
        enum _ITERATOR_DEBUG_LEVEL = 1;
    else version (_ITERATOR_DEBUG_LEVEL_2)
        enum _ITERATOR_DEBUG_LEVEL = 2;
    else
    {
        // Match the C Runtime
        static if (__CXXLIB__ == "libcmtd" || __CXXLIB__ == "msvcrtd")
            enum _ITERATOR_DEBUG_LEVEL = 2;
        else static if (__CXXLIB__ == "libcmt" || __CXXLIB__ == "msvcrt" ||
                        __CXXLIB__ == "msvcrt100" || __CXXLIB__ == "msvcrt110" || __CXXLIB__ == "msvcrt120")
            enum _ITERATOR_DEBUG_LEVEL = 0;
        else
        {
            static if (__CXXLIB__.length > 0)
                pragma(msg, "Unrecognised C++ runtime library '" ~ __CXXLIB__ ~ "'");

            // No runtime specified; as a best-guess, -release will produce code that matches the MSVC release CRT
            version (_DEBUG)
                enum _ITERATOR_DEBUG_LEVEL = 2;
            else
                enum _ITERATOR_DEBUG_LEVEL = 0;
        }
    }

    // convenient alias for the C++ std library name
    enum __CXXLIB__ = __traits(getTargetInfo, "cppRuntimeLibrary");

extern(C++, "std"):
package:
    enum _LOCK_DEBUG = 3;

    extern(C++, class) struct _Lockit
    {
        this(int) nothrow @nogc @safe;
        ~this() nothrow @nogc @safe;

    private:
        int _Locktype;
    }
    void dummyDtor() { assert(false); }
    pragma(linkerDirective, "/ALTERNATENAME:" ~ _Lockit.__dtor.mangleof ~ "=" ~ dummyDtor.mangleof);

    struct _Container_base0
    {
    extern(D):
        void _Orphan_all()() nothrow @nogc @safe {}
        void _Swap_all()(ref _Container_base0) nothrow @nogc @safe {}
        void _Swap_proxy_and_iterators()(ref _Container_base0) nothrow {}
    }
    struct _Iterator_base0
    {
    extern(D):
        void _Adopt()(const(void)*) nothrow @nogc @safe {}
        const(_Container_base0)* _Getcont()() const nothrow @nogc @safe { return null; }

        enum bool _Unwrap_when_unverified = true;
    }

    struct _Container_proxy
    {
        const(_Container_base12)* _Mycont;
        _Iterator_base12* _Myfirstiter;
    }

    struct _Container_base12
    {
    extern(D):
        inout(_Iterator_base12*)*_Getpfirst()() inout nothrow @nogc @safe
        {
            return _Myproxy == null ? null : &_Myproxy._Myfirstiter;
        }
        void _Orphan_all()() nothrow @nogc @safe
        {
            static if (_ITERATOR_DEBUG_LEVEL == 2)
            {
                if (_Myproxy != null)
                {
                    auto _Lock = _Lockit(_LOCK_DEBUG);
                    for (_Iterator_base12 **_Pnext = &_Myproxy._Myfirstiter; *_Pnext != null; *_Pnext = (*_Pnext)._Mynextiter)
                        (*_Pnext)._Myproxy = null;
                    _Myproxy._Myfirstiter = null;
                }
            }
        }
//        void _Swap_all()(ref _Container_base12) nothrow @nogc;

        void _Swap_proxy_and_iterators()(ref _Container_base12 _Right) nothrow
        {
            static if (_ITERATOR_DEBUG_LEVEL == 2)
                auto _Lock = _Lockit(_LOCK_DEBUG);

            _Container_proxy* _Temp = _Myproxy;
            _Myproxy = _Right._Myproxy;
            _Right._Myproxy = _Temp;

            if (_Myproxy)
                _Myproxy._Mycont = &this;

            if (_Right._Myproxy)
                _Right._Myproxy._Mycont = &_Right;
        }

        _Container_proxy* _Myproxy;
    }

    struct _Iterator_base12
    {
    extern(D):
        void _Adopt()(_Container_base12 *_Parent) nothrow @nogc @safe
        {
            if (_Parent == null)
            {
                static if (_ITERATOR_DEBUG_LEVEL == 2)
                {
                    auto _Lock = _Lockit(_LOCK_DEBUG);
                    _Orphan_me();
                }
            }
            else
            {
                _Container_proxy *_Parent_proxy = _Parent._Myproxy;

                static if (_ITERATOR_DEBUG_LEVEL == 2)
                {
                    if (_Myproxy != _Parent_proxy)
                    {
                        auto _Lock = _Lockit(_LOCK_DEBUG);
                        _Orphan_me();
                        _Mynextiter = _Parent_proxy._Myfirstiter;
                        _Parent_proxy._Myfirstiter = &this;
                        _Myproxy = _Parent_proxy;
                    }
                }
                else
                    _Myproxy = _Parent_proxy;
            }
        }
        void _Clrcont()() nothrow @nogc @safe
        {
            _Myproxy = null;
        }
        const(_Container_base12)* _Getcont()() const nothrow @nogc @safe
        {
            return _Myproxy == null ? null : _Myproxy._Mycont;
        }
        inout(_Iterator_base12*)*_Getpnext()() inout nothrow @nogc @safe
        {
            return &_Mynextiter;
        }
        void _Orphan_me()() nothrow @nogc @safe
        {
            static if (_ITERATOR_DEBUG_LEVEL == 2)
            {
                if (_Myproxy != null)
                {
                    _Iterator_base12 **_Pnext = &_Myproxy._Myfirstiter;
                    while (*_Pnext != null && *_Pnext != &this)
                        _Pnext = &(*_Pnext)._Mynextiter;
                    assert(*_Pnext, "ITERATOR LIST CORRUPTED!");
                    *_Pnext = _Mynextiter;
                    _Myproxy = null;
                }
            }
        }

        enum bool _Unwrap_when_unverified = _ITERATOR_DEBUG_LEVEL == 0;

        _Container_proxy *_Myproxy;
        _Iterator_base12 *_Mynextiter;
    }

    static if (_ITERATOR_DEBUG_LEVEL == 0)
    {
        alias _Container_base = _Container_base0;
        alias _Iterator_base = _Iterator_base0;
    }
    else
    {
        alias _Container_base = _Container_base12;
        alias _Iterator_base = _Iterator_base12;
    }

    extern (C++, class) struct _Compressed_pair(_Ty1, _Ty2, bool Ty1Empty = is_empty!_Ty1.value)
    {
    pragma (inline, true):
    extern(D):
    pure nothrow @nogc:
        enum _HasFirst = !Ty1Empty;

        ref inout(_Ty1) first() inout @safe { return _Myval1; }
        ref inout(_Ty2) second() inout @safe { return _Myval2; }

        static if (!Ty1Empty)
            _Ty1 _Myval1;
        else
        {
            @property ref inout(_Ty1) _Myval1() inout @trusted { return *_GetBase(); }
            private inout(_Ty1)* _GetBase() inout @trusted { return cast(inout(_Ty1)*)&this; }
        }
        _Ty2 _Myval2;
    }

    // these are all [[noreturn]]
    void _Xbad_alloc() nothrow;
    void _Xinvalid_argument(const(char)* message) nothrow;
    void _Xlength_error(const(char)* message) nothrow;
    void _Xout_of_range(const(char)* message) nothrow;
    void _Xoverflow_error(const(char)* message) nothrow;
    void _Xruntime_error(const(char)* message) nothrow;
}
else version (CppRuntime_LLVM)
{
    import core.stdcpp.type_traits : is_empty;

extern(C++, "std"):

    extern (C++, class) struct __compressed_pair(_T1, _T2)
    {
    pragma (inline, true):
    extern(D):
        enum Ty1Empty = is_empty!_T1.value;
        enum Ty2Empty = is_empty!_T2.value;

        ref inout(_T1) first() inout nothrow @safe @nogc { return __value1_; }
        ref inout(_T2) second() inout nothrow @safe @nogc { return __value2_; }

    private:
        private inout(_T1)* __get_base1() inout { return cast(inout(_T1)*)&this; }
        private inout(_T2)* __get_base2() inout { return cast(inout(_T2)*)&__get_base1()[Ty1Empty ? 0 : 1]; }

        static if (!Ty1Empty)
            _T1 __value1_;
        else
            @property ref inout(_T1) __value1_() inout nothrow @trusted @nogc { return *__get_base1(); }
        static if (!Ty2Empty)
            _T2 __value2_;
        else
            @property ref inout(_T2) __value2_() inout nothrow @trusted @nogc { return *__get_base2(); }
    }
}
version (CppRuntime_GNU)
{
    import core.atomic;

    alias _Atomic_word = int;

    void __atomic_add_dispatch()(_Atomic_word* __mem, int __val) nothrow @nogc @safe
    {
        version (__GTHREADS)
        {
            // TODO: check __gthread_active_p()
//            if (__gthread_active_p())
                __atomic_add(__mem, __val);
//            }
//            else
//            __atomic_add_single(__mem, __val);
        }
        else
            __atomic_add_single(__mem, __val);
    }

    void __atomic_add()(_Atomic_word* __mem, int __val) nothrow @nogc @safe
    {
        atomicFetchAdd!(MemoryOrder.acq_rel)(*__mem, __val);
    }

    void __atomic_add_single()(_Atomic_word* __mem, int __val) nothrow @nogc @safe
    {
        *__mem += __val;
    }

    _Atomic_word __exchange_and_add_dispatch()(_Atomic_word* __mem, int __val) nothrow @nogc @safe
    {
        version (__GTHREADS)
        {
            // TODO: check __gthread_active_p()
            return __exchange_and_add(__mem, __val);

//            if (__gthread_active_p())
//                return __exchange_and_add(__mem, __val);
//            else
//                return __exchange_and_add_single(__mem, __val);
        }
        else
            return __exchange_and_add_single(__mem, __val);
    }

    _Atomic_word __exchange_and_add()(_Atomic_word* __mem, int __val) nothrow @nogc @safe
    {
        return atomicFetchAdd!(MemoryOrder.acq_rel)(*__mem, __val);
    }

    _Atomic_word __exchange_and_add_single()(_Atomic_word* __mem, int __val) nothrow @nogc @safe
    {
        _Atomic_word __result = *__mem;
        *__mem += __val;
        return __result;
    }
}
