static const unsigned Windows2000  = 0x0500;
static const unsigned WindowsXP    = 0x0501;
static const unsigned WindowsVista = 0x0600;
static const unsigned Windows7     = 0x0601;

static unsigned OsVersion() {
  OSVERSIONINFO versionInfo = { 0 };
  versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&versionInfo);
  return (versionInfo.dwMajorVersion << 8) + (versionInfo.dwMajorVersion << 0);
}

static HBITMAP CreateBitmap(const image &image) {
  HDC hdc = GetDC(0);
  BITMAPINFO bitmapInfo;
  memset(&bitmapInfo, 0, sizeof(BITMAPINFO));
  bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bitmapInfo.bmiHeader.biWidth = image.width;
  bitmapInfo.bmiHeader.biHeight = -image.height;  //bitmaps are stored upside down unless we negate height
  bitmapInfo.bmiHeader.biPlanes = 1;
  bitmapInfo.bmiHeader.biBitCount = 32;
  bitmapInfo.bmiHeader.biCompression = BI_RGB;
  bitmapInfo.bmiHeader.biSizeImage = image.width * image.height * 4;
  void *bits = nullptr;
  HBITMAP hbitmap = CreateDIBSection(hdc, &bitmapInfo, DIB_RGB_COLORS, &bits, NULL, 0);
  if(bits) memcpy(bits, image.data, image.width * image.height * 4);
  ReleaseDC(0, hdc);
  return hbitmap;
}

static Keyboard::Keycode Keysym(unsigned keysym, unsigned keyflags) {
  #define pressed(keysym) (GetAsyncKeyState(keysym) & 0x8000)
  #define enabled(keysym) (GetKeyState(keysym))
  #define shifted() (pressed(VK_LSHIFT) || pressed(VK_RSHIFT))
  #define extended() (keyflags & (1 << 24))

  switch(keysym) {
  case VK_ESCAPE: return Keyboard::Keycode::Escape;
  case VK_F1: return Keyboard::Keycode::F1;
  case VK_F2: return Keyboard::Keycode::F2;
  case VK_F3: return Keyboard::Keycode::F3;
  case VK_F4: return Keyboard::Keycode::F4;
  case VK_F5: return Keyboard::Keycode::F5;
  case VK_F6: return Keyboard::Keycode::F6;
  case VK_F7: return Keyboard::Keycode::F7;
  case VK_F8: return Keyboard::Keycode::F8;
  case VK_F9: return Keyboard::Keycode::F9;
  //Keyboard::Keycode::F10 (should be captured under VK_MENU from WM_SYSKEY(UP,DOWN); but this is not working...)
  case VK_F11: return Keyboard::Keycode::F11;
  case VK_F12: return Keyboard::Keycode::F12;

  //Keyboard::Keycode::PrintScreen
  //Keyboard::Keycode::SysRq
  case VK_SCROLL: return Keyboard::Keycode::ScrollLock;
  case VK_PAUSE: return Keyboard::Keycode::Pause;
  //Keyboard::Keycode::Break

  case VK_INSERT: return extended() ? Keyboard::Keycode::Insert : Keyboard::Keycode::KeypadInsert;
  case VK_DELETE: return extended() ? Keyboard::Keycode::Delete : Keyboard::Keycode::KeypadDelete;
  case VK_HOME: return extended() ? Keyboard::Keycode::Home : Keyboard::Keycode::KeypadHome;
  case VK_END: return extended() ? Keyboard::Keycode::End : Keyboard::Keycode::KeypadEnd;
  case VK_PRIOR: return extended() ? Keyboard::Keycode::PageUp : Keyboard::Keycode::KeypadPageUp;
  case VK_NEXT: return extended() ? Keyboard::Keycode::PageDown : Keyboard::Keycode::KeypadPageDown;

  case VK_UP: return extended() ? Keyboard::Keycode::Up : Keyboard::Keycode::KeypadUp;
  case VK_DOWN: return extended() ? Keyboard::Keycode::Down : Keyboard::Keycode::KeypadDown;
  case VK_LEFT: return extended() ? Keyboard::Keycode::Left : Keyboard::Keycode::KeypadLeft;
  case VK_RIGHT: return extended() ? Keyboard::Keycode::Right : Keyboard::Keycode::KeypadRight;

  case VK_OEM_3: return !shifted() ? Keyboard::Keycode::Grave : Keyboard::Keycode::Tilde;
  case '1': return !shifted() ? Keyboard::Keycode::Number1 : Keyboard::Keycode::Exclamation;
  case '2': return !shifted() ? Keyboard::Keycode::Number2 : Keyboard::Keycode::At;
  case '3': return !shifted() ? Keyboard::Keycode::Number3 : Keyboard::Keycode::Pound;
  case '4': return !shifted() ? Keyboard::Keycode::Number4 : Keyboard::Keycode::Dollar;
  case '5': return !shifted() ? Keyboard::Keycode::Number5 : Keyboard::Keycode::Percent;
  case '6': return !shifted() ? Keyboard::Keycode::Number6 : Keyboard::Keycode::Power;
  case '7': return !shifted() ? Keyboard::Keycode::Number7 : Keyboard::Keycode::Ampersand;
  case '8': return !shifted() ? Keyboard::Keycode::Number8 : Keyboard::Keycode::Asterisk;
  case '9': return !shifted() ? Keyboard::Keycode::Number9 : Keyboard::Keycode::ParenthesisLeft;
  case '0': return !shifted() ? Keyboard::Keycode::Number0 : Keyboard::Keycode::ParenthesisRight;
  case VK_OEM_MINUS: return !shifted() ? Keyboard::Keycode::Minus : Keyboard::Keycode::Underscore;
  case VK_OEM_PLUS: return !shifted() ? Keyboard::Keycode::Equal : Keyboard::Keycode::Plus;
  case VK_BACK: return Keyboard::Keycode::Backspace;

  case VK_OEM_4: return !shifted() ? Keyboard::Keycode::BracketLeft : Keyboard::Keycode::BraceLeft;
  case VK_OEM_6: return !shifted() ? Keyboard::Keycode::BracketRight : Keyboard::Keycode::BraceRight;
  case VK_OEM_5: return !shifted() ? Keyboard::Keycode::Backslash : Keyboard::Keycode::Pipe;
  case VK_OEM_1: return !shifted() ? Keyboard::Keycode::Semicolon : Keyboard::Keycode::Colon;
  case VK_OEM_7: return !shifted() ? Keyboard::Keycode::Apostrophe : Keyboard::Keycode::Quote;
  case VK_OEM_COMMA: return !shifted() ? Keyboard::Keycode::Comma : Keyboard::Keycode::CaretLeft;
  case VK_OEM_PERIOD: return !shifted() ? Keyboard::Keycode::Period : Keyboard::Keycode::CaretRight;
  case VK_OEM_2: return !shifted() ? Keyboard::Keycode::Slash : Keyboard::Keycode::Question;

  case VK_TAB: return Keyboard::Keycode::Tab;
  case VK_CAPITAL: return Keyboard::Keycode::CapsLock;
  case VK_RETURN: return !extended() ? Keyboard::Keycode::Return : Keyboard::Keycode::Enter;
  case VK_SHIFT: return !pressed(VK_RSHIFT) ? Keyboard::Keycode::ShiftLeft : Keyboard::Keycode::ShiftRight;
  case VK_CONTROL: return !pressed(VK_RCONTROL) ? Keyboard::Keycode::ControlLeft : Keyboard::Keycode::ControlRight;
  case VK_LWIN: return Keyboard::Keycode::SuperLeft;
  case VK_RWIN: return Keyboard::Keycode::SuperRight;
  case VK_MENU:
    if(keyflags & (1 << 24)) return Keyboard::Keycode::AltRight;
    return Keyboard::Keycode::AltLeft;
  case VK_SPACE: return Keyboard::Keycode::Space;
  case VK_APPS: return Keyboard::Keycode::Menu;

  case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M':
  case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
    if(enabled(VK_CAPITAL)) {
      if(shifted()) {
        return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::a + keysym - 'A');
      } else {
        return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::A + keysym - 'A');
      }
    } else {
      if(shifted()) {
        return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::A + keysym - 'A');
      } else {
        return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::a + keysym - 'A');
      }
    }
    break;

  case VK_NUMLOCK: return Keyboard::Keycode::NumLock;
  case VK_DIVIDE: return Keyboard::Keycode::Divide;
  case VK_MULTIPLY: return Keyboard::Keycode::Multiply;
  case VK_SUBTRACT: return Keyboard::Keycode::Subtract;
  case VK_ADD: return Keyboard::Keycode::Add;
  case VK_DECIMAL: return Keyboard::Keycode::Point;
  case VK_NUMPAD1: return Keyboard::Keycode::Keypad1;
  case VK_NUMPAD2: return Keyboard::Keycode::Keypad2;
  case VK_NUMPAD3: return Keyboard::Keycode::Keypad3;
  case VK_NUMPAD4: return Keyboard::Keycode::Keypad4;
  case VK_NUMPAD5: return Keyboard::Keycode::Keypad5;
  case VK_NUMPAD6: return Keyboard::Keycode::Keypad6;
  case VK_NUMPAD7: return Keyboard::Keycode::Keypad7;
  case VK_NUMPAD8: return Keyboard::Keycode::Keypad8;
  case VK_NUMPAD9: return Keyboard::Keycode::Keypad9;
  case VK_NUMPAD0: return Keyboard::Keycode::Keypad0;

  case VK_CLEAR: return Keyboard::Keycode::KeypadCenter;
  }

  return Keyboard::Keycode::None;

  #undef pressed
  #undef enabled
  #undef shifted
  #undef extended
}
