// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.

#include "../include/ThreadEmulation.h"

#include <assert.h>
#include <vector>
#include <set>
#include <map>
#include <mutex>

using namespace std;
using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::System::Threading;

//namespace ThreadEmulation
//{
    // Stored data for CREATE_SUSPENDED and ResumeThread.
    struct PendingThreadInfo
    {
        LPTHREAD_START_ROUTINE lpStartAddress;
        LPVOID lpParameter;
        HANDLE completionEvent;
        int nPriority;
    };

    static map<HANDLE, PendingThreadInfo> pendingThreads;
    static mutex pendingThreadsLock;

    
    // Thread local storage.
    typedef vector<void*> ThreadLocalData;

    static __declspec(thread) ThreadLocalData* currentThreadData = nullptr;
    static set<ThreadLocalData*> allThreadData;
    static DWORD nextTlsIndex = 0;
    static vector<DWORD> freeTlsIndices;
    static mutex tlsAllocationLock;


    // Converts a Win32 thread priority to WinRT format.
    static WorkItemPriority GetWorkItemPriority(int nPriority)
    {
        if (nPriority < 0)
            return WorkItemPriority::Low;
        else if (nPriority > 0)
            return WorkItemPriority::High;
        else
            return WorkItemPriority::Normal;
    }


    // Helper shared between CreateThread and ResumeThread.
    static void StartThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, HANDLE completionEvent, int nPriority)
    {
        auto workItemHandler = ref new WorkItemHandler([=](IAsyncAction^)
        {
            // Run the user callback.
            try
            {
                lpStartAddress(lpParameter);
            }
            catch (...) { }

            // Clean up any TLS allocations made by this thread.
            TlsShutdown();

            // Signal that the thread has completed.
            SetEvent(completionEvent);
            CloseHandle(completionEvent);

        }, CallbackContext::Any);

        ThreadPool::RunAsync(workItemHandler, GetWorkItemPriority(nPriority), WorkItemOptions::TimeSliced);
    }


    _Use_decl_annotations_ HANDLE WINAPI CreateThreadRT(LPSECURITY_ATTRIBUTES unusedThreadAttributes, SIZE_T unusedStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD unusedThreadId)
    {
        // Validate parameters.
        assert(unusedThreadAttributes == nullptr);
        assert(unusedStackSize == 0);
        assert((dwCreationFlags & ~CREATE_SUSPENDED) == 0);
        assert(unusedThreadId == nullptr);

        // Create a handle that will be signalled when the thread has completed.
        HANDLE threadHandle = CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);

        if (!threadHandle)
            return nullptr;

        // Make a copy of the handle for internal use. This is necessary because
        // the caller is responsible for closing the handle returned by CreateThread,
        // and they may do that before or after the thread has finished running.
        HANDLE completionEvent;
        
        if (!DuplicateHandle(GetCurrentProcess(), threadHandle, GetCurrentProcess(), &completionEvent, 0, false, DUPLICATE_SAME_ACCESS))
        {
            CloseHandle(threadHandle);
            return nullptr;
        }

        try
        {
            if (dwCreationFlags & CREATE_SUSPENDED)
            {
                // Store info about a suspended thread.
                PendingThreadInfo info;

                info.lpStartAddress = lpStartAddress;
                info.lpParameter = lpParameter;
                info.completionEvent = completionEvent;
                info.nPriority = 0;

                lock_guard<mutex> lock(pendingThreadsLock);

                pendingThreads[threadHandle] = info;
            }
            else
            {
                // Start the thread immediately.
                StartThread(lpStartAddress, lpParameter, completionEvent, 0);
            }
    
            return threadHandle;
        }
        catch (...)
        {
            // Clean up if thread creation fails.
            CloseHandle(threadHandle);
            CloseHandle(completionEvent);

            return nullptr;
        }
    }


    _Use_decl_annotations_ DWORD WINAPI ResumeThreadRT(HANDLE hThread)
    {
        lock_guard<mutex> lock(pendingThreadsLock);

        // Look up the requested thread.
        auto threadInfo = pendingThreads.find(hThread);

        if (threadInfo == pendingThreads.end())
        {
            // Can only resume threads while they are in CREATE_SUSPENDED state.
            assert(false);
            return (DWORD)-1;
        }

        // Start the thread.
        try
        {
            PendingThreadInfo& info = threadInfo->second;

            StartThread(info.lpStartAddress, info.lpParameter, info.completionEvent, info.nPriority);
        }
        catch (...)
        {
            return (DWORD)-1;
        }

        // Remove this thread from the pending list.
        pendingThreads.erase(threadInfo);

        return 0;
    }


    _Use_decl_annotations_ BOOL WINAPI SetThreadPriorityRT(HANDLE hThread, int nPriority)
    {
        lock_guard<mutex> lock(pendingThreadsLock);

        // Look up the requested thread.
        auto threadInfo = pendingThreads.find(hThread);

        if (threadInfo == pendingThreads.end())
        {
            // Can only set priority on threads while they are in CREATE_SUSPENDED state.
            assert(false);
            return false;
        }

        // Store the new priority.
        threadInfo->second.nPriority = nPriority;

        return true;
    }


    _Use_decl_annotations_ VOID WINAPI SleepRT(DWORD dwMilliseconds)
    {
        static HANDLE singletonEvent = nullptr;

        HANDLE sleepEvent = singletonEvent;

        // Demand create the event.
        if (!sleepEvent)
        {
            sleepEvent = CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);

            if (!sleepEvent)
                return;

            HANDLE previousEvent = InterlockedCompareExchangePointerRelease(&singletonEvent, sleepEvent, nullptr);
            
            if (previousEvent)
            {
                // Back out if multiple threads try to demand create at the same time.
                CloseHandle(sleepEvent);
                sleepEvent = previousEvent;
            }
        }

        // Emulate sleep by waiting with timeout on an event that is never signalled.
        WaitForSingleObjectEx(sleepEvent, dwMilliseconds, false);
    }


    DWORD WINAPI TlsAllocRT()
    {
        lock_guard<mutex> lock(tlsAllocationLock);
        
        // Can we reuse a previously freed TLS slot?
        if (!freeTlsIndices.empty())
        {
            DWORD result = freeTlsIndices.back();
            freeTlsIndices.pop_back();
            return result;
        }

        // Allocate a new TLS slot.
        return nextTlsIndex++;
    }


    _Use_decl_annotations_ BOOL WINAPI TlsFreeRT(DWORD dwTlsIndex)
    {
        lock_guard<mutex> lock(tlsAllocationLock);

        assert(dwTlsIndex < nextTlsIndex);
        assert(find(freeTlsIndices.begin(), freeTlsIndices.end(), dwTlsIndex) == freeTlsIndices.end());

        // Store this slot for reuse by TlsAlloc.
        try
        {
            freeTlsIndices.push_back(dwTlsIndex);
        }
        catch (...)
        {
            return false;
        }

        // Zero the value for all threads that might be using this now freed slot.
        for each (auto threadData in allThreadData)
        {
            if (threadData->size() > dwTlsIndex)
            {
                threadData->at(dwTlsIndex) = nullptr;
            }
        }

        return true;
    }


    _Use_decl_annotations_ LPVOID WINAPI TlsGetValueRT(DWORD dwTlsIndex)
    {
        ThreadLocalData* threadData = currentThreadData;

        if (threadData && threadData->size() > dwTlsIndex)
        {
            // Return the value of an allocated TLS slot.
            return threadData->at(dwTlsIndex);
        }
        else
        {
            // Default value for unallocated slots.
            return nullptr;
        }
    }


    _Use_decl_annotations_ BOOL WINAPI TlsSetValueRT(DWORD dwTlsIndex, LPVOID lpTlsValue)
    {
        ThreadLocalData* threadData = currentThreadData;

        if (!threadData)
        {
            // First time allocation of TLS data for this thread.
            try
            {
                threadData = new ThreadLocalData(dwTlsIndex + 1, nullptr);
                
                lock_guard<mutex> lock(tlsAllocationLock);

                allThreadData.insert(threadData);

                currentThreadData = threadData;
            }
            catch (...)
            {
                if (threadData)
                    delete threadData;

                return false;
            }
        }
        else if (threadData->size() <= dwTlsIndex)
        {
            // This thread already has a TLS data block, but it must be expanded to fit the specified slot.
            try
            {
                lock_guard<mutex> lock(tlsAllocationLock);

                threadData->resize(dwTlsIndex + 1, nullptr);
            }
            catch (...)
            {
                return false;
            }
        }

        // Store the new value for this slot.
        threadData->at(dwTlsIndex) = lpTlsValue;

        return true;
    }


    // Called at thread exit to clean up TLS allocations.
    void WINAPI TlsShutdown()
    {
        ThreadLocalData* threadData = currentThreadData;

        if (threadData)
        {
            {
                lock_guard<mutex> lock(tlsAllocationLock);

                allThreadData.erase(threadData);
            }

            currentThreadData = nullptr;

            delete threadData;
        }
    }
//}
