#include <Core/Common/CoreTypes.h>
#include <Core/Common/CoreUtils.h>
#include <Core/HAL/OS/OS.h>
#include <Core/HAL/OS/OS_Private.h>
#include <Core/HAL/Win32.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>

namespace Juliet
{
    namespace
    {
        global RIO_EXTENSION_FUNCTION_TABLE w32_rio_functions = {};
    }

    namespace Memory::Internal
    {
        Byte* OS_Reserve(size_t size)
        {
            auto* result = static_cast<Byte*>(VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE));
            return result;
        }

        bool OS_Commit(Byte* ptr, size_t size)
        {
            bool result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != nullptr);

            Assert(size <= static_cast<size_t>(MaxValueOf<DWORD>()));

            if (w32_rio_functions.RIORegisterBuffer != nullptr && w32_rio_functions.RIODeregisterBuffer != nullptr)
            {
                w32_rio_functions.RIODeregisterBuffer(
                    w32_rio_functions.RIORegisterBuffer(reinterpret_cast<PCHAR>(ptr), static_cast<DWORD>(size)));
            }

            return result;
        }

        void OS_Release(Byte* ptr, size_t size)
        {
            // size not used on windows
            std::ignore = size;

            VirtualFree(ptr, 0, MEM_RELEASE);
        }
    } // namespace Memory::Internal

    namespace Debug::Internal
    {
        bool IsDebuggerPresent()
        {
            return ::IsDebuggerPresent();
        }
    } // namespace Debug::Internal

    namespace
    {
        // Used to handle a crash/exception
        LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS* exceptionPtrs)
        {
            (void)exceptionPtrs;
            // See for more info https://github.com/EpicGamesExt/raddebugger/blob/master/src/os/core/win32/os_core_win32.c
            ExitProcess(1);
        }
    } // namespace

    namespace Internal
    {
        int OS_Main(EntryPointFunc entryPointFunc, int argc, wchar_t** argv)
        {
            SetUnhandledExceptionFilter(&ExceptionFilter);

            // Allow only one instance to be launched.
            CreateMutex(nullptr, false, L"Local\\Juliet.App");
            if (GetLastError() == ERROR_ALREADY_EXISTS)
            {
                MessageBox(nullptr, L"An instance of Juliet is already running.", L"Juliet", MB_OK | MB_ICONEXCLAMATION);
                return EXIT_FAILURE;
            }

            // Create a dummy socket to access RIO functions. Those will be used to do fine memory management
            {
                WSADATA WinSockData;
                WSAStartup(MAKEWORD(2, 2), &WinSockData);
                GUID   guid     = WSAID_MULTIPLE_RIO;
                DWORD  rio_byte = 0;
                SOCKET Sock     = socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
                WSAIoctl(Sock, SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
                         reinterpret_cast<void**>(&w32_rio_functions), sizeof(w32_rio_functions), &rio_byte, nullptr, nullptr);
                closesocket(Sock);
            }

            int result = entryPointFunc(argc, argv);
            return result;
        }
    } // namespace Internal
} // namespace Juliet
