////////////////////////////////
// GFX Functions

// event construction

MR4TH_SYMBOL GFX_Event gfx_event_dummy = {0};

MR4TH_SYMBOL GFX_Event*
gfx_event_push(Arena *arena, GFX_EventList *list){
  GFX_Event *result = &gfx_event_dummy;
  if (arena != 0 && list != 0){
    result = push_array(arena, GFX_Event, 1);
    SLLQueuePush(list->first, list->last, result);
    list->count += 1;
  }
  return(result);
}


/*******************************
**      Begin GFX Win32       **
*******************************/

#if OS_WINDOWS

////////////////////////////////
// Win32 Implementation: GFX Functions

// global states

MR4TH_SYMBOL B32        w32_is_initialized = 0;
MR4TH_SYMBOL W32_Window w32_window_slots[GFX_MAX_WINDOW_COUNT] = {0};
MR4TH_SYMBOL W32_Window*w32_window_free = 0;

MR4TH_SYMBOL GFX_ResizingFunc *w32_resizing_func = 0;

#define w32_handle_from_slot(s) (GFX_Window*)(((s) - w32_window_slots) + 1)
#define w32_slot_from_handle(h) (w32_window_slots + ((U64)(h) - 1))
#define w32_handle_is_valid(h)  (1 <= (U64)(h) && (U64)(h) <= ArrayCount(w32_window_slots))

MR4TH_SYMBOL Arena*         w32_input_gather_arena = 0;
MR4TH_SYMBOL GFX_EventList* w32_input_gather_list = 0;

// apis for all

MR4TH_SYMBOL void
gfx_message_box(String8 title, String8 message){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  String16 title16 = str16_from_str8(scratch.arena, title);
  String16 message16 = str16_from_str8(scratch.arena, message);
  
  MessageBoxW(0, (WCHAR*)message16.str, (WCHAR*)title16.str, MB_OK);
  
  arena_release_scratch(&scratch);
}

MR4TH_SYMBOL GFX_OpenFileResult*
gfx_open_file_box(Arena *arena, GFX_Window *window, String8 title){
  ArenaTemp scratch = arena_get_scratch(&arena, 1);
  
  String8 restore_cwd =
    os_file_path(scratch.arena, OS_SystemPath_CurrentDirectory);
  
  HWND owner_hwnd = 0;
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *w32window = gfx_w32_window_from_handle(window);
      owner_hwnd = w32window->wnd;
    }
  }
  
  U32 buf_size = KB(4);
  U16 *buf = push_array(scratch.arena, U16, buf_size);
  
  String16 title16 = str16_from_str8(scratch.arena, title);
  
  OPENFILENAMEW inout = {0};
  inout.lStructSize = sizeof(inout);
  inout.hwndOwner = owner_hwnd;
  // TODO(allen): bake filter string from a high level filter data structure
  inout.lpstrFilter = 0;
  inout.lpstrFile = (WCHAR*)buf;
  inout.nMaxFile = buf_size;
  // TODO(allen): send hint about where to open the file dialogue here
  inout.lpstrInitialDir = 0;
  inout.lpstrTitle = (WCHAR*)title16.str;
  inout.Flags = OFN_ENABLESIZING;
  
  B32 success = (GetOpenFileNameW(&inout) != 0);
  
  // fill result
  GFX_OpenFileResult *result = 0;
  if (success){
    // extract file name (utf-16)
    U16 *opl = buf + buf_size;
    U16 *ptr = buf;
    for (;ptr < opl && *ptr != 0; ptr += 1);
    String16 file_name_16 = CLiteral(String16){buf, ptr - buf};
    
    result = push_array(arena, GFX_OpenFileResult, 1);
    result->file_name = str8_from_str16(arena, file_name_16);
  }
  
  os_set_current_directory(restore_cwd);
  
  arena_release_scratch(&scratch);
  
  return(result);
}

// apis for applications

MR4TH_SYMBOL B32
gfx_is_inited(void){
  return(w32_is_initialized);
}

MR4TH_SYMBOL B32
gfx_init(void){
  B32 error = 0;
  
  // decide whether to proceed
  B32 proceed = 1;
  {
    if (gfx_is_inited()){
      proceed = 0;
    }
  }
  
  if (proceed){
    // register window class
    if (!error){
      HINSTANCE instance = w32_get_instance();
      WNDCLASSW wnd_class = {0};
      wnd_class.style = CS_HREDRAW | CS_VREDRAW;
      wnd_class.lpfnWndProc = gfx_w32_window_proc;
      wnd_class.hInstance = instance;
      wnd_class.lpszClassName = W32_GRAPHICS_WINDOW_CLASS_NAME;
      ATOM atom = RegisterClassW(&wnd_class);
      if (atom == 0){
        er_emit_lit("error registering window class");
        error = 1;
      }
    }
    
    // in graphics application use "precise" timers
    if (!error){
      timeBeginPeriod(1);
    }
    
    // set up window slots
    if (!error){
      MemoryZeroArray(w32_window_slots);
      w32_window_free = w32_window_slots;
      W32_Window *last = w32_window_slots + ArrayCount(w32_window_slots) - 1;
      for (W32_Window *slot = w32_window_slots;
           slot < last;
           slot += 1){
        slot->next = (slot + 1);
      }
    }
    
    // set layer initialized flag
    if (!error){
      w32_is_initialized = 1;
    }
  }
  
  // success is returned IFF gfx is initialized
  Assert((!error) == gfx_is_inited());
  
  // fill result
  B32 result = (!error);
  return(result);
}

MR4TH_SYMBOL void
gfx_set_cursor(GFX_CursorAppearance appearance){
  if (appearance < GFX_CursorAppearance_COUNT){
    MR4TH_SYMBOL_STATIC HCURSOR cursors[GFX_CursorAppearance_COUNT] = {0};
    
#define W32CursorXList(X)\
X(Pointer,    IDC_ARROW)\
X(HandPointer,IDC_HAND)\
X(TextInsert, IDC_IBEAM)\
X(Loading,    IDC_WAIT)\
X(ResizeLeftRight, IDC_SIZEWE)\
X(ResizeUpDown,    IDC_SIZENS)
    
    if (cursors[appearance] == 0){
      HCURSOR newcursor = 0;
      switch (appearance){
#define X(N,R) case GFX_CursorAppearance_##N: newcursor = LoadCursorA(0, R); break;
        W32CursorXList(X)
#undef X
      }
      cursors[appearance] = newcursor;
    }
    
    SetCursor(cursors[appearance]);
  }
}

MR4TH_SYMBOL void
gfx_set_resizing_hook(GFX_ResizingFunc *func){
  if (gfx_is_inited()){
    w32_resizing_func = func;
  }
}

MR4TH_SYMBOL GFX_ResizingFunc*
gfx_get_resizing_hook(void){
  GFX_ResizingFunc *result = 0;
  if (gfx_is_inited()){
    result = w32_resizing_func;
  }
  return(result);
}

/// (window handling)

#define W32_WS_BORDERED (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
#define W32_WS_RESIZABLE (WS_THICKFRAME | WS_MAXIMIZEBOX)

MR4TH_SYMBOL GFX_Window*
gfx_window_create(void){
  B32 error = 0;
  
  // api check
  if (!gfx_is_inited()){
    error = 1;
  }
  
  // allocate slot
  W32_Window *slot_ptr = 0;
  if (!error){
    slot_ptr = w32_window_free;
    if (slot_ptr == 0){
      er_emit_lit("reached max window count");
      error = 1;
    }
  }
  
  // create window
  HWND wnd = 0;
  if (!error){
    HINSTANCE instance = w32_get_instance();
    wnd = CreateWindowW(W32_GRAPHICS_WINDOW_CLASS_NAME,
                        L"TITLE ME",
                        WS_TILEDWINDOW,
                        CW_USEDEFAULT, CW_USEDEFAULT,
                        CW_USEDEFAULT, CW_USEDEFAULT,
                        0, 0, instance, 0 // parent, menu, inst, param
                        );
    
    if (wnd == 0){
      er_emit_lit("failed to create graphics window");
      error = 1;
    }
  }
  
  // cleanup on error
  if (error){
    if (wnd != 0){
      DestroyWindow(wnd);
    }
  }
  
  // fill result
  GFX_Window *result = 0;
  if (!error){
    SLLStackPop(w32_window_free);
    MemoryZeroStruct(slot_ptr);
    slot_ptr->wnd = wnd;
    result = w32_handle_from_slot(slot_ptr);
  }
  return(result);
}

MR4TH_SYMBOL void
gfx_window_destroy(GFX_Window *window){
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      if (slot->wnd != 0){
        if (slot->release_func != 0){
          slot->release_func(window);
        }
        DestroyWindow(slot->wnd);
        MemoryZeroStruct(slot);
        SLLStackPush(w32_window_free, slot);
      }
    }
  }
}

MR4TH_SYMBOL B32
gfx_window_is_valid(GFX_Window *window){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      if (slot->wnd != 0){
        result = 1;
      }
    }
  }
  
  return(result);
}

MR4TH_SYMBOL B32
gfx_window_set_visible(GFX_Window *window, B32 visible){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      int cmd = SW_HIDE;
      if (visible){
        cmd = SW_SHOW;
      }
      ShowWindow(slot->wnd, cmd);
      result = 1;
    }
  }
  
  return(result);
}

MR4TH_SYMBOL B32
gfx_window_set_title(GFX_Window *window, String8 title){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      ArenaTemp scratch = arena_get_scratch(0, 0);
      String16 title16 = str16_from_str8(scratch.arena, title);
      W32_Window *slot = w32_slot_from_handle(window);
      if (SetWindowTextW(slot->wnd, (WCHAR*)title16.str)){
        result = 1;
      }
      arena_release_scratch(&scratch);
    }
  }
  
  return(result);
}

MR4TH_SYMBOL B32
gfx_window_set_inner_rect(GFX_Window *window, S32 *rect){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      LONG style = GetWindowLongW(slot->wnd, GWL_STYLE);
      RECT r = {0};
      r.left   = rect[0];
      r.top    = rect[1];
      r.right  = rect[2];
      r.bottom = rect[3];
      if (AdjustWindowRect(&r, style, 0)){
        if (SetWindowPos(slot->wnd, HWND_TOP,
                         r.left, r.top, r.right - r.left, r.bottom - r.top,
                         0)){
          result = 1;
        }
      }
    }
  }
  
  return(result);
}

MR4TH_SYMBOL B32
gfx_window_get_inner_rect(GFX_Window *window, S32 *rect_out){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      RECT rect = {0};
      if (GetClientRect(slot->wnd, &rect)){
        if (ClientToScreen(slot->wnd, (POINT*)(&rect.left))){
          if (ClientToScreen(slot->wnd, (POINT*)(&rect.right))){
            rect_out[0] = rect.left;
            rect_out[1] = rect.top;
            rect_out[2] = rect.right;
            rect_out[3] = rect.bottom;
            result = 1;
          }
        }
      }
    }
  }
  
  return(result);
}

MR4TH_SYMBOL B32
gfx_window_get_outer_rect(GFX_Window *window, S32 *rect_out){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      RECT rect = {0};
      if (GetWindowRect(slot->wnd, &rect)){
        rect_out[0] = rect.left;
        rect_out[1] = rect.top;
        rect_out[2] = rect.right;
        rect_out[3] = rect.bottom;
        result = 1;
      }
    }
  }
  
  return(result);
}

MR4TH_SYMBOL B32
gfx_window_set_resizable(GFX_Window *window, B32 resizable){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      DWORD style = GetWindowLongW(slot->wnd, GWL_STYLE);
      DWORD new_style = 0;
      if (resizable){
        new_style = style | W32_WS_RESIZABLE;
      }
      else{
        new_style = style & ~W32_WS_RESIZABLE;
      }
      if (SetWindowLongW(slot->wnd, GWL_STYLE, new_style)){
        slot->supports_resizing = resizable;
        result = 1;
      }
    }
  }
  
  return(result);
}

MR4TH_SYMBOL B32
gfx_window_set_full_screen(GFX_Window *window, B32 full_screen){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      
      // do we need to modify the window?
      DWORD style = GetWindowLongW(slot->wnd, GWL_STYLE);
      B32 is_full_screen_already = !(style & WS_TILEDWINDOW);
      if (is_full_screen_already != full_screen){
        
        // change to full screen
        if (full_screen){
          slot->place.length = sizeof(slot->place);
          if (GetWindowPlacement(slot->wnd, &slot->place)){
            HMONITOR mon = MonitorFromWindow(slot->wnd, MONITOR_DEFAULTTOPRIMARY);
            if (mon != 0){
              MONITORINFO moninfo = {0};
              moninfo.cbSize = sizeof(moninfo);
              if (GetMonitorInfoW(mon, &moninfo)){
                SetWindowLongW(slot->wnd, GWL_STYLE, style & ~WS_TILEDWINDOW);
                SetWindowPos(slot->wnd, HWND_TOP,
                             moninfo.rcMonitor.left, moninfo.rcMonitor.top,
                             moninfo.rcMonitor.right - moninfo.rcMonitor.left,
                             moninfo.rcMonitor.bottom - moninfo.rcMonitor.top,
                             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
              }
            }
          }
        }
        
        // change to windowed
        else{
          DWORD new_style = style | W32_WS_BORDERED;
          if (slot->supports_resizing){
            new_style |= W32_WS_RESIZABLE;
          }
          SetWindowLongW(slot->wnd, GWL_STYLE, new_style);
          SetWindowPlacement(slot->wnd, &slot->place);
          SetWindowPos(slot->wnd, 0, 0, 0, 0, 0,
                       SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
                       SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
        }
      }
    }
  }
  
  return(result);
}

MR4TH_SYMBOL B32
gfx_window_is_full_screen(GFX_Window *window){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      DWORD style = GetWindowLongW(slot->wnd, GWL_STYLE);
      result = !(style & W32_WS_BORDERED);
    }
  }
  
  return(result);
}

MR4TH_SYMBOL B32
gfx_window_is_minimized(GFX_Window *window){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      result = IsIconic(slot->wnd);
    }
  }
  
  return(result);
}

MR4TH_SYMBOL B32
gfx_window_is_maximized(GFX_Window *window){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      result = IsZoomed(slot->wnd);
    }
  }
  
  return(result);
}

MR4TH_SYMBOL B32
gfx_window_set_drops_allowed(GFX_Window *window, B32 allowed){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *w32window = gfx_w32_window_from_handle(window);
      DragAcceptFiles(w32window->wnd, allowed);
      result = 1;
    }
  }
  
  return(result);
}

MR4TH_SYMBOL GFX_Window*
gfx_get_window_with_keyboard_focus(void){
  GFX_Window *result = 0;
  
  if (gfx_is_inited()){
    HWND active_window = GetFocus();
    if (active_window != 0){
      result = gfx_w32_handle_from_hwnd(active_window);
    }
  }
  
  return(result);
}

MR4TH_SYMBOL B32
gfx_window_set_user_data(GFX_Window *window, void *ptr){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      if (slot->wnd != 0){
        slot->user_data = ptr;
        result = 1;
      }
    }
  }
  
  return(result);
}

MR4TH_SYMBOL void*
gfx_window_get_user_data(GFX_Window *window){
  void *result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      result = slot->user_data;
    }
  }
  
  return(result);
}

MR4TH_SYMBOL void*
gfx_window_get_equipped_data(GFX_Window *window){
  void *result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      result = slot->equipped_data;
    }
  }
  
  return(result);
}

MR4TH_SYMBOL U64
gfx_window_refresh_rate(GFX_Window *window){
  U64 result = 60;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      HMONITOR monitor = MonitorFromWindow(slot->wnd, MONITOR_DEFAULTTOPRIMARY);
      
      MONITORINFOEXW info; MemoryZeroStruct(&info);
      info.cbSize = sizeof(info);
      if (GetMonitorInfoW(monitor, (MONITORINFO*)&info)){
        DEVMODEW devmode = {0};
        devmode.dmSize = sizeof(devmode);
        EnumDisplaySettingsW(info.szDevice, ENUM_CURRENT_SETTINGS, &devmode);
        U64 retrieved_rate = devmode.dmDisplayFrequency;
        result = ClampBot(retrieved_rate, 24);
      }
    }
  }
  
  Assert(result != 0);
  return(result);
}

/// (input)

MR4TH_SYMBOL void
gfx_wait_for_input(void){
  WaitMessage();
}

MR4TH_SYMBOL GFX_EventList*
gfx_get_input(Arena *arena){
  GFX_EventList *input = push_array(arena, GFX_EventList, 1);
  
  // flush thread message queue
  {
    // set destination for gathered input
    w32_input_gather_arena = arena;
    w32_input_gather_list = input;
    
    // gather input
    MSG msg = {0};
    for (;PeekMessageW(&msg, 0, 0, 0, PM_REMOVE);){
      DispatchMessageW(&msg);
      TranslateMessage(&msg);
    }
    
    // unset destination for gathered input
    w32_input_gather_arena = 0;
    w32_input_gather_list = 0;
  }
  
  return(input);
}

MR4TH_SYMBOL void
gfx_get_absolute_mouse_position(S32 *out){
  POINT p = {0};
  GetCursorPos(&p);
  out[Axis_X] = p.x;
  out[Axis_Y] = p.y;
}

// apis for system implementors

MR4TH_SYMBOL B32
gfx_window_set_equipped_data(GFX_Window *window, void *ptr,
                             GFX_WindowReleaseFunc *release_func){
  B32 result = 0;
  
  if (gfx_is_inited()){
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      if (slot->wnd != 0 && slot->equipped_data == 0){
        slot->equipped_data = ptr;
        slot->release_func = release_func;
        result = 1;
      }
    }
  }
  
  return(result);
}

////////////////////////////////
// GFX Win32 Functions

MR4TH_SYMBOL LRESULT
gfx_w32_window_proc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam){
  LRESULT result = 0;
  switch (msg){
#if 0
    case WM_SIZING:
    {
      // here we can intercept and optionally modify user initiated resizing
      // (should set result to 1 if we do)
    }break;
#endif
    
    
    /// WINDOW ///
    
    case WM_SIZE:
    {
      if (w32_resizing_func != 0){
        GFX_Window *window = gfx_w32_handle_from_hwnd(wnd);
        U16 width = (lparam & 0xFFFF);
        U16 height = (lparam >> 16);
        w32_resizing_func(window, width, height);
      }
    }break;
    
    case WM_CLOSE:
    {
      GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
      event->kind = GFX_EventKind_WindowClose;
      event->window = gfx_w32_handle_from_hwnd(wnd);
    }break;
    
    
    /// MOUSE & KEYBOARD ///
    
    case WM_KILLFOCUS:
    {
      GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
      event->window = gfx_w32_handle_from_hwnd(wnd);
      event->kind  = GFX_EventKind_WindowLoseFocus;
    }break;
    
    case WM_LBUTTONDOWN:
    {
      GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
      event->window = gfx_w32_handle_from_hwnd(wnd);
      event->kind  = GFX_EventKind_MouseButton;
      event->flags = GFX_EventFlag_Press;
      event->code  = GFX_MouseButton_Left;
      SetCapture(wnd);
    }break;
    
    case WM_RBUTTONDOWN:
    {
      GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
      event->window = gfx_w32_handle_from_hwnd(wnd);
      event->kind  = GFX_EventKind_MouseButton;
      event->flags = GFX_EventFlag_Press;
      event->code  = GFX_MouseButton_Right;
      SetCapture(wnd);
    }break;
    
    case WM_MBUTTONDOWN:
    {
      GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
      event->window = gfx_w32_handle_from_hwnd(wnd);
      event->kind  = GFX_EventKind_MouseButton;
      event->flags = GFX_EventFlag_Press;
      event->code  = GFX_MouseButton_Middle;
      SetCapture(wnd);
    }break;
    
    case WM_LBUTTONUP:
    {
      GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
      event->window = gfx_w32_handle_from_hwnd(wnd);
      event->kind  = GFX_EventKind_MouseButton;
      event->flags = GFX_EventFlag_Release;
      event->code  = GFX_MouseButton_Left;
      ReleaseCapture();
    }break;
    
    case WM_RBUTTONUP:
    {
      GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
      event->window = gfx_w32_handle_from_hwnd(wnd);
      event->kind  = GFX_EventKind_MouseButton;
      event->flags = GFX_EventFlag_Release;
      event->code  = GFX_MouseButton_Right;
      ReleaseCapture();
    }break;
    
    case WM_MBUTTONUP:
    {
      GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
      event->window = gfx_w32_handle_from_hwnd(wnd);
      event->kind  = GFX_EventKind_MouseButton;
      event->flags = GFX_EventFlag_Release;
      event->code  = GFX_MouseButton_Middle;
      ReleaseCapture();
    }break;
    
    case WM_MOUSEWHEEL:
    {
      S16 delta = (S16)(wparam >> 16);
      
      GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
      event->window = gfx_w32_handle_from_hwnd(wnd);
      event->kind  = GFX_EventKind_MouseWheel;
      event->delta = -1.f*(F32)delta;
    }break;
    
    case WM_SYSKEYDOWN:
    case WM_SYSKEYUP:
    case WM_KEYDOWN:
    case WM_KEYUP:
    {
      B32 prev_state = ((lparam >> 30)&1);
      B32 released   = ((lparam >> 31)&1);
      
      // generate key event
      GFX_Key key = gfx_w32_key_code_from_vk_code(wparam);
      if (key != GFX_Key_Null){
        U32 flags = 0;
        
        if (released){
          flags |= GFX_EventFlag_Release;
        }
        else if (prev_state){
          flags |= GFX_EventFlag_Repeat;
        }
        else{
          flags |= GFX_EventFlag_Press;
        }
        
        if ((GetKeyState(VK_SHIFT)&0x1000) != 0){
          flags |= GFX_EventFlag_Shift;
        }
        if ((GetKeyState(VK_CONTROL)&0x1000) != 0){
          flags |= GFX_EventFlag_Control;
        }
        if ((GetKeyState(VK_MENU)&0x1000) != 0){
          flags |= GFX_EventFlag_Alt;
        }
        
        
        GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
        event->kind  = GFX_EventKind_Key;
        event->flags = flags;
        event->code  = key;
      }
      
      // generate text inserts in special cases
      if (!released){
        if (wparam == VK_TAB){
          GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
          event->window = gfx_w32_handle_from_hwnd(wnd);
          event->kind = GFX_EventKind_TextInsert;
          event->code = '\t';
        }
        else if (wparam == VK_RETURN){
          GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
          event->window = gfx_w32_handle_from_hwnd(wnd);
          event->kind = GFX_EventKind_TextInsert;
          event->code = '\n';
        }
      }
      
      // don't skip default handling on sys keys
      if (msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP){
        result = DefWindowProcW(wnd, msg, wparam, lparam);
      }
    }break;
    
    case WM_SYSCOMMAND:
    {
      B32 is_menu = (wparam == SC_KEYMENU && (lparam >> 16) <= 0);
      if (!is_menu){
        result = DefWindowProcW(wnd, msg, wparam, lparam);
      }
    }break;
    
    case WM_CHAR:
    {
      U32 ch = (wparam & 0xFFFF);
      
      MR4TH_SYMBOL_STATIC U16 lo = 0;
      MR4TH_SYMBOL_STATIC U16 hi = 0;
      
      U32 codepoint = 0;
      if (IS_LOW_SURROGATE(ch)){
        lo = ch;
      }
      else if (IS_HIGH_SURROGATE(ch)){
        hi = ch;
      }
      else{
        codepoint = ch;
        lo = hi = 0;
      }
      
      if (lo != 0 && hi != 0){
        if (IS_SURROGATE_PAIR(hi, lo)){
          codepoint = (0x10000 + ((hi&0x3FF)<<10) + (lo&0x3FF));
        }
        lo = hi = 0;
      }
      
      if (codepoint >= ' ' && codepoint != 0x7F){
        GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
        event->window = gfx_w32_handle_from_hwnd(wnd);
        event->kind = GFX_EventKind_TextInsert;
        event->code = codepoint;
      }
      
    }break;
    
    
    /// DRAG & DROP ///
    
    case WM_DROPFILES:
    {
      ArenaTemp scratch = arena_get_scratch(0, 0);
      
      HDROP hdrop = (HDROP)wparam;
      
      // get list of file names
      String8List list = {0};
      
      U32 string_count = DragQueryFileW(hdrop, max_U32, 0, 0);
      for (U32 i = 0; i < string_count; i += 1){
        // get file name (as utf-16)
        U32 size = DragQueryFileW(hdrop, i, 0, 0);
        U16 *buf16 = push_array(scratch.arena, U16, size + 1);
        DragQueryFileW(hdrop, i, (WCHAR*)buf16, size + 1);
        
        // convert to utf-8
        String8 string = str8_from_str16(w32_input_gather_arena, CLiteral(String16){buf16, size});
        
        // save file name
        str8_list_push(w32_input_gather_arena, &list, string);
      }
      
      // get drop point
      POINT point = {0};
      DragQueryPoint(hdrop, &point);
      
      // finish drag & drop
      DragFinish(hdrop);
      
      // emit event
      GFX_Event *event = gfx_event_push(w32_input_gather_arena, w32_input_gather_list);
      GFX_FileDropData *file_drop_data = push_array(w32_input_gather_arena, GFX_FileDropData, 1);
      event->kind = GFX_EventKind_FileDrop;
      event->window = gfx_w32_handle_from_hwnd(wnd);
      event->file_drop_data = file_drop_data;
      file_drop_data->files = list;
      file_drop_data->point[Axis_X] = point.x;
      file_drop_data->point[Axis_Y] = point.y;
      
      arena_release_scratch(&scratch);
    }break;
    
    
    default:
    {
      result = DefWindowProcW(wnd, msg, wparam, lparam);
    }break;
  }
  
  return(result);
}

MR4TH_SYMBOL W32_Window*
gfx_w32_window_from_handle(GFX_Window *window){
  // This is an internal function so a user should
  // have validated *before* they called.
  Assert(w32_handle_is_valid(window));
  W32_Window *slot = w32_slot_from_handle(window);
  Assert(slot->wnd);
  W32_Window *result = slot;
  return(result);
}

MR4TH_SYMBOL GFX_Window*
gfx_w32_handle_from_hwnd(HWND wnd){
  // TODO(allen): speed this up by attaching data right on the HWND
  GFX_Window *result = 0;
  if (wnd != 0){
    W32_Window *slot = w32_window_slots;
    for (U32 i = 0; i < GFX_MAX_WINDOW_COUNT; i += 1, slot += 1){
      if (slot->wnd == wnd){
        result = w32_handle_from_slot(slot);
        break;
      }
    }
  }
  return(result);
}

MR4TH_SYMBOL GFX_Key
gfx_w32_key_code_from_vk_code(WPARAM vk){
  GFX_Key key = GFX_Key_Null;
  
  if ('0' <= vk && vk <= '9'){
    key = (GFX_Key)(vk - '0' + GFX_Key_0);
  }
  else if ('A' <= vk && vk <= 'Z'){
    key = (GFX_Key)(vk - 'A' + GFX_Key_A);
  }
  else if (VK_F1 <= vk && vk <= VK_F24){
    key = (GFX_Key)(vk - VK_F1 + GFX_Key_F1);
  }
  else if (VK_NUMPAD0 <= vk && vk <= VK_NUMPAD9){
    key = (GFX_Key)(vk - VK_NUMPAD0 + GFX_Key_Num0);
  }
  
  else switch (vk){
    case VK_BACK:       key = GFX_Key_Backspace;    break;
    case VK_TAB:        key = GFX_Key_Tab;          break;
    case VK_RETURN:     key = GFX_Key_Return;       break;
    case VK_SHIFT:      key = GFX_Key_Shift;        break;
    case VK_CONTROL:    key = GFX_Key_Control;      break;
    case VK_MENU:       key = GFX_Key_Alt;          break;
    case VK_PAUSE:      key = GFX_Key_PauseBreak;   break;
    case VK_CAPITAL:    key = GFX_Key_CapsLock;     break;
    case VK_ESCAPE:     key = GFX_Key_Escape;       break;
    case VK_SPACE:      key = GFX_Key_Space;        break;
    case VK_PRIOR:      key = GFX_Key_PageUp;       break;
    case VK_NEXT:       key = GFX_Key_PageDown;     break;
    case VK_END:        key = GFX_Key_End;          break;
    case VK_HOME:       key = GFX_Key_Home;         break;
    case VK_LEFT:       key = GFX_Key_Left;         break;
    case VK_UP:         key = GFX_Key_Up;           break;
    case VK_RIGHT:      key = GFX_Key_Right;        break;
    case VK_DOWN:       key = GFX_Key_Down;         break;
    case VK_SNAPSHOT:   key = GFX_Key_PrintScreen;  break;
    case VK_INSERT:     key = GFX_Key_Insert;       break;
    case VK_DELETE:     key = GFX_Key_Delete;       break;
    case VK_MULTIPLY:   key = GFX_Key_NumMultiply;  break;
    case VK_ADD:        key = GFX_Key_NumAdd;       break;
    case VK_SUBTRACT:   key = GFX_Key_NumSubtract;  break;
    case VK_DECIMAL:    key = GFX_Key_NumDecimal;   break;
    case VK_DIVIDE:     key = GFX_Key_NumDivide;    break;
    case VK_NUMLOCK:    key = GFX_Key_NumLock;      break;
    case VK_SCROLL:     key = GFX_Key_ScrollLock;   break;
    case VK_LSHIFT:     key = GFX_Key_Shift;        break;
    case VK_RSHIFT:     key = GFX_Key_Shift;        break;
    case VK_LCONTROL:   key = GFX_Key_Control;      break;
    case VK_RCONTROL:   key = GFX_Key_Control;      break;
    case VK_LMENU:      key = GFX_Key_Alt;          break;
    case VK_RMENU:      key = GFX_Key_Alt;          break;
    case VK_OEM_1:      key = GFX_Key_Semicolon;    break;
    case VK_OEM_PLUS:   key = GFX_Key_Equals;       break;
    case VK_OEM_COMMA:  key = GFX_Key_Comma;        break;
    case VK_OEM_MINUS:  key = GFX_Key_Minus;        break;
    case VK_OEM_PERIOD: key = GFX_Key_Period;       break;
    case VK_OEM_2:      key = GFX_Key_Slash;        break;
    case VK_OEM_3:      key = GFX_Key_Tick;         break;
    case VK_OEM_4:      key = GFX_Key_LeftBracket;  break;
    case VK_OEM_5:      key = GFX_Key_BackSlash;    break;
    case VK_OEM_6:      key = GFX_Key_RightBracket; break;
    case VK_OEM_7:      key = GFX_Key_Quote;        break;
  }
  
  return(key);
}

#endif

/*******************************
**       End GFX Win32        **
*******************************/
