#pragma once
/**
* @file mingw.thread.h
* @brief std::thread implementation for MinGW
*
* This file is part of the mingw-w64 runtime package.
* No warranty is given; refer to the file DISCLAIMER within this package.
*/

#ifndef WIN32STDTHREAD_H
#ifndef _GLIBCXX_HAS_GTHREADS

#define WIN32STDTHREAD_H
#define _GLIBCXX_THREAD 1
#define _GLIBCXX_HAS_GTHREADS 1

#include <windows.h>
#include <functional>
#include <memory>
#include <chrono>
#include <system_error>
#include <process.h>

//instead of INVALID_HANDLE_VALUE _beginthreadex returns 0
#define _STD_THREAD_INVALID_HANDLE 0
namespace std
{

class thread
{
    public:
        class id
        {
                DWORD mId;
                void clear() {
                    mId = 0;
                }
                friend class thread;
            public:
                id( DWORD aId = 0 ): mId( aId ) {}
                bool operator==( const id &other ) const {
                    return mId == other.mId;
                }
        };
    protected:
        HANDLE mHandle;
        id mThreadId;
    public:
        typedef HANDLE native_handle_type;
        id get_id() const noexcept {
            return mThreadId;
        }
        native_handle_type native_handle() const {
            return mHandle;
        }
        thread(): mHandle( _STD_THREAD_INVALID_HANDLE ) {}
        thread( thread &other )
            : mHandle( other.mHandle ), mThreadId( other.mThreadId ) {
            other.mHandle = _STD_THREAD_INVALID_HANDLE;
            other.mThreadId.clear();
        }
        template<class Function, class... Args>
        explicit thread( Function &&f, Args &&... args ) {
            typedef decltype( std::bind( f, args... ) ) Call;
            Call *call = new Call( std::bind( f, args... ) );
            mHandle = ( HANDLE )_beginthreadex( NULL, 0, threadfunc<Call>,
                                                ( LPVOID )call, 0, ( unsigned * ) & ( mThreadId.mId ) );
        }
        template <class Call>
        static unsigned int __stdcall threadfunc( void *arg ) {
            std::unique_ptr<Call> upCall( static_cast<Call *>( arg ) );
            ( *upCall )();
            return ( unsigned long )0;
        }
        bool joinable() const {
            return mHandle != _STD_THREAD_INVALID_HANDLE;
        }
        void join() {
            if( get_id() == GetCurrentThreadId() ) {
                throw system_error( EDEADLK, generic_category() );
            }
            if( mHandle == _STD_THREAD_INVALID_HANDLE ) {
                throw system_error( ESRCH, generic_category() );
            }
            if( !joinable() ) {
                throw system_error( EINVAL, generic_category() );
            }
            WaitForSingleObject( mHandle, INFINITE );
            CloseHandle( mHandle );
            mHandle = _STD_THREAD_INVALID_HANDLE;
            mThreadId.clear();
        }

        ~thread() {
            if( joinable() ) {
                std::terminate();
            }
        }
        thread &operator=( const thread & ) = delete;
        thread &operator=( thread &&other ) noexcept {
            if( joinable() ) {
                std::terminate();
            }
            swap( std::forward<thread>( other ) );
            return *this;
        }
        void swap( thread &&other ) noexcept {
            std::swap( mHandle, other.mHandle );
            std::swap( mThreadId.mId, other.mThreadId.mId );
        }
        static unsigned int hardware_concurrency() noexcept {
            return 1;
        }
        void detach() {
            if( !joinable() ) {
                throw system_error();
            }
            mHandle = _STD_THREAD_INVALID_HANDLE;
            mThreadId.clear();
        }
};
namespace this_thread
{
inline thread::id get_id()
{
    return thread::id( GetCurrentThreadId() );
}
inline void yield()
{
    Sleep( 0 );
}
template< class Rep, class Period >
void sleep_for( const std::chrono::duration<Rep, Period> &sleep_duration )
{
    Sleep( chrono::duration_cast<chrono::milliseconds>( sleep_duration ).count() );
}
template <class Clock, class Duration>
void sleep_until( const std::chrono::time_point<Clock, Duration> &sleep_time )
{
    sleep_for( sleep_time - Clock::now() );
}
}

}
#endif // _GLIBCXX_THREAD
#endif // WIN32STDTHREAD_H
