// Data
static ID3D11Device*            g_pd3dDevice = nullptr;
static ID3D11DeviceContext*     g_pd3dDeviceContext = nullptr;
static IDXGISwapChain*          g_pSwapChain = nullptr;
static bool                     g_SwapChainOccluded = false;
static UINT                     g_ResizeWidth = 0, g_ResizeHeight = 0;
static ID3D11RenderTargetView*  g_mainRenderTargetView = nullptr;

// Forward declarations of helper functions
bool CreateDeviceD3D(HWND hWnd);
void CleanupDeviceD3D();
void CreateRenderTarget();
void CleanupRenderTarget();
LRESULT WINAPI ImGui_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// Helper functions
bool CreateDeviceD3D(HWND hWnd) {
  // Setup swap chain
  // This is a basic setup. Optimally could use e.g. DXGI_SWAP_EFFECT_FLIP_DISCARD and handle fullscreen mode differently. See #8979 for suggestions.
  DXGI_SWAP_CHAIN_DESC sd;
  ZeroMemory(&sd, sizeof(sd));
  sd.BufferCount = 2;
  sd.BufferDesc.Width = 0;
  sd.BufferDesc.Height = 0;
  sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
  sd.BufferDesc.RefreshRate.Numerator = 60;
  sd.BufferDesc.RefreshRate.Denominator = 1;
  sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
  sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
  sd.OutputWindow = hWnd;
  sd.SampleDesc.Count = 1;
  sd.SampleDesc.Quality = 0;
  sd.Windowed = TRUE;
  sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

  UINT createDeviceFlags = 0;
  //createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
  D3D_FEATURE_LEVEL featureLevel;
  const D3D_FEATURE_LEVEL featureLevelArray[2] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, };
  HRESULT res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
  if (res == DXGI_ERROR_UNSUPPORTED) // Try high-performance WARP software driver if hardware is not available.
      res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
  if (res != S_OK)
      return false;

  // Disable DXGI's default Alt+Enter fullscreen behavior.
  // - You are free to leave this enabled, but it will not work properly with multiple viewports.
  // - This must be done for all windows associated to the device. Our DX11 backend does this automatically for secondary viewports that it creates.
  IDXGIFactory* pSwapChainFactory;
  if (SUCCEEDED(g_pSwapChain->GetParent(IID_PPV_ARGS(&pSwapChainFactory)))) {
    pSwapChainFactory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER);
    pSwapChainFactory->Release();
  }

  CreateRenderTarget();
  return true;
}

void CleanupDeviceD3D() {
    CleanupRenderTarget();
    if (g_pSwapChain) { g_pSwapChain->Release(); g_pSwapChain = nullptr; }
    if (g_pd3dDeviceContext) { g_pd3dDeviceContext->Release(); g_pd3dDeviceContext = nullptr; }
    if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = nullptr; }
}

void CreateRenderTarget() {
  ID3D11Texture2D* pBackBuffer;
  g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
  g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &g_mainRenderTargetView);
  pBackBuffer->Release();
}

void CleanupRenderTarget() {
    if (g_mainRenderTargetView) { g_mainRenderTargetView->Release(); g_mainRenderTargetView = nullptr; }
}

// Forward declare message handler from imgui_impl_win32.cpp
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// Win32 message handler
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
LRESULT WINAPI ImGui_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
  if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
    return true;

  switch (msg) {
    case WM_SIZE: {
      if (wParam == SIZE_MINIMIZED)
          return 0;
      g_ResizeWidth = (UINT)LOWORD(lParam); // Queue resize
      g_ResizeHeight = (UINT)HIWORD(lParam);
      
    } return 0;
    case WM_SYSCOMMAND: {
      if ((wParam & 0xfff0) == SC_CLOSE || (wParam & 0xfff0) == SC_MINIMIZE) {
        Win32_Minimize_Window_To_Tray(get_window_info(hWnd));
        return 0;
      }
      if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu
          return 0;
    } break;
    case WM_CREATE: {
      // This is pretty dumb to have a single one for global state! See if we can attach it to Window_Info
    } return 0;
    case WM_TRAYICON: {
      // Most applications perform actions on button up (after click), but we do
      // on button down to make it feel as responsive as possible.
      if (lParam == WM_LBUTTONDOWN) {
        Win32_Bring_Window_To_Foreground(get_main_window_pointer());
      }
      if (lParam == WM_RBUTTONDOWN) {
        POINT curPos; GetCursorPos(&curPos);
        SetForegroundWindow(hWnd);
        Window_Info* info = get_window_info(hWnd);
        if (!info) return 0;
        u32 clicked = TrackPopupMenu(info->tray_icon_menu, TPM_RETURNCMD | TPM_NONOTIFY, curPos.x, curPos.y, 0, hWnd, nullptr);
        if (clicked == ICON_CONTEXT_MENU_ITEM_ID) {
          log("(WM_TRAYICON:Exit) Quitting application...");
          ::PostQuitMessage(0);
        }
      }
    } return 0;
    case WM_DESTROY: {
      ::PostQuitMessage(0);
    } return 0;
  } // case (msg)
  return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}
// ~ Keyboard Input
struct Key_Combination {
  ImGuiKey key = ImGuiKey_None;
  bool ctrl;
  bool shift;
  bool alt;
  bool once = true;
  
  bool triggered;
  
  f64 last_key_down_time;
};

struct KB_State {
  f64 current_frame_time;
  b32 ctrl_key_down;
  b32 shift_key_down;
  b32 alt_key_down;
};

global KB_State global_keyboard_state;

force_inline bool key_combination_failed(Key_Combination* kc) {
  return false;
}

bool is_ctrl_key_down ()  { return (ImGui::IsKeyDown(ImGuiKey_LeftCtrl)  || ImGui::IsKeyDown(ImGuiKey_RightCtrl));  }
bool is_shift_key_down () { return (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)); }
bool is_alt_key_down ()   { return (ImGui::IsKeyDown(ImGuiKey_LeftAlt)   || ImGui::IsKeyDown(ImGuiKey_RightAlt));   }

bool check_key_combination (Key_Combination* kc) {
  kc->triggered = false; // reset
  
  if (kc->last_key_down_time && ImGui::IsKeyReleased(kc->key)) {
    kc->last_key_down_time = 0.0;
  }
  
  if ( kc->ctrl  && !is_ctrl_key_down())  return key_combination_failed(kc);
  if (!kc->ctrl  &&  is_ctrl_key_down())  return key_combination_failed(kc);
  if ( kc->shift && !is_shift_key_down()) return key_combination_failed(kc);
  if (!kc->shift &&  is_shift_key_down()) return key_combination_failed(kc);
  if ( kc->alt   && !is_alt_key_down())   return key_combination_failed(kc);
  if (!kc->alt   &&  is_alt_key_down())   return key_combination_failed(kc);
  
  if (kc->once) {
    kc->triggered = (ImGui::IsKeyDown(kc->key) && kc->last_key_down_time == 0);
    if (kc->triggered) {
      kc->last_key_down_time = global_keyboard_state.current_frame_time;
    }
  } else {
    kc->triggered = (ImGui::IsKeyDown(kc->key));
  }
  
  if (kc->triggered) {
#if BUILD_DEBUG
    log("Key triggered: %s%s%s%s | once=%d, ts: %1.3f", 
      (kc->ctrl)  ? "ctrl+"  : "",
      (kc->shift) ? "shift+" : "",
      (kc->alt)   ? "alt+"   : "", 
      ImGui::GetKeyName(kc->key), kc->once, kc->last_key_down_time);
#endif
  }
  
  return kc->triggered;
}


void update_global_keyboard_state () {
  global_keyboard_state.current_frame_time = GetUnixTimestamp();
  global_keyboard_state.ctrl_key_down  = is_ctrl_key_down();
  global_keyboard_state.shift_key_down = is_shift_key_down();
  global_keyboard_state.alt_key_down   = is_alt_key_down();
}

// ~ ImGui Style
#include "../lib/third_party/dear-imgui/imgui_internal.h"
void Set_Custom_Style () {
  ImGuiStyle* style = &ImGui::GetStyle();
  ImVec4* colors = style->Colors;

  style->Alpha = 1.0;
  style->WindowRounding = 3.0f;
  style->GrabRounding   = 3.0f;
  style->GrabMinSize    = 20.0f;
  style->FrameRounding  = 8.0f;
  style->TabRounding    = 8.0f;

  colors[ImGuiCol_Text]                   = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
  colors[ImGuiCol_TextDisabled]           = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
  colors[ImGuiCol_WindowBg]               = ImVec4(0.06f, 0.06f, 0.06f, 0.94f);
  colors[ImGuiCol_ChildBg]                = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
  colors[ImGuiCol_PopupBg]                = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
  colors[ImGuiCol_Border]                 = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
  colors[ImGuiCol_BorderShadow]           = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
  colors[ImGuiCol_FrameBg]                = ImVec4(0.16f, 0.29f, 0.48f, 0.54f);
  colors[ImGuiCol_FrameBgHovered]         = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
  colors[ImGuiCol_FrameBgActive]          = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);
  colors[ImGuiCol_TitleBg]                = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
  colors[ImGuiCol_TitleBgActive]          = ImVec4(0.16f, 0.29f, 0.48f, 1.00f);
  colors[ImGuiCol_TitleBgCollapsed]       = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
  colors[ImGuiCol_MenuBarBg]              = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
  colors[ImGuiCol_ScrollbarBg]            = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
  colors[ImGuiCol_ScrollbarGrab]          = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
  colors[ImGuiCol_ScrollbarGrabHovered]   = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
  colors[ImGuiCol_ScrollbarGrabActive]    = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
  colors[ImGuiCol_CheckMark]              = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
  colors[ImGuiCol_SliderGrab]             = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);
  colors[ImGuiCol_SliderGrabActive]       = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
  colors[ImGuiCol_Button]                 = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
  colors[ImGuiCol_ButtonHovered]          = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
  colors[ImGuiCol_ButtonActive]           = ImVec4(0.06f, 0.53f, 0.98f, 1.00f);
  colors[ImGuiCol_Header]                 = ImVec4(0.26f, 0.59f, 0.98f, 0.31f);
  colors[ImGuiCol_HeaderHovered]          = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);
  colors[ImGuiCol_HeaderActive]           = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
  colors[ImGuiCol_Separator]              = colors[ImGuiCol_Border];
  colors[ImGuiCol_SeparatorHovered]       = ImVec4(0.10f, 0.40f, 0.75f, 0.78f);
  colors[ImGuiCol_SeparatorActive]        = ImVec4(0.10f, 0.40f, 0.75f, 1.00f);
  colors[ImGuiCol_ResizeGrip]             = ImVec4(0.26f, 0.59f, 0.98f, 0.20f);
  colors[ImGuiCol_ResizeGripHovered]      = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);
  colors[ImGuiCol_ResizeGripActive]       = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);
  colors[ImGuiCol_InputTextCursor]        = colors[ImGuiCol_Text];
  colors[ImGuiCol_TabHovered]             = colors[ImGuiCol_HeaderHovered];
  colors[ImGuiCol_Tab]                    = ImLerp(colors[ImGuiCol_Header],       colors[ImGuiCol_TitleBgActive], 0.80f);
  colors[ImGuiCol_TabSelected]            = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f);
  colors[ImGuiCol_TabSelectedOverline]    = colors[ImGuiCol_HeaderActive];
  colors[ImGuiCol_TabDimmed]              = ImLerp(colors[ImGuiCol_Tab],          colors[ImGuiCol_TitleBg], 0.80f);
  colors[ImGuiCol_TabDimmedSelected]      = ImLerp(colors[ImGuiCol_TabSelected],  colors[ImGuiCol_TitleBg], 0.40f);
  colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.50f, 0.50f, 0.50f, 0.00f);
  colors[ImGuiCol_DockingPreview]         = ImVec4(0.26f, 0.59f, 0.98f, 0.75f); //colors[ImGuiCol_HeaderActive] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f);
  colors[ImGuiCol_DockingEmptyBg]         = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
  colors[ImGuiCol_PlotLines]              = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
  colors[ImGuiCol_PlotLinesHovered]       = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
  colors[ImGuiCol_PlotHistogram]          = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
  colors[ImGuiCol_PlotHistogramHovered]   = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
  colors[ImGuiCol_TableHeaderBg]          = ImVec4(0.19f, 0.19f, 0.20f, 1.00f);
  colors[ImGuiCol_TableBorderStrong]      = ImVec4(0.31f, 0.31f, 0.35f, 1.00f);   // Prefer using Alpha=1.0 here
  colors[ImGuiCol_TableBorderLight]       = ImVec4(0.23f, 0.23f, 0.25f, 1.00f);   // Prefer using Alpha=1.0 here
  colors[ImGuiCol_TableRowBg]             = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
  colors[ImGuiCol_TableRowBgAlt]          = ImVec4(1.00f, 1.00f, 1.00f, 0.06f);
  colors[ImGuiCol_TextLink]               = colors[ImGuiCol_HeaderActive];
  colors[ImGuiCol_TextSelectedBg]         = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
  colors[ImGuiCol_TreeLines]              = colors[ImGuiCol_Border];
  colors[ImGuiCol_DragDropTarget]         = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
  colors[ImGuiCol_DragDropTargetBg]       = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
  colors[ImGuiCol_UnsavedMarker]          = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
  colors[ImGuiCol_NavCursor]              = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
  colors[ImGuiCol_NavWindowingHighlight]  = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
  colors[ImGuiCol_NavWindowingDimBg]      = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
  colors[ImGuiCol_ModalWindowDimBg]       = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
}

struct ImGui_Font_Size_Pair {
  f32 font_size;
  ImFont* data;
};

struct ImGui_Font_Info {
  string font_name;
  Array<ImGui_Font_Size_Pair> sizes;
  s64 current_size = 1;
};

// We can make a more complicated structure here with a Map<string, Font_Info>
// but we only have one font at this point, so it's redundant
global ImGuiContext* imgui_context;
global f32 imgui_font_sizes[6] = {18, 24, 30, 36, 42, 48};
global ImGui_Font_Info imgui_default_font;

bool ImGui_Default_Font_Loaded () {
  // #TODO #font_crash Should only check once at the top of the frame!
  return imgui_default_font.sizes.count > 0;
}

f32 ImGui_Default_Font_Current_Size () {
  return imgui_default_font.sizes[imgui_default_font.current_size].font_size;
}

void ImGui_Push_Default_Font () {
  if (ImGui_Default_Font_Loaded()) {
    ImFont* font_data = imgui_default_font.sizes[imgui_default_font.current_size].data;
    f32 unscaled_font_size = imgui_default_font.sizes[imgui_default_font.current_size].font_size;
    ImGui::PushFont(font_data, unscaled_font_size);
  }
}

void ImGui_Pop_Default_Font () {
  // #font_crash: This will break if we load font mid-frame! don't do this.
  // We should only check this once at the top of the frame!
  if (ImGui_Default_Font_Loaded()) {
    ImGui::PopFont();
  }
}

#define imgui_push_default_font_index(x) \
  ImGui_Font_Size Concat(_auto_push_font_, __LINE__)(x)

struct ImGui_Font_Size {
  ImGui_Font_Size (s64 size_index) {
    s64 size_target = clamp<s64>(size_index, 0, 5);
    ImFont* font_data = imgui_default_font.sizes[size_target].data;
    f32 unscaled_font_size = imgui_default_font.sizes[size_target].font_size;
    ImGui::PushFont(font_data, unscaled_font_size);
  }
  ~ImGui_Font_Size () {
    ImGui::PopFont();
  }
};

void ImGui_Show_Font_Info () {
  ImGui::Begin("Font Settings"); // imgui_push_default_font_index(0);
  if (!ImGui_Default_Font_Loaded()){ 
    ImGui::Text("Default font not present!");
  } else {
    ImGui::Text("Current font size: %.1f (index: %d)", ImGui_Default_Font_Current_Size(), 
      imgui_default_font.current_size);
    ImGui::SameLine();
    if (ImGui::Button("Decrease Font Size")) { imgui_default_font.current_size = clamp<s64>(imgui_default_font.current_size - 1, 0, 5); }
    ImGui::SameLine();
    if (ImGui::Button("Increase Font Size")) { imgui_default_font.current_size = clamp<s64>(imgui_default_font.current_size + 1, 0, 5); }
  }
  ImGui::End();
}

#define push_imgui_window(title) \
  Push_ImGui_Window Concat(_push_imgui_window_guard, __LINE__)(title)

struct Push_ImGui_Window {
  Push_ImGui_Window (string name) {
    ImGui::Begin((char*)name.data);
  }
  ~Push_ImGui_Window () {
    ImGui::End();
  }
};