#pragma comment(lib, "gdi32")

#define OS_W32_GraphicalWindowClassName L"ApplicationWindowClass"

root_function OS_InitGfxReceipt 
OS_gfx_init(OS_InitReceipt os_init_receipt)
{
  if (is_main_thread() && os_g_w32_gfx_state == 0)
  {

    {
      // Global state
      Arena *arena = m_make_arena_reserve(Gigabytes(1));
      os_g_w32_gfx_state = PushArray(arena, OS_W32_Gfx_State, 1);
      os_g_w32_gfx_state->arena = arena;
      os_g_w32_gfx_state->window_arena = m_make_arena_reserve(Gigabytes(1));
    }
    // TODO(antonl) DPI awareness

    // Register window class
    {
      /* WNDCLASSW window_class = { 0 }; */
      /* window_class.style = CS_HREDRAW | CS_VREDRAW; */
      /* window_class.lpfnWndProc = OS_W32_WindowProc; */
      /* window_class.hInstance = g_os_w32_hinstance; */
      /* window_class.lpszClassName = OS_W32_GraphicalWindowClassName; */
      /* window_class.hCursor = LoadCursor(0, IDC_ARROW); */
      /* RegisterClassW(&window_class); */
      WNDCLASSEXW window_class = {0};
      window_class.cbSize = sizeof(WNDCLASSEXW);
      window_class.lpfnWndProc = OS_W32_window_proc;
      window_class.hInstance = os_g_w32_hinstance;
      window_class.lpszClassName = OS_W32_GraphicalWindowClassName;
      if(!RegisterClassExW(&window_class))
      { 
        break_debugger();
      }
    }

    // Rjf makes a "global invisible window", but why?
    {
      os_g_w32_gfx_state->global_hwnd = CreateWindowExW(0,
                                                      OS_W32_GraphicalWindowClassName,
                                                      L"",
                                                      WS_OVERLAPPEDWINDOW,
                                                      100,100,
                                                      0,0,
                                                      0,0,
                                                      os_g_w32_hinstance, 0);
      os_g_w32_gfx_state->global_hdc = GetDC(os_g_w32_gfx_state->global_hwnd);
    }
  }
  
  OS_InitGfxReceipt out;
  out.u64[0] = 1;
  return out;
}

root_function OS_Handle
OS_W32_handle_from_window(OS_W32_Window *window)
{
  OS_Handle handle = { 0 };
  handle.u64[0] = (U64)window;
  return handle;
}

root_function OS_W32_Window*
OS_W32_window_from_handle(OS_Handle handle)
{
  OS_W32_Window *window = (OS_W32_Window *)handle.u64[0];
  return window;
}

root_function OS_Handle
OS_window_open(OS_Window_Flags flags, Vec2_S64 size, String8 title)
{
  OS_Handle handle = { 0 };

  {
    // Window allocation
    OS_W32_Window *window = os_g_w32_gfx_state->free_window;
    {
      // Windows are stored in a stack on the gfx state
      if (window != 0)
      {
        StackPop(os_g_w32_gfx_state->free_window);
      }
      else
      {
        window = PushArray(os_g_w32_gfx_state->window_arena, OS_W32_Window, 1);
      }
      MemoryZeroStruct(window);
      DLLPushBack(os_g_w32_gfx_state->first_window, os_g_w32_gfx_state->last_window, window);
    }

    // Open window
    HWND hwnd = 0;
    HDC hdc = 0;
    {
      ArenaTemp scratch = scratch_get(0, 0);
      String16 title16 = str16_from8(scratch.arena, title);
      hwnd = CreateWindowExW(0,
                            OS_W32_GraphicalWindowClassName, (LPCWSTR)title16.str,
                            WS_OVERLAPPEDWINDOW | WS_VISIBLE, 
                            100, 100,
                            size.x, size.y, 0, 0, os_g_w32_hinstance, 0);
      hdc = GetDC(hwnd);
      SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)window);
      scratch_release(scratch);
    }

    {
      window->hwnd = hwnd;
      window->hdc = hdc;
    }

    handle = OS_W32_handle_from_window(window);
  }
  return handle;
}

function LRESULT
OS_W32_window_proc(HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param)
{
  LRESULT result = 0;
  
  OS_Event *event = 0;
  OS_W32_Window *window = (OS_W32_Window *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
  OS_Handle window_handle = OS_W32_handle_from_window(window);
  ArenaTemp scratch = scratch_get(&os_w32_tl_events_arena, 1);
  OS_EventList fallback_event_list = {0};
  if(os_w32_tl_events_arena == 0)
  {
    os_w32_tl_events_arena = scratch.arena;
    os_w32_tl_events_list = &fallback_event_list;
  }
  
  B32 is_release = 0;
  Axis2 scroll_axis = Axis2_Y;
  switch(message)
  {
    default:
    {
      result = DefWindowProcW(hwnd, message, w_param, l_param);
    } break;
    
    //- General window events 
    case WM_CLOSE:
    {
      event = PushArray(os_w32_tl_events_arena, OS_Event, 1);
      event->kind = OS_EventKind_WindowClose;
      event->window = window_handle;
    } break;
    
    //- Mouse buttons 
    case WM_LBUTTONUP:
    case WM_MBUTTONUP:
    case WM_RBUTTONUP:
    {
      ReleaseCapture();
      is_release = 1;
    } fallthrough;
    case WM_LBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_RBUTTONDOWN:
    {
      if(is_release == 0)
      {
        SetCapture(hwnd);
      }
      OS_EventKind kind = is_release ? OS_EventKind_Release : OS_EventKind_Press;
      OS_Key key = OS_Key_MouseLeft;
      switch(message)
      {
        case WM_MBUTTONUP: case WM_MBUTTONDOWN: key = OS_Key_MouseMiddle; break;
        case WM_RBUTTONUP: case WM_RBUTTONDOWN: key = OS_Key_MouseRight; break;
      }
      event = PushArray(os_w32_tl_events_arena, OS_Event, 1);
      event->kind = kind;
      event->window = window_handle;
      event->key = key;
      event->position = OS_mouse_from_window(window_handle);
    } break;
    
    //- Keyboard events
    case WM_SYSKEYDOWN: case WM_SYSKEYUP:
    {
      result = DefWindowProcW(hwnd, message, w_param, l_param);
    } fallthrough;
    case WM_KEYDOWN: 
    case WM_KEYUP:
    {
      // TODO(anton): Just check this thing with was down, is down.., WINAPI crap
      B32 was_down = !!(l_param & (1 << 30));
      B32 is_down  =  !(l_param & (1 << 31));
      OS_EventKind kind = is_down ? OS_EventKind_Press : OS_EventKind_Release;
      
      // TODO(anton): Here we use statics but maybe we should not...
      // Could just move this out and pre-init or generate it and include or whatever...
      // probably should just be in some meta header.
      local_persist OS_Key key_table[256] = {0};
      local_persist B32 key_table_initialised = 0;
      if(!key_table_initialised)
      {
        key_table_initialised = 1;
        
        for (U32 i = 'A', j = OS_Key_A; i <= 'Z'; i += 1, j += 1)
        {
          key_table[i] = (OS_Key)j;
        }
        for (U32 i = '0', j = OS_Key_0; i <= '9'; i += 1, j += 1)
        {
          key_table[i] = (OS_Key)j;
        }
        for (U32 i = VK_F1, j = OS_Key_F1; i <= VK_F24; i += 1, j += 1)
        {
          key_table[i] = (OS_Key)j;
        }
        
        key_table[VK_ESCAPE]        = OS_Key_Esc;
        key_table[VK_OEM_3]         = OS_Key_GraveAccent;
        key_table[VK_OEM_MINUS]     = OS_Key_Minus;
        key_table[VK_OEM_PLUS]      = OS_Key_Equal;
        key_table[VK_BACK]          = OS_Key_Backspace;
        key_table[VK_TAB]           = OS_Key_Tab;
        key_table[VK_SPACE]         = OS_Key_Space;
        key_table[VK_RETURN]        = OS_Key_Enter;
        key_table[VK_CONTROL]       = OS_Key_Ctrl;
        key_table[VK_SHIFT]         = OS_Key_Shift;
        key_table[VK_MENU]          = OS_Key_Alt;
        key_table[VK_UP]            = OS_Key_Up;
        key_table[VK_LEFT]          = OS_Key_Left;
        key_table[VK_DOWN]          = OS_Key_Down;
        key_table[VK_RIGHT]         = OS_Key_Right;
        key_table[VK_DELETE]        = OS_Key_Delete;
        key_table[VK_PRIOR]         = OS_Key_PageUp;
        key_table[VK_NEXT]          = OS_Key_PageDown;
        key_table[VK_HOME]          = OS_Key_Home;
        key_table[VK_END]           = OS_Key_End;
        key_table[VK_OEM_2]         = OS_Key_ForwardSlash;
        key_table[VK_OEM_PERIOD]    = OS_Key_Period;
        key_table[VK_OEM_COMMA]     = OS_Key_Comma;
        key_table[VK_OEM_7]         = OS_Key_Quote;
        key_table[VK_OEM_4]         = OS_Key_LeftBracket;
        key_table[VK_OEM_6]         = OS_Key_RightBracket;
        key_table[VK_INSERT]        = OS_Key_Insert;
        key_table[VK_OEM_1]         = OS_Key_Semicolon;
      }
      
      OS_Key key = OS_Key_Null;
      if(w_param < ArrayCount(key_table))
      {
        key = key_table[w_param];
      }
      
      event = PushArray(os_w32_tl_events_arena, OS_Event, 1);
      event->kind = kind;
      event->window = window_handle;
      event->key = key;
    } break;
    
    
  }
  // If we registered an event we push it to the event list.
  if(event)
  {
    DLLPushBack(os_w32_tl_events_list->first, os_w32_tl_events_list->last, event);
    os_w32_tl_events_list->count += 1;
  }
  
  scratch_release(scratch);
  return result;
}

root_function Vec2_F32
OS_mouse_from_window(OS_Handle handle)
{
  Vec2_F32 result = vec2_F32(-100, -100);
  OS_W32_Window *window = OS_W32_window_from_handle(handle);
  if(window != 0)
  {
    POINT point;
    if(GetCursorPos(&point))
    {
      if(ScreenToClient(window->hwnd, &point))
      {
        result = vec2_F32(point.x, point.y);
      }
    }
  }
  
  return result;
}

root_function OS_EventList
OS_get_events(Arena* arena)
{
  OS_EventList list = {0};
  os_w32_tl_events_arena = arena;
  os_w32_tl_events_list = &list;
  for(MSG message; PeekMessage(&message, 0, 0, 0, PM_REMOVE);)
  {
    TranslateMessage(&message);
    DispatchMessage(&message);
  }
  os_w32_tl_events_arena = 0;
  os_w32_tl_events_list = 0;
  
  return list;
}

root_function void
OS_consume_event(OS_EventList *events, OS_Event *event)
{
  DLLRemove(events->first, events->last, event);
  events->count -= 1;
  event->kind = OS_EventKind_Null;
}

root_function void
OS_window_first_paint(OS_Handle handle)
{
  ArenaTemp scratch = scratch_get(0,0);
  OS_W32_Window *window = OS_W32_window_from_handle(handle);
  ShowWindow(window->hwnd, SW_SHOW);
  UpdateWindow(window->hwnd);
  scratch_release(scratch);
}