// Copyright (c) Wojciech Figat. All rights reserved.

#if PLATFORM_WEB

#include "WebPlatform.h"
#include "WebThread.h"
#include "WebFileSystem.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/Version.h"
#include "Engine/Core/Types/Guid.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Platform/CPUInfo.h"
#include "Engine/Platform/MemoryStats.h"
#include "Engine/Platform/MessageBox.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/Web/WebGame.h"
#include "Engine/Utilities/StringConverter.h"
#include <chrono>
#include <dlfcn.h>
#include <unistd.h>
#include <emscripten/emscripten.h>
#include <emscripten/threading.h>
#include <emscripten/emmalloc.h>
#include <emscripten/version.h>
#include <emscripten/heap.h>

namespace
{
    CPUInfo Cpu;
    double DateStart = emscripten_get_now();
};

void WebGame::InitMainWindowSettings(CreateWindowSettings& settings)
{
    // Set window size matching the canvas item in HTML
    settings.Fullscreen = false;
    int width = 1, height = 1;
    emscripten_get_canvas_element_size(WEB_CANVAS_ID, &width, &height);
    settings.Size.X = width;
    settings.Size.Y = height;
}

DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon)
{
    StringAnsi textAnsi(text);
    StringAnsi captionAnsi(caption);
    EM_ASM({ alert(UTF8ToString($0) + "\n\n" + UTF8ToString($1)); }, captionAnsi.Get(), textAnsi.Get());
    return DialogResult::None;
}

WebThread* WebThread::Create(IRunnable* runnable, const String& name, ThreadPriority priority, uint32 stackSize)
{
#ifdef __EMSCRIPTEN_PTHREADS__
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    if (stackSize != 0)
        pthread_attr_setstacksize(&attr, stackSize);
    auto thread = New<WebThread>(runnable, name, priority);
    const int result = pthread_create(&thread->_thread, &attr, ThreadProc, thread);
    if (result != 0)
    {
        LOG(Warning, "Failed to spawn a thread. Result code: {0}", result);
        Delete(thread);
        return nullptr;
    }
    return thread;
#else
    LOG(Fatal, "Threading is not supported.");
    return nullptr;
#endif
}

void WebFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result)
{
    result = TEXT("/");
}

#if 0

void* WebPlatform::Allocate(uint64 size, uint64 alignment)
{
    void* ptr = nullptr;
    if (alignment && size)
    {
        // Alignment always has to be power of two
        ASSERT_LOW_LAYER((alignment & (alignment - 1)) == 0);
        ptr = emscripten_builtin_memalign(alignment, size);
        if (!ptr)
            OutOfMemory();
#if COMPILE_WITH_PROFILER
        OnMemoryAlloc(ptr, size);
#endif
    }
    return ptr;
}

void WebPlatform::Free(void* ptr)
{
    if (ptr)
    {
#if COMPILE_WITH_PROFILER
        OnMemoryFree(ptr);
#endif
        emscripten_builtin_free(ptr);
    }
}

#endif

String WebPlatform::GetSystemName()
{
    return TEXT("Browser");
}

Version WebPlatform::GetSystemVersion()
{
    return Version(1, 0);
}

CPUInfo WebPlatform::GetCPUInfo()
{
    return Cpu;
}

MemoryStats WebPlatform::GetMemoryStats()
{
    MemoryStats result;
    result.TotalPhysicalMemory = emscripten_get_heap_max();
    result.UsedPhysicalMemory = emscripten_get_heap_size();
    result.TotalVirtualMemory = 2ull * 1024 * 1024 * 1024; // Max 2GB
    result.UsedVirtualMemory = result.UsedPhysicalMemory;
    result.ProgramSizeMemory = 0;
    return result;
}

ProcessMemoryStats WebPlatform::GetProcessMemoryStats()
{
    // Mock memory stats
    ProcessMemoryStats result;
    result.UsedPhysicalMemory = 1 * 1024 * 1024;
    result.UsedVirtualMemory = result.UsedPhysicalMemory;
    return result;
}

void WebPlatform::SetThreadPriority(ThreadPriority priority)
{
    // Not supported
}

void WebPlatform::SetThreadAffinityMask(uint64 affinityMask)
{
    // Not supported
}

void WebPlatform::Sleep(int32 milliseconds)
{
    emscripten_thread_sleep(milliseconds);
}

void WebPlatform::Yield()
{
    Sleep(0);
}

double WebPlatform::GetTimeSeconds()
{
    double time = emscripten_get_now();
    return time * 0.001;
}

uint64 WebPlatform::GetTimeCycles()
{
    return (uint64)(emscripten_get_now() * 1000.0);
}

void WebPlatform::GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond)
{
    // Get local time
    using namespace std::chrono;
    system_clock::time_point now = system_clock::now();
    time_t tt = system_clock::to_time_t(now);
    tm time = *localtime(&tt);

    // Extract time
    year = time.tm_year + 1900;
    month = time.tm_mon + 1;
    dayOfWeek = time.tm_wday;
    day = time.tm_mday;
    hour = time.tm_hour;
    minute = time.tm_min;
    second = time.tm_sec;
    millisecond = (int64)(emscripten_get_now() - DateStart) % 1000; // Fake it based on other timer
}

void WebPlatform::GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond)
{
    // Get UTC time
    using namespace std::chrono;
    system_clock::time_point now = system_clock::now();
    time_t tt = system_clock::to_time_t(now);
    tm time = *gmtime(&tt);

    // Extract time
    year = time.tm_year + 1900;
    month = time.tm_mon + 1;
    dayOfWeek = time.tm_wday;
    day = time.tm_mday;
    hour = time.tm_hour;
    minute = time.tm_min;
    second = time.tm_sec;
    millisecond = (int64)(emscripten_get_now() - DateStart) % 1000; // Fake it based on other timer
}

void WebPlatform::Log(const StringView& msg, int32 logType)
{
    const StringAsANSI<512> msgAnsi(*msg, msg.Length());

    // Fix % characters that should not be formatted
    auto buffer = (char*)msgAnsi.Get();
    for (int32 i = 0; buffer[i]; i++)
    {
        if (buffer[i] == '%')
            buffer[i] = 'p';
    }

    int flags = EM_LOG_CONSOLE;
    if (logType == (int32)LogType::Warning)
        flags |= EM_LOG_WARN;
    if (logType == (int32)LogType::Error)
        flags |= EM_LOG_ERROR;
    emscripten_log(flags, buffer);
}

String WebPlatform::GetComputerName()
{
    return TEXT("Web");
}

bool WebPlatform::GetHasFocus()
{
    return true;
}

String WebPlatform::GetMainDirectory()
{
    return TEXT("/");
}

String WebPlatform::GetExecutableFilePath()
{
    return TEXT("/index.html");
}

Guid WebPlatform::GetUniqueDeviceId()
{
    return Guid(1, 2, 3, 4);
}

String WebPlatform::GetWorkingDirectory()
{
    return GetMainDirectory();
}

bool WebPlatform::SetWorkingDirectory(const String& path)
{
    return true;
}

bool WebPlatform::Init()
{
    if (PlatformBase::Init())
        return true;

#if COMPILE_WITH_PROFILER
    // Setup platform-specific memory profiler tags
    ProfilerMemory::RenameGroup(ProfilerMemory::Groups::WEB_MEM_TAG_HEAP_SIZE, TEXT("Emscripten/HeapSize"));
    ProfilerMemory::RenameGroup(ProfilerMemory::Groups::WEB_MEM_TAG_HEAP_MAX, TEXT("Emscripten/HeapMax"));
#endif

    // Set info about the CPU
    Platform::MemoryClear(&Cpu, sizeof(Cpu));
    Cpu.ProcessorPackageCount = 1;
    Cpu.ProcessorCoreCount = Math::Min(emscripten_num_logical_cores(), PLATFORM_THREADS_LIMIT);
    Cpu.LogicalProcessorCount = Cpu.ProcessorCoreCount;
    Cpu.ClockSpeed = GetClockFrequency();

    return false;
}

void WebPlatform::LogInfo()
{
    PlatformBase::LogInfo();

#ifdef __EMSCRIPTEN_major__
    LOG(Info, "Emscripten {}.{}.{}", __EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__);
#elif defined(__EMSCRIPTEN_MAJOR__)
    LOG(Info, "Emscripten {}.{}.{}", __EMSCRIPTEN_MAJOR__, __EMSCRIPTEN_MINOR__, __EMSCRIPTEN_TINY__);
#else
    LOG(Info, "Emscripten");
#endif
#ifdef __EMSCRIPTEN_PTHREADS__
    LOG(Info, "Threading: pthreads");
#else
    LOG(Info, "Threading: disabled");
#endif
}

void WebPlatform::Tick()
{
#if COMPILE_WITH_PROFILER
    ProfilerMemory::OnGroupSet(ProfilerMemory::Groups::WEB_MEM_TAG_HEAP_SIZE, (int64)emscripten_get_heap_size(), 1);
    ProfilerMemory::OnGroupSet(ProfilerMemory::Groups::WEB_MEM_TAG_HEAP_MAX, (int64)emscripten_get_heap_max(), 1);
#endif
}

void WebPlatform::Exit()
{
    // Exit runtime
    emscripten_cancel_main_loop();
    emscripten_force_exit(Engine::ExitCode);
}

extern char** environ;

void WebPlatform::GetEnvironmentVariables(Dictionary<String, String, HeapAllocation>& result)
{
    char** s = environ;
    for (; *s; s++)
    {
        char* var = *s;
        int32 split = -1;
        for (int32 i = 0; var[i]; i++)
        {
            if (var[i] == '=')
            {
                split = i;
                break;
            }
        }
        if (split == -1)
            result[String(var)] = String::Empty;
        else
            result[String(var, split)] = String(var + split + 1);
    }
}

bool WebPlatform::GetEnvironmentVariable(const String& name, String& value)
{
    char* env = getenv(StringAsANSI<>(*name).Get());
    if (env)
    {
        value = String(env);
        return false;
    }
    return true;
}

bool WebPlatform::SetEnvironmentVariable(const String& name, const String& value)
{
    return setenv(StringAsANSI<>(*name).Get(), StringAsANSI<>(*value).Get(), true) != 0;
}

void* WebPlatform::LoadLibrary(const Char* filename)
{
    PROFILE_CPU();
    ZoneText(filename, StringUtils::Length(filename));
    const StringAsANSI<> filenameANSI(filename);
    void* result = dlopen(filenameANSI.Get(), RTLD_NOW);
    if (!result)
    {
        LOG(Error, "Failed to load {0} because {1}", filename, String(dlerror()));
    }
    return result;
}

void WebPlatform::FreeLibrary(void* handle)
{
    dlclose(handle);
}

void* WebPlatform::GetProcAddress(void* handle, const char* symbol)
{
    return dlsym(handle, symbol);
}

Array<PlatformBase::StackFrame> WebPlatform::GetStackFrames(int32 skipCount, int32 maxDepth, void* context)
{
    Array<StackFrame> result;
#if CRASH_LOG_ENABLE
    // Get callstack
    char callstack[2000];
    emscripten_get_callstack(EM_LOG_JS_STACK, callstack, sizeof(callstack));

    // Parse callstack
    int32 pos = 0;
    while (callstack[pos])
    {
        StackFrame& frame = result.AddOne();
        frame.ProgramCounter = 0;
        frame.ModuleName[0] = 0;
        frame.FileName[0] = 0;
        frame.LineNumber = 0;

        // Skip leading spaces
        while (callstack[pos] == ' ')
            pos++;

        // Skip 'at '
        pos += 3;

        // Read function name
        int32 dst = 0;
        while (callstack[pos] && callstack[pos] != '\n' && dst < ARRAY_COUNT(frame.FileName) - 1)
            frame.FunctionName[dst++] = callstack[pos++];
        frame.FunctionName[dst] = 0;

        // Skip '\n'
        if (callstack[pos])
            pos++;
    }

    // Adjust frames range
    skipCount += 2; // Skip this function and utility JS frames
    while (skipCount-- && result.HasItems())
        result.RemoveAtKeepOrder(0);
    if (result.Count() > maxDepth)
        result.Resize(maxDepth);
#endif
    return result;
}

#if 1
// Fix linker errors when using '-sAUTO_JS_LIBRARIES=0' due to unused WebGL functions (referenced by SDL3 port itself from emscripten: SDL_emscriptenopengles.c)
extern "C" EMSCRIPTEN_WEBGL_CONTEXT_HANDLE emscripten_webgl_create_context(const char* target, const EmscriptenWebGLContextAttributes* attributes)
{
    return 0;
}

extern "C" EMSCRIPTEN_RESULT emscripten_webgl_make_context_current(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
{
    return 0;
}

extern "C" EMSCRIPTEN_RESULT emscripten_webgl_destroy_context(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
{
    return 0;
}

extern "C" void* emscripten_webgl_get_proc_address(const char* name)
{
    return nullptr;
}

extern "C" void emscripten_webgl_init_context_attributes(EmscriptenWebGLContextAttributes* attributes)
{
}

// Reference: emscripten\system\lib\html5\dom_pk_codes.c
#include <emscripten/dom_pk_codes.h>
extern "C" DOM_PK_CODE_TYPE emscripten_compute_dom_pk_code(const char* keyCodeString)
{
    if (!keyCodeString) return 0;

    /* Compute the collision free hash. */
    unsigned int hash = 0;
    while (*keyCodeString) hash = ((hash ^ 0x7E057D79U) << 3) ^ (unsigned int)*keyCodeString++;

    /*
     * Don't expose the hash values out to the application, but map to fixed IDs.
     * This is useful for mapping back codes to MDN documentation page at
     *
     *   https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
     */
    switch (hash) {
    case 0x98051284U /* Unidentified       */: return DOM_PK_UNKNOWN;              /* 0x0000 */
    case 0x67243A2DU /* Escape             */: return DOM_PK_ESCAPE;               /* 0x0001 */
    case 0x67251058U /* Digit0             */: return DOM_PK_0;                    /* 0x0002 */
    case 0x67251059U /* Digit1             */: return DOM_PK_1;                    /* 0x0003 */
    case 0x6725105AU /* Digit2             */: return DOM_PK_2;                    /* 0x0004 */
    case 0x6725105BU /* Digit3             */: return DOM_PK_3;                    /* 0x0005 */
    case 0x6725105CU /* Digit4             */: return DOM_PK_4;                    /* 0x0006 */
    case 0x6725105DU /* Digit5             */: return DOM_PK_5;                    /* 0x0007 */
    case 0x6725105EU /* Digit6             */: return DOM_PK_6;                    /* 0x0008 */
    case 0x6725105FU /* Digit7             */: return DOM_PK_7;                    /* 0x0009 */
    case 0x67251050U /* Digit8             */: return DOM_PK_8;                    /* 0x000A */
    case 0x67251051U /* Digit9             */: return DOM_PK_9;                    /* 0x000B */
    case 0x92E14DD3U /* Minus              */: return DOM_PK_MINUS;                /* 0x000C */
    case 0x92E1FBACU /* Equal              */: return DOM_PK_EQUAL;                /* 0x000D */
    case 0x36BF1CB5U /* Backspace          */: return DOM_PK_BACKSPACE;            /* 0x000E */
    case 0x7B8E51E2U /* Tab                */: return DOM_PK_TAB;                  /* 0x000F */
    case 0x2C595B51U /* KeyQ               */: return DOM_PK_Q;                    /* 0x0010 */
    case 0x2C595B57U /* KeyW               */: return DOM_PK_W;                    /* 0x0011 */
    case 0x2C595B45U /* KeyE               */: return DOM_PK_E;                    /* 0x0012 */
    case 0x2C595B52U /* KeyR               */: return DOM_PK_R;                    /* 0x0013 */
    case 0x2C595B54U /* KeyT               */: return DOM_PK_T;                    /* 0x0014 */
    case 0x2C595B59U /* KeyY               */: return DOM_PK_Y;                    /* 0x0015 */
    case 0x2C595B55U /* KeyU               */: return DOM_PK_U;                    /* 0x0016 */
    case 0x2C595B49U /* KeyI               */: return DOM_PK_I;                    /* 0x0017 */
    case 0x2C595B4FU /* KeyO               */: return DOM_PK_O;                    /* 0x0018 */
    case 0x2C595B50U /* KeyP               */: return DOM_PK_P;                    /* 0x0019 */
    case 0x45D8158CU /* BracketLeft        */: return DOM_PK_BRACKET_LEFT;         /* 0x001A */
    case 0xDEEABF7CU /* BracketRight       */: return DOM_PK_BRACKET_RIGHT;        /* 0x001B */
    case 0x92E1C5D2U /* Enter              */: return DOM_PK_ENTER;                /* 0x001C */
    case 0xE058958CU /* ControlLeft        */: return DOM_PK_CONTROL_LEFT;         /* 0x001D */
    case 0x2C595B41U /* KeyA               */: return DOM_PK_A;                    /* 0x001E */
    case 0x2C595B53U /* KeyS               */: return DOM_PK_S;                    /* 0x001F */
    case 0x2C595B44U /* KeyD               */: return DOM_PK_D;                    /* 0x0020 */
    case 0x2C595B46U /* KeyF               */: return DOM_PK_F;                    /* 0x0021 */
    case 0x2C595B47U /* KeyG               */: return DOM_PK_G;                    /* 0x0022 */
    case 0x2C595B48U /* KeyH               */: return DOM_PK_H;                    /* 0x0023 */
    case 0x2C595B4AU /* KeyJ               */: return DOM_PK_J;                    /* 0x0024 */
    case 0x2C595B4BU /* KeyK               */: return DOM_PK_K;                    /* 0x0025 */
    case 0x2C595B4CU /* KeyL               */: return DOM_PK_L;                    /* 0x0026 */
    case 0x2707219EU /* Semicolon          */: return DOM_PK_SEMICOLON;            /* 0x0027 */
    case 0x92E0B58DU /* Quote              */: return DOM_PK_QUOTE;                /* 0x0028 */
    case 0x36BF358DU /* Backquote          */: return DOM_PK_BACKQUOTE;            /* 0x0029 */
    case 0x26B1958CU /* ShiftLeft          */: return DOM_PK_SHIFT_LEFT;           /* 0x002A */
    case 0x36BF2438U /* Backslash          */: return DOM_PK_BACKSLASH;            /* 0x002B */
    case 0x2C595B5AU /* KeyZ               */: return DOM_PK_Z;                    /* 0x002C */
    case 0x2C595B58U /* KeyX               */: return DOM_PK_X;                    /* 0x002D */
    case 0x2C595B43U /* KeyC               */: return DOM_PK_C;                    /* 0x002E */
    case 0x2C595B56U /* KeyV               */: return DOM_PK_V;                    /* 0x002F */
    case 0x2C595B42U /* KeyB               */: return DOM_PK_B;                    /* 0x0030 */
    case 0x2C595B4EU /* KeyN               */: return DOM_PK_N;                    /* 0x0031 */
    case 0x2C595B4DU /* KeyM               */: return DOM_PK_M;                    /* 0x0032 */
    case 0x92E1A1C1U /* Comma              */: return DOM_PK_COMMA;                /* 0x0033 */
    case 0x672FFAD4U /* Period             */: return DOM_PK_PERIOD;               /* 0x0034 */
    case 0x92E0A438U /* Slash              */: return DOM_PK_SLASH;                /* 0x0035 */
    case 0xC5A6BF7CU /* ShiftRight         */: return DOM_PK_SHIFT_RIGHT;          /* 0x0036 */
    case 0x5D64DA91U /* NumpadMultiply     */: return DOM_PK_NUMPAD_MULTIPLY;      /* 0x0037 */
    case 0xC914958CU /* AltLeft            */: return DOM_PK_ALT_LEFT;             /* 0x0038 */
    case 0x92E09CB5U /* Space              */: return DOM_PK_SPACE;                /* 0x0039 */
    case 0xB8FAE73BU /* CapsLock           */: return DOM_PK_CAPS_LOCK;            /* 0x003A */
    case 0x7174B789U /* F1                 */: return DOM_PK_F1;                   /* 0x003B */
    case 0x7174B78AU /* F2                 */: return DOM_PK_F2;                   /* 0x003C */
    case 0x7174B78BU /* F3                 */: return DOM_PK_F3;                   /* 0x003D */
    case 0x7174B78CU /* F4                 */: return DOM_PK_F4;                   /* 0x003E */
    case 0x7174B78DU /* F5                 */: return DOM_PK_F5;                   /* 0x003F */
    case 0x7174B78EU /* F6                 */: return DOM_PK_F6;                   /* 0x0040 */
    case 0x7174B78FU /* F7                 */: return DOM_PK_F7;                   /* 0x0041 */
    case 0x7174B780U /* F8                 */: return DOM_PK_F8;                   /* 0x0042 */
    case 0x7174B781U /* F9                 */: return DOM_PK_F9;                   /* 0x0043 */
    case 0x7B8E57B0U /* F10                */: return DOM_PK_F10;                  /* 0x0044 */
    case 0x92E08B35U /* Pause              */: return DOM_PK_PAUSE;                /* 0x0045 */
    case 0xCDED173BU /* ScrollLock         */: return DOM_PK_SCROLL_LOCK;          /* 0x0046 */
    case 0xC925FCDFU /* Numpad7            */: return DOM_PK_NUMPAD_7;             /* 0x0047 */
    case 0xC925FCD0U /* Numpad8            */: return DOM_PK_NUMPAD_8;             /* 0x0048 */
    case 0xC925FCD1U /* Numpad9            */: return DOM_PK_NUMPAD_9;             /* 0x0049 */
    case 0x5EA3E8A4U /* NumpadSubtract     */: return DOM_PK_NUMPAD_SUBTRACT;      /* 0x004A */
    case 0xC925FCDCU /* Numpad4            */: return DOM_PK_NUMPAD_4;             /* 0x004B */
    case 0xC925FCDDU /* Numpad5            */: return DOM_PK_NUMPAD_5;             /* 0x004C */
    case 0xC925FCDEU /* Numpad6            */: return DOM_PK_NUMPAD_6;             /* 0x004D */
    case 0x380B9C8CU /* NumpadAdd          */: return DOM_PK_NUMPAD_ADD;           /* 0x004E */
    case 0xC925FCD9U /* Numpad1            */: return DOM_PK_NUMPAD_1;             /* 0x004F */
    case 0xC925FCDAU /* Numpad2            */: return DOM_PK_NUMPAD_2;             /* 0x0050 */
    case 0xC925FCDBU /* Numpad3            */: return DOM_PK_NUMPAD_3;             /* 0x0051 */
    case 0xC925FCD8U /* Numpad0            */: return DOM_PK_NUMPAD_0;             /* 0x0052 */
    case 0x95852DACU /* NumpadDecimal      */: return DOM_PK_NUMPAD_DECIMAL;       /* 0x0053 */
    case 0xCC1E198EU /* PrintScreen        */: return DOM_PK_PRINT_SCREEN;         /* 0x0054 */
    case 0x16BF2438U /* IntlBackslash      */: return DOM_PK_INTL_BACKSLASH;       /* 0x0056 */
    case 0x7B8E57B1U /* F11                */: return DOM_PK_F11;                  /* 0x0057 */
    case 0x7B8E57B2U /* F12                */: return DOM_PK_F12;                  /* 0x0058 */
    case 0x7393FBACU /* NumpadEqual        */: return DOM_PK_NUMPAD_EQUAL;         /* 0x0059 */
    case 0x7B8E57B3U /* F13                */: return DOM_PK_F13;                  /* 0x0064 */
    case 0x7B8E57B4U /* F14                */: return DOM_PK_F14;                  /* 0x0065 */
    case 0x7B8E57B5U /* F15                */: return DOM_PK_F15;                  /* 0x0066 */
    case 0x7B8E57B6U /* F16                */: return DOM_PK_F16;                  /* 0x0067 */
    case 0x7B8E57B7U /* F17                */: return DOM_PK_F17;                  /* 0x0068 */
    case 0x7B8E57B8U /* F18                */: return DOM_PK_F18;                  /* 0x0069 */
    case 0x7B8E57B9U /* F19                */: return DOM_PK_F19;                  /* 0x006A */
    case 0x7B8E57A8U /* F20                */: return DOM_PK_F20;                  /* 0x006B */
    case 0x7B8E57A9U /* F21                */: return DOM_PK_F21;                  /* 0x006C */
    case 0x7B8E57AAU /* F22                */: return DOM_PK_F22;                  /* 0x006D */
    case 0x7B8E57ABU /* F23                */: return DOM_PK_F23;                  /* 0x006E */
    case 0xB9F4C50DU /* KanaMode           */: return DOM_PK_KANA_MODE;            /* 0x0070 */
    case 0x92E14D02U /* Lang2              */: return DOM_PK_LANG_2;               /* 0x0071 */
    case 0x92E14D01U /* Lang1              */: return DOM_PK_LANG_1;               /* 0x0072 */
    case 0x6723C677U /* IntlRo             */: return DOM_PK_INTL_RO;              /* 0x0073 */
    case 0x7B8E57ACU /* F24                */: return DOM_PK_F24;                  /* 0x0076 */
    case 0xC91CC12CU /* Convert            */: return DOM_PK_CONVERT;              /* 0x0079 */
    case 0x2ADCC12CU /* NonConvert         */: return DOM_PK_NON_CONVERT;          /* 0x007B */
    case 0xC935DA8EU /* IntlYen            */: return DOM_PK_INTL_YEN;             /* 0x007D */
    case 0x7393A1C1U /* NumpadComma        */: return DOM_PK_NUMPAD_COMMA;         /* 0x007E */
    case 0x92E08A8DU /* Paste              */: return DOM_PK_PASTE;                /* 0xE00A */
    case 0x01DC7D93U /* MediaTrackPrevious */: return DOM_PK_MEDIA_TRACK_PREVIOUS; /* 0xE010 */
    case 0x7B8E5494U /* Cut                */: return DOM_PK_CUT;                  /* 0xE017 */
    case 0x2C5949B1U /* Copy               */: return DOM_PK_COPY;                 /* 0xE018 */
    case 0x2AD2E17CU /* MediaTrackNext     */: return DOM_PK_MEDIA_TRACK_NEXT;     /* 0xE019 */
    case 0x7393C5D2U /* NumpadEnter        */: return DOM_PK_NUMPAD_ENTER;         /* 0xE01C */
    case 0xF2EEBF7CU /* ControlRight       */: return DOM_PK_CONTROL_RIGHT;        /* 0xE01D */
    case 0x2A45030DU /* AudioVolumeMute    */: return DOM_PK_AUDIO_VOLUME_MUTE;    /* 0xE020 */
    case 0xEA45030DU /* VolumeMute         */: return DOM_PK_AUDIO_VOLUME_MUTE;    /* 0xE020 */
    case 0x370ECA3AU /* LaunchApp2         */: return DOM_PK_LAUNCH_APP_2;         /* 0xE021 */
    case 0x2D1C0B35U /* MediaPlayPause     */: return DOM_PK_MEDIA_PLAY_PAUSE;     /* 0xE022 */
    case 0x39237F80U /* MediaStop          */: return DOM_PK_MEDIA_STOP;           /* 0xE024 */
    case 0x92E1C9A4U /* Eject              */: return DOM_PK_EJECT;                /* 0xE02C */
    case 0x2A45179EU /* AudioVolumeDown    */: return DOM_PK_AUDIO_VOLUME_DOWN;    /* 0xE02E */
    case 0xEA45179EU /* VolumeDown         */: return DOM_PK_AUDIO_VOLUME_DOWN;    /* 0xE02E */
    case 0x156CC610U /* AudioVolumeUp      */: return DOM_PK_AUDIO_VOLUME_UP;      /* 0xE030 */
    case 0xBA6CC610U /* VolumeUp           */: return DOM_PK_AUDIO_VOLUME_UP;      /* 0xE030 */
    case 0x49387F45U /* BrowserHome        */: return DOM_PK_BROWSER_HOME;         /* 0xE032 */
    case 0x6CB5328DU /* NumpadDivide       */: return DOM_PK_NUMPAD_DIVIDE;        /* 0xE035 */
    case 0xB88EBF7CU /* AltRight           */: return DOM_PK_ALT_RIGHT;            /* 0xE038 */
    case 0x2C595DD8U /* Help               */: return DOM_PK_HELP;                 /* 0xE03B */
    case 0xC925873BU /* NumLock            */: return DOM_PK_NUM_LOCK;             /* 0xE045 */
    case 0x2C595F45U /* Home               */: return DOM_PK_HOME;                 /* 0xE047 */
    case 0xC91BB690U /* ArrowUp            */: return DOM_PK_ARROW_UP;             /* 0xE048 */
    case 0x672F9210U /* PageUp             */: return DOM_PK_PAGE_UP;              /* 0xE049 */
    case 0x3799258CU /* ArrowLeft          */: return DOM_PK_ARROW_LEFT;           /* 0xE04B */
    case 0x4CE33F7CU /* ArrowRight         */: return DOM_PK_ARROW_RIGHT;          /* 0xE04D */
    case 0x7B8E55DCU /* End                */: return DOM_PK_END;                  /* 0xE04F */
    case 0x3799379EU /* ArrowDown          */: return DOM_PK_ARROW_DOWN;           /* 0xE050 */
    case 0xBA90179EU /* PageDown           */: return DOM_PK_PAGE_DOWN;            /* 0xE051 */
    case 0x6723CB2CU /* Insert             */: return DOM_PK_INSERT;               /* 0xE052 */
    case 0x6725C50DU /* Delete             */: return DOM_PK_DELETE;               /* 0xE053 */
    case 0xB929C58CU /* MetaLeft           */: return DOM_PK_META_LEFT;            /* 0xE05B */
    case 0x6723658CU /* OSLeft             */: return DOM_PK_OS_LEFT;              /* 0xE05B */
    case 0x39643F7CU /* MetaRight          */: return DOM_PK_META_RIGHT;           /* 0xE05C */
    case 0xC9313F7CU /* OSRight            */: return DOM_PK_OS_RIGHT;             /* 0xE05C */
    case 0xE00E97CDU /* ContextMenu        */: return DOM_PK_CONTEXT_MENU;         /* 0xE05D */
    case 0x92E09712U /* Power              */: return DOM_PK_POWER;                /* 0xE05E */
    case 0x3F665A78U /* BrowserSearch      */: return DOM_PK_BROWSER_SEARCH;       /* 0xE065 */
    case 0xA2E93BD3U /* BrowserFavorites   */: return DOM_PK_BROWSER_FAVORITES;    /* 0xE066 */
    case 0x0B1D4938U /* BrowserRefresh     */: return DOM_PK_BROWSER_REFRESH;      /* 0xE067 */
    case 0x49384F80U /* BrowserStop        */: return DOM_PK_BROWSER_STOP;         /* 0xE068 */
    case 0x0B49023CU /* BrowserForward     */: return DOM_PK_BROWSER_FORWARD;      /* 0xE069 */
    case 0x493868BBU /* BrowserBack        */: return DOM_PK_BROWSER_BACK;         /* 0xE06A */
    case 0x370ECA39U /* LaunchApp1         */: return DOM_PK_LAUNCH_APP_1;         /* 0xE06B */
    case 0x370ED6ECU /* LaunchMail         */: return DOM_PK_LAUNCH_MAIL;          /* 0xE06C */
    case 0x39AB4892U /* LaunchMediaPlayer  */: return DOM_PK_LAUNCH_MEDIA_PLAYER;  /* 0xE06D */
    case 0x39AA45A4U /* MediaSelect        */: return DOM_PK_MEDIA_SELECT;         /* 0xE06D */
    default: return DOM_PK_UNKNOWN;
    }
}
#endif

#endif
