#include "RawInputMouse.h"
#include <cassert>
#include <vector>

namespace RawInputMouse
{

namespace
{

std::vector<Data> g_Data;
std::vector<char> g_RawInputMessageData;

} // namespace `anonymous`

void Initialize()
{
    RAWINPUTDEVICE device;
    device.usUsagePage = 0x01;
    device.usUsage = 0x02;
    device.dwFlags = 0;
    device.hwndTarget = 0;

    BOOL b = RegisterRawInputDevices(&device, 1, sizeof device);
    assert(b);
}

void WmInput(WPARAM wParam, LPARAM lParam)
{
    bool inForeground = (GET_RAWINPUT_CODE_WPARAM(wParam) == RIM_INPUT);
    HRAWINPUT hRawInput = (HRAWINPUT)lParam;

    UINT dataSize;
    UINT u = GetRawInputData(hRawInput, RID_INPUT, NULL, &dataSize, sizeof(RAWINPUTHEADER));
    assert(u == 0);
    
    if(dataSize == 0) return;
    
    if(dataSize > g_RawInputMessageData.size())
        g_RawInputMessageData.resize(dataSize);
    
    void* dataBuf = &g_RawInputMessageData[0];
    
    u = GetRawInputData(hRawInput, RID_INPUT, dataBuf, &dataSize, sizeof(RAWINPUTHEADER));
    assert(u == dataSize);
    
    const RAWINPUT *raw = (const RAWINPUT*)dataBuf;
    
    if (raw->header.dwType == RIM_TYPEMOUSE)
    {
        const RAWMOUSE& mouseData = raw->data.mouse;
        HANDLE deviceHandle = raw->header.hDevice;
        USHORT flags = mouseData.usButtonFlags;
        short wheelDelta = (short)mouseData.usButtonData;
        LONG x = mouseData.lLastX, y = mouseData.lLastY;

        /*
        flags can be one or more of bits:
            RI_MOUSE_LEFT_BUTTON_DOWN
            RI_MOUSE_LEFT_BUTTON_UP
            RI_MOUSE_MIDDLE_BUTTON_DOWN
            RI_MOUSE_MIDDLE_BUTTON_UP
            RI_MOUSE_RIGHT_BUTTON_DOWN
            RI_MOUSE_RIGHT_BUTTON_UP
            RI_MOUSE_BUTTON_1_DOWN
            RI_MOUSE_BUTTON_1_UP
            RI_MOUSE_BUTTON_2_DOWN
            RI_MOUSE_BUTTON_2_UP
            RI_MOUSE_BUTTON_3_DOWN
            RI_MOUSE_BUTTON_3_UP
            RI_MOUSE_BUTTON_4_DOWN
            RI_MOUSE_BUTTON_4_UP
            RI_MOUSE_BUTTON_5_DOWN
            RI_MOUSE_BUTTON_5_UP
            RI_MOUSE_WHEEL

        (mouseData.usFlags & MOUSE_MOVE_ABSOLUTE) was never true in my tests.
        On both mice and laptop touchpad (x, y) were relative like (-3, 0) etc.

        ulRawButtons doesn't work on my laptop, it's always 0.
        */

        size_t index = 0, count = g_Data.size();
        while(index < count && g_Data[index].DeviceHandle != deviceHandle)
            ++index;
        if(index == count)
        {
            g_Data.push_back(Data());
            g_Data[index].DeviceHandle = deviceHandle;
            Reset(index);
        }

        Data& data = g_Data[index];
        data.Flags |= flags;
        data.DeltaX += x;
        data.DeltaY += y;
        data.WheelDelta += wheelDelta;

        /*
        wprintf(
            L"Mouse: Device=0x%08X, Flags=%04x, WheelDelta=%d, X=%d, Y=%d\n",
            deviceHandle, flags, wheelDelta, x, y);
        */
    }
}

size_t GetCount()
{
    return g_Data.size();
}

const Data& GetData(size_t index)
{
    return g_Data[index];
}

void Reset(size_t index)
{
    Data& data = g_Data[index];
    data.Flags = 0;
    data.DeltaX = 0;
    data.DeltaY = 0;
    data.WheelDelta = 0;
}

void ResetAll()
{
    for(size_t i = 0, count = g_Data.size(); i < count; ++i)
        Reset(i);
}

} // namespace RawInputMouse
