#pragma once

struct InputMouseRawInput {
  Input& input;
  InputMouseRawInput(Input& input) : input(input) {}

  uintptr handle = 0;
  bool mouseAcquired = false;

  struct Mouse {
    shared_pointer<HID::Mouse> hid{new HID::Mouse};

    int relativeX = 0;
    int relativeY = 0;
    int relativeZ = 0;
    bool buttons[5] = {0};
  } ms;

  auto acquired() -> bool {
    if(mouseAcquired) {
      SetFocus((HWND)handle);
      SetCapture((HWND)handle);
      RECT rc;
      GetWindowRect((HWND)handle, &rc);
      ClipCursor(&rc);
    }
    return GetCapture() == (HWND)handle;
  }

  auto acquire() -> bool {
    if(!mouseAcquired) {
      mouseAcquired = true;
      ShowCursor(false);
    }
    return acquired();
  }

  auto release() -> bool {
    if(mouseAcquired) {
      mouseAcquired = false;
      ReleaseCapture();
      ClipCursor(nullptr);
      ShowCursor(true);
    }
    return true;
  }

  auto update(RAWINPUT* input) -> void {
    if((input->data.mouse.usFlags & 1) == MOUSE_MOVE_RELATIVE) {
      ms.relativeX += input->data.mouse.lLastX;
      ms.relativeY += input->data.mouse.lLastY;
    }

    if(input->data.mouse.usButtonFlags & RI_MOUSE_WHEEL) {
      ms.relativeZ += (int16_t)input->data.mouse.usButtonData;
    }

    if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) ms.buttons[0] = 1;
    if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP  ) ms.buttons[0] = 0;
    if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) ms.buttons[1] = 1;
    if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP  ) ms.buttons[1] = 0;
    if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) ms.buttons[2] = 1;
    if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP  ) ms.buttons[2] = 0;
    if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) ms.buttons[3] = 1;
    if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP  ) ms.buttons[3] = 0;
    if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) ms.buttons[4] = 1;
    if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP  ) ms.buttons[4] = 0;
  }

  auto assign(uint groupID, uint inputID, int16_t value) -> void {
    auto& group = ms.hid->group(groupID);
    if(group.input(inputID).value() == value) return;
    input.doChange(ms.hid, groupID, inputID, group.input(inputID).value(), value);
    group.input(inputID).setValue(value);
  }

  auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
    assign(HID::Mouse::GroupID::Axis, 0, ms.relativeX);
    assign(HID::Mouse::GroupID::Axis, 1, ms.relativeY);
    assign(HID::Mouse::GroupID::Axis, 2, ms.relativeZ);

    //keys are intentionally reordered below:
    //in ruby, button order is {left, middle, right, up, down}
    assign(HID::Mouse::GroupID::Button, 0, ms.buttons[0]);
    assign(HID::Mouse::GroupID::Button, 2, ms.buttons[1]);
    assign(HID::Mouse::GroupID::Button, 1, ms.buttons[2]);
    assign(HID::Mouse::GroupID::Button, 4, ms.buttons[3]);
    assign(HID::Mouse::GroupID::Button, 3, ms.buttons[4]);

    ms.relativeX = 0;
    ms.relativeY = 0;
    ms.relativeZ = 0;

    devices.append(ms.hid);
  }

  auto initialize(uintptr handle) -> bool {
    if(!handle) return false;
    this->handle = handle;

    ms.hid->setVendorID(HID::Mouse::GenericVendorID);
    ms.hid->setProductID(HID::Mouse::GenericProductID);
    ms.hid->setPathID(0);

    ms.hid->axes().append("X");
    ms.hid->axes().append("Y");
    ms.hid->axes().append("Z");

    ms.hid->buttons().append("Left");
    ms.hid->buttons().append("Middle");
    ms.hid->buttons().append("Right");
    ms.hid->buttons().append("Up");
    ms.hid->buttons().append("Down");

    rawinput.updateMouse = {&InputMouseRawInput::update, this};
    return true;
  }

  auto terminate() -> void {
    rawinput.updateMouse.reset();
    release();
  }
};
