////////////////////////////////
// NOTE(allen): APIs for All

link_function 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);
}


////////////////////////////////
// NOTE(allen): APIs for Applications

global B32        w32_is_initialized = 0;
global W32_Window w32_window_slots[GFX_MAX_WINDOW_COUNT];
global W32_Window*w32_window_free = 0;

global 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))

global B32 hack_peek_input_to_fail = 0;

link_function 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 return result = 1;)
    }break;
#endif
    
    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:
    {
      hack_peek_input_to_fail = 1;
    }break;
    
    default:
    {
      result = DefWindowProcW(wnd, msg, wparam, lparam);
    }break;
  }
  return(result);
}

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

link_function 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(str8_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);
}

link_function void
gfx_set_resizing_hook(GFX_ResizingFunc *func){
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  // main path
  else{
    w32_resizing_func = func;
  }
}

link_function GFX_ResizingFunc*
gfx_get_resizing_hook(void){
  GFX_ResizingFunc *result = 0;
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  // main path
  else{
    result = w32_resizing_func;
  }
  return(result);
}

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

link_function GFX_Window*
gfx_window_create(void){
  B32 error = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
    error = 1;
  }
  
  // allocate slot
  W32_Window *slot_ptr = 0;
  if (!error){
    slot_ptr = w32_window_free;
    if (slot_ptr == 0){
      er_emit(str8_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(str8_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);
}

link_function void
gfx_window_destroy(GFX_Window *window){
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    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);
      }
    }
  }
}

link_function B32
gfx_window_is_valid(GFX_Window *window){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      if (slot->wnd != 0){
        result = 1;
      }
    }
  }
  
  return(result);
}

link_function B32
gfx_window_set_visible(GFX_Window *window, B32 visible){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    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);
}

link_function B32
gfx_window_set_title(GFX_Window *window, String8 title){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    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);
}

link_function B32
gfx_window_set_inner_rect(GFX_Window *window, RectS32 *rect){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    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->x0;
      r.top    = rect->y0;
      r.right  = rect->x1;
      r.bottom = rect->y1;
      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);
}

link_function B32
gfx_window_get_inner_rect(GFX_Window *window, RectS32 *out){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    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))){
            out->x0 = rect.left;
            out->y0 = rect.top;
            out->x1 = rect.right;
            out->y1 = rect.bottom;
            result = 1;
          }
        }
      }
    }
  }
  
  return(result);
}

link_function B32
gfx_window_get_outer_rect(GFX_Window *window, RectS32 *out){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      RECT rect = {0};
      if (GetWindowRect(slot->wnd, &rect)){
        out->x0 = rect.left;
        out->y0 = rect.top;
        out->x1 = rect.right;
        out->y1 = rect.bottom;
        result = 1;
      }
    }
  }
  
  return(result);
}

link_function B32
gfx_window_set_resizable(GFX_Window *window, B32 resizable){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    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);
}

link_function B32
gfx_window_set_full_screen(GFX_Window *window, B32 full_screen){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    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);
}

link_function B32
gfx_window_is_full_screen(GFX_Window *window){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    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);
}

link_function B32
gfx_window_is_minimized(GFX_Window *window){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      result = IsIconic(slot->wnd);
    }
  }
  
  return(result);
}

link_function B32
gfx_window_is_maximized(GFX_Window *window){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      result = IsZoomed(slot->wnd);
    }
  }
  
  return(result);
}

link_function B32
gfx_window_set_user_data(GFX_Window *window, void *ptr){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    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);
}

link_function void*
gfx_window_get_user_data(GFX_Window *window){
  void *result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      result = slot->user_data;
    }
  }
  
  return(result);
}

link_function void*
gfx_window_get_equipped_data(GFX_Window *window){
  void *result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    if (w32_handle_is_valid(window)){
      W32_Window *slot = w32_slot_from_handle(window);
      result = slot->equipped_data;
    }
  }
  
  return(result);
}

link_function B32
gfx_wait_for_input(void){
  B32 result = 1;
  MSG msg = {0};
  BOOL get_message = GetMessageW(&msg, 0, 0, 0);
  if (get_message <= 0){
    result = 0;
  }
  else{
    DispatchMessage(&msg);
  }
  return(result);
}

link_function B32
gfx_peek_input(void){
  B32 result = 1;
  if (hack_peek_input_to_fail){
    result = 0;
  }
  else{
    MSG msg = {0};
    for (;PeekMessageW(&msg, 0, 0, 0, PM_REMOVE);){
      DispatchMessage(&msg);
    }
  }
  return(result);
}

link_function U64
gfx_window_refresh_rate(GFX_Window *window){
  U64 result = 60;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    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);
}

////////////////////////////////
// NOTE(allen): APIs for Graphics System Implementors

link_function B32
gfx_window_set_equipped_data(GFX_Window *window, void *ptr,
                             GFX_WindowReleaseFunc *release_func){
  B32 result = 0;
  
  // api check
  if (!gfx_is_inited()){
    er_emit(str8_lit("gfx layer is not initialized"));
  }
  
  // main path
  else{
    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);
}


////////////////////////////////
// NOTE(allen): W32 Graphics Functions

link_function W32_Window*
gfx_w32_window_from_handle(GFX_Window *window){
  // NOTE(allen): 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);
}

link_function GFX_Window*
gfx_w32_handle_from_hwnd(HWND wnd){
  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);
}
