#include "mr4th_osw.win32.h"

////////////////////////////////
// Win32 Ctx

static OSW_W32_Ctx osw__w32_ctx = {0};

////////////////////////////////
// Functions: OSW Win32

MR4TH_SYMBOL_STATIC void
osw_w32__update_window_visibility(OSW_Window *window){
  if (!window->is_visible){
    ShowWindow(window->hwnd, SW_HIDE);
  }
  else{
    if (window->is_minimized){
      ShowWindow(window->hwnd, SW_SHOWMINIMIZED);
    }
    else if (window->is_maximized){
      ShowWindow(window->hwnd, SW_SHOWMAXIMIZED);
    }
    else{
      ShowWindow(window->hwnd, SW_SHOWNORMAL);
    }
  }
}

MR4TH_SYMBOL_STATIC OSW_Window*
osw_w32__window_from_hwnd(HWND hwnd){
  LONG_PTR r = GetWindowLongPtrW(hwnd, GWLP_USERDATA);
  OSW_Window *window = (OSW_Window*)PtrFromInt(r);
  return(window);
}

MR4TH_SYMBOL_STATIC LRESULT
osw_w32__window_proc(HWND   hwnd,   UINT   msg,
                     WPARAM wparam, LPARAM lparam){
  LRESULT result = 0;
  switch (msg){
    case WM_CREATE: {
      CREATESTRUCTW *create = (CREATESTRUCTW*)lparam;
      SetWindowLongPtrW(hwnd, GWLP_USERDATA, IntFromPtr(create->lpCreateParams));
    }break;
    
    case WM_CLOSE: {
      ExitProcess(0);
    }break;
    
    case WM_GETMINMAXINFO: {
      MINMAXINFO *minmaxinfo = (MINMAXINFO*)lparam;
      OSW_Window *window = osw_w32__window_from_hwnd(hwnd);
      if (window != 0){
        minmaxinfo->ptMinTrackSize.x = window->mmbox.x0;
        minmaxinfo->ptMinTrackSize.y = window->mmbox.y0;
        minmaxinfo->ptMaxTrackSize.x = window->mmbox.x1;
        minmaxinfo->ptMaxTrackSize.y = window->mmbox.y1;
      }
    }break;
    
    case WM_SIZE: {
      // TODO(allen): figure out window resizing mechanics
    }break;
    
    default: {
      result = DefWindowProcW(hwnd, msg, wparam, lparam);
    }break;
  }
  return(result);
}

////////////////////////////////
// Win32 Implementation: OSW Functions

#define OSW_W32__GRAPHICS_CLASS L"mr4th-graphics"

// global

MR4TH_SYMBOL void
osw_init(void){
  B32 error = 0;
  
  // register graphics class
  if (!error){
    WNDCLASSW wnd_class = {0};
    wnd_class.lpfnWndProc = osw_w32__window_proc;
    wnd_class.hInstance = w32_get_instance();
    wnd_class.lpszClassName = OSW_W32__GRAPHICS_CLASS;
    ATOM graphics_atom = RegisterClassW(&wnd_class);
    if (graphics_atom == 0){
      er_emit_lit("osw: failed to create graphics window class");
      error = 1;
    }
  }
  
  // timeBeginPeriod
  if (!error){
    timeBeginPeriod(1);
  }
}

MR4TH_SYMBOL B32
osw_has_capability(OSW_Cap cap){
  B32 result = 0;
  switch (cap){
    case OSW_Cap_AbsoluteCoordinates: 
    case OSW_Cap_CustomBorders: {
      result = 1;
    }break;
  }
  return(result);
}

MR4TH_SYMBOL void
osw_tick(Arena *arena, B32 block){
  osw_event__write_begin(arena);
  
  if (block){
    WaitMessage();
  }
  
  MSG msg = {0};
  for (;PeekMessageW(&msg, 0, 0, 0, PM_REMOVE);){
    TranslateMessage(&msg);
    DispatchMessageW(&msg);
  }
}

#define OSW_W32__HCURSOR_XLIST(X) \
X(IDC_ARROW)   X(IDC_HAND)   X(IDC_IBEAM)  X(IDC_WAIT)     X(IDC_CROSS)    \
X(IDC_SIZEALL) X(IDC_SIZENS) X(IDC_SIZEWE) X(IDC_SIZENWSE) X(IDC_SIZENESW)

enum{
  OSW_W32__HCURSOR_NULL = 0,
#define X(N) OSW_W32__HCURSOR_##N,
  OSW_W32__HCURSOR_XLIST(X)
#undef X
  OSW_W32__HCURSOR_COUNT
};

#define OSW_W32__CURSOR_SHAPE_XLIST(X) \
X(Hidden,     NULL) \
X(Default,    IDC_ARROW) \
X(Pointer,    IDC_HAND)  \
X(TextInsert, IDC_IBEAM) \
X(Loading,    IDC_WAIT)  \
X(CrossHair,  IDC_CROSS) \
X(Move,       IDC_SIZEALL) \
X(Resize_T,   IDC_SIZENS) \
X(Resize_B,   IDC_SIZENS) \
X(Resize_L,   IDC_SIZEWE) \
X(Resize_R,   IDC_SIZEWE) \
X(Resize_TL,  IDC_SIZENWSE) \
X(Resize_TR,  IDC_SIZENESW) \
X(Resize_BL,  IDC_SIZENESW) \
X(Resize_BR,  IDC_SIZENWSE)

MR4TH_SYMBOL void
osw_set_cursor_shape(OSW_CursorShape shape){
  if (shape < OSW_CursorShape_COUNT){
    // TODO(allen): probably some way to clean this up
    B32 has_mouse = 0;
    V2S32 mp = osw_get_absolute_cursor_pos();
    for (OSW_Window *window = osw_window__first();
         window != 0; window = window->base.next){
      RectS32 rect = osw_window_get_absolute_inner_rect(window);
      if (rect.x0 <= mp.x && mp.x < rect.x1 &&
          rect.y0 <= mp.y && mp.y < rect.y1){
        has_mouse = 1;
        break;
      }
    }
    
    if (has_mouse){
      // setup hcursors
      static HCURSOR hcursors[OSW_W32__HCURSOR_COUNT] = {0};
      if (hcursors[1] == 0){
#define X(N) hcursors[OSW_W32__HCURSOR_##N] = LoadCursorA(0, N);
        OSW_W32__HCURSOR_XLIST(X)
#undef X
      }
      
      // map shape to index into hcursors
      U32 cursor_idx = 0;
      switch (shape){
#define X(N,R) case OSW_CursorShape_##N: cursor_idx = OSW_W32__HCURSOR_##R; break;
        OSW_W32__CURSOR_SHAPE_XLIST(X)
#undef X
      }
      
      // set cursor
      SetCursor(hcursors[cursor_idx]);
    }
  }
}

// windows

MR4TH_SYMBOL OSW_Window*
osw_window_create(V2S32 size){
  OSW_Window *window = osw_window__alloc();
  if (window != 0){
    HINSTANCE instance = w32_get_instance();
    window->hwnd = CreateWindowW(OSW_W32__GRAPHICS_CLASS,
                                 L"untitled", WS_TILEDWINDOW,
                                 CW_USEDEFAULT, CW_USEDEFAULT, /* x,y */
                                 size.x, size.y, /* w,h */
                                 0, 0, instance, window
                                 /* parent, menu, inst, param */);
    
    window->mmbox.x0 = 1;
    window->mmbox.y0 = 1;
    window->mmbox.x1 = (1 << 30);
    window->mmbox.y1 = (1 << 30);
  }
  return(window);
}

MR4TH_SYMBOL void
osw_window_destroy(OSW_Window *window){
  if (window != 0){
    DestroyWindow(window->hwnd);
  }
}

MR4TH_SYMBOL void
osw_window_set_title(OSW_Window *window, String8 str){
  if (window != 0){
    ArenaTemp scratch = arena_get_scratch(0, 0);
    String16 str16 = str16_from_str8(scratch.arena, str);
    SetWindowTextW(window->hwnd, (WCHAR*)str16.str);
    arena_release_scratch(&scratch);
  }
}

MR4TH_SYMBOL void
osw_window_set_min_size(OSW_Window *window, V2S32 size){
  window->mmbox.x0 = ClampBot(1, size.x);
  window->mmbox.y0 = ClampBot(1, size.y);
}

MR4TH_SYMBOL void
osw_window_set_max_size(OSW_Window *window, V2S32 size){
  window->mmbox.x1 = ClampTop(size.x, (1 << 30));
  window->mmbox.y1 = ClampTop(size.y, (1 << 30));
}

MR4TH_SYMBOL void
osw_window_set_visible(OSW_Window *window, B32 visible){
  B32 changed = (visible != window->is_visible);
  window->is_visible = visible;
  if (changed){
    osw_w32__update_window_visibility(window);
  }
}

#define OSW_W32__WS_BORDERED (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
#define OSW_W32__WS_RESIZABLE (WS_THICKFRAME | WS_MAXIMIZEBOX)

MR4TH_SYMBOL void
osw_window_set_fullscreen(OSW_Window *window, B32 fullscreen){
  DWORD style = GetWindowLongW(window->hwnd, GWL_STYLE);
  
  B32 is_fullscreen = !(style & WS_TILEDWINDOW);
  if (fullscreen != is_fullscreen){
    if (fullscreen){
      window->placement.length = sizeof(window->placement);
      if (GetWindowPlacement(window->hwnd, &window->placement)){
        HMONITOR monitor = MonitorFromWindow(window->hwnd, MONITOR_DEFAULTTOPRIMARY);
        if (monitor != 0){
          MONITORINFO monitor_info = {0};
          monitor_info.cbSize = sizeof(monitor_info);
          if (GetMonitorInfoW(monitor, &monitor_info)){
            SetWindowLongW(window->hwnd, GWL_STYLE, style & ~WS_TILEDWINDOW);
            SetWindowPos(window->hwnd, HWND_TOP,
                         monitor_info.rcMonitor.left,
                         monitor_info.rcMonitor.top,
                         monitor_info.rcMonitor.right  - monitor_info.rcMonitor.left,
                         monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top,
                         SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
          }
        }
      }
    }
    
    if (!fullscreen){
      DWORD new_style = style | OSW_W32__WS_BORDERED;
      if (window->is_resizable){
        new_style |= OSW_W32__WS_RESIZABLE;
      }
      SetWindowLongW(window->hwnd, GWL_STYLE, new_style);
      SetWindowPlacement(window->hwnd, &window->placement);
      SetWindowPos(window->hwnd, 0, 0, 0, 0, 0,
                   SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
                   SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
    }
  }
}

MR4TH_SYMBOL void
osw_window_set_maximize(OSW_Window *window, B32 maximize){
  B32 changed = (maximize != window->is_maximized);
  window->is_maximized = maximize;
  if (changed){
    osw_w32__update_window_visibility(window);
  }
}

MR4TH_SYMBOL void
osw_window_set_minimize(OSW_Window *window){
  B32 changed = (window->is_minimized == 0);
  window->is_maximized = 0;
  window->is_minimized = 1;
  if (changed){
    osw_w32__update_window_visibility(window);
  }
}

MR4TH_SYMBOL V2S32
osw_window_get_size(OSW_Window *window){
  V2S32 result = {0};
  RECT rect = {0};
  if (GetClientRect(window->hwnd, &rect)){
    result.x = rect.right - rect.left;
    result.y = rect.bottom - rect.top;
  }
  return(result);
}

MR4TH_SYMBOL B32
osw_window_is_visible(OSW_Window *window){
  return(window->is_visible);
}

MR4TH_SYMBOL B32
osw_window_is_fullscreen(OSW_Window *window){
  return(window->is_fullscreen);
}

MR4TH_SYMBOL B32
osw_window_is_maximized(OSW_Window *window){
  return(window->is_maximized);
}

MR4TH_SYMBOL B32
osw_window_is_minimized(OSW_Window *window){
  return(window->is_minimized);
}

// CAP: absolute positions

MR4TH_SYMBOL V2S32
osw_get_absolute_cursor_pos(void){
  POINT p = {0};
  GetCursorPos(&p);
  V2S32 r = {p.x, p.y};
  return(r);
}

MR4TH_SYMBOL RectS32
osw_window_get_absolute_outer_rect(OSW_Window *window){
  RectS32 result = {0};
  RECT rect = {0};
  if (GetWindowRect(window->hwnd, &rect)){
    result.x0 = rect.left;
    result.y0 = rect.top;
    result.x1 = rect.right;
    result.y1 = rect.bottom;
  }
  return(result);
}

MR4TH_SYMBOL RectS32
osw_window_get_absolute_inner_rect(OSW_Window *window){
  RectS32 result = {0};
  RECT rect = {0};
  if (GetClientRect(window->hwnd, &rect)){
    if (ClientToScreen(window->hwnd, (POINT*)(&rect.left))){
      if (ClientToScreen(window->hwnd, (POINT*)(&rect.right))){
        result.x0 = rect.left;
        result.y0 = rect.top;
        result.x1 = rect.right;
        result.y1 = rect.bottom;
      }
    }
  }
  return(result);
}

// CAP: custom borders

MR4TH_SYMBOL void
osw_window_set_custom_border(OSW_Window *window, B32 custom_border){
  window->has_custom_border = custom_border;
}

MR4TH_SYMBOL void
osw_window_frame_custom_border_size(OSW_Window *window, RectS32 border_size){
  window->border_size = border_size;
}
