// #TODO: #OS_Win32
// [ ] #Exception handling code in `Win32_Exception_Filter`
// [~] #Thread cleanup: in `thread_deinit` is there any requirement to cleanup child threads?
  // - I think: no? Threads should handle their own lifetimes, and the parent threads should ensure child threads are complete
  // before terminating.
  // Or we can move child threads up to the parent?

constexpr s64 FILETIME_TO_UNIX = 116444736000000000i64;
f64 GetUnixTimestamp () {
    FILETIME fileTime;
    GetSystemTimePreciseAsFileTime(&fileTime);
    s64 ticks = ((s64)fileTime.dwHighDateTime << (s64)32) | (s64)fileTime.dwLowDateTime;
    return (ticks - FILETIME_TO_UNIX) / (10.0 * 1000.0 * 1000.0);
}
s64 GetUnixTimestampNanoseconds () {
    FILETIME fileTime;
    GetSystemTimePreciseAsFileTime(&fileTime);
    
    s64 ticks = ((s64)fileTime.dwHighDateTime << (s64)32) 
                | (s64)fileTime.dwLowDateTime;  // in 100ns ticks
    s64 unix_time = (ticks - FILETIME_TO_UNIX); // in 100ns ticks
    
    s64 unix_time_nanoseconds = unix_time * 100;
    
    return unix_time_nanoseconds;
}

u64 FILETIME_to_ticks (FILETIME fileTime) {
  u64 ticks = ((u64)fileTime.dwHighDateTime << (u64)32) 
                | (u64)fileTime.dwLowDateTime;  // in 100ns ticks
  return ticks;
}

string format_time_datetime (FILETIME ft) {
  SYSTEMTIME stUTC, st;
  FileTimeToSystemTime(&ft, &stUTC);
  SystemTimeToTzSpecificLocalTime(nullptr, &stUTC, &st);
  
  return format_string("%04u-%02u-%02u %02u:%02u:%02u.%03u",
             st.wYear,
             st.wMonth,
             st.wDay,
             st.wHour,
             st.wMinute,
             st.wSecond,
             st.wMilliseconds);
}

struct OS_System_Info {
  // #cpuid
  s32 logical_processor_count;
  s32 physical_core_count;
  s32 primary_core_count;
  s32 secondary_core_count; // Any weaker or "Efficiency" cores.
  u64 page_size;
  u64 large_page_size;
  u64 allocation_granularity;
  string machine_name;
  
  // #Monitors
  b32 monitors_enumerated;
  Array<Monitor> monitors; // Back with default_allocator
  
  // #Drives
  Table<string, OS_Drive*> drives; // should we just store ptrs to OS_Drive? I think so..
  // That way we can fetch the OS_Drive* and have the pointer be stable. Especially if it's b
  // backed by an arena.
};

struct OS_Process_Info {
  u32 process_id;
  b32 large_pages_allowed;
  string binary_path;
  string working_path;
  string user_program_data_path;
  Array<string> module_load_paths;
  Array<string> environment_paths;
  
  b32 window_class_initialized;
  Arena* event_arena;
  Array<Window_Info> windows;
};

struct OS_State_Win32 {
  Arena* arena;
  
  OS_System_Info system_info;
  OS_Process_Info process_info;
};

global OS_State_Win32 global_win32_state;
internal b32 global_win32_is_quiet = 0; // No console output (`quiet` flag passed)


Table<string, OS_Drive*>* get_drive_table () {
  return &global_win32_state.system_info.drives;
}

ArrayView<OS_Drive*> os_get_available_drives () {
  // @Allocates: Recommended to set context allocator to temp().
  auto drive_table = get_drive_table();
  Array<OS_Drive*> drives;
  
  // #hash_table_iterator : instead of writing this everywhere, just use this function
  // to get an Array of drives.
  for (s64 i = 0; i < drive_table->allocated; i += 1) {
    Table_Entry<string, OS_Drive*>* entry = &drive_table->entries[i]; // we should take ptr here if we want to modify?
    
    if (entry->hash > HASH_TABLE_FIRST_VALID_HASH) {
      if (entry->value->label.data == nullptr || !entry->value->is_present) continue; // Some volumes may not be real and therefore have no label.
      
      array_add(drives, entry->value);
    }
  }
  
  return drives;
}

OS_Drive* get_drive_from_label (string drive_label) {
  // #TODO: Validate the input is in the correct format. We're looking for something like `C:\`
  // not just the drive letter!
  Table<string, OS_Drive*>* drive_table = get_drive_table();
  OS_Drive** drive_ptr = table_find_pointer(drive_table, drive_label);
  Assert(drive_ptr != nullptr);
  return *drive_ptr;
}

internal LONG WINAPI Win32_Exception_Filter (EXCEPTION_POINTERS* exception_ptrs) {
  if (global_win32_is_quiet) { ExitProcess(1); }

  local_persist volatile LONG first = 0;
  if(InterlockedCompareExchange(&first, 1, 0) != 0)
  { // prevent failures in other threads to popup same message box
    // this handler just shows first thread that crashes
    // we are terminating afterwards anyway
    for (;;) Sleep(1000);
  }
  
  // #TODO: Runtime assertion failed?
  
  // #Exception handling code (TODO)
  if (thread_context()->stack_trace) {
    os_write_string_unsynchronized("\n[Win32_Exception_Filter] Stack Trace\n", true);
    print_stack_trace();
  }
  
  ExitProcess(1);
  
  return 0;
}

// internal void Main_Entry_Point (int argc, WCHAR **argv);
internal void Win32_Entry_Point (int argc, WCHAR **argv) { stack_trace();
  os_write_string_unsynchronized("Fatal Error!\n\nStack trace: ", true);
  print_stack_trace();

  // Timed_Block_Print("Win32_Entry_Point");
  // See: w32_entry_point_caller(); (raddebugger)
  SetUnhandledExceptionFilter(&Win32_Exception_Filter);
  
  SYSTEM_INFO sysinfo = {0};
  GetSystemInfo(&sysinfo);
  
  // Try to allow large pages if we can.
  // b32 large_pages_allowed = 0;
  // {
  //   HANDLE token;
  //   if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))
  //   {
  //     LUID luid;
  //     if(LookupPrivilegeValue(0, SE_LOCK_MEMORY_NAME, &luid))
  //     {
  //       TOKEN_PRIVILEGES priv;
  //       priv.PrivilegeCount           = 1;
  //       priv.Privileges[0].Luid       = luid;
  //       priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  //       large_pages_allowed = !!AdjustTokenPrivileges(token, 0, &priv, sizeof(priv), 0, 0);
  //     }
  //     CloseHandle(token);
  //   }
  // }
  
  push_arena(thread_context()->arena);
  
  { OS_System_Info* info = &global_win32_state.system_info;
    info->logical_processor_count = (s32)sysinfo.dwNumberOfProcessors;
    info->page_size               = sysinfo.dwPageSize;
    info->large_page_size         = GetLargePageMinimum();
    info->allocation_granularity  = sysinfo.dwAllocationGranularity;
  }
  { OS_Process_Info* info = &global_win32_state.process_info;
    info->large_pages_allowed = false;
    info->process_id = GetCurrentProcessId();
  }
  // #cpuid
  { OS_System_Info* info = &global_win32_state.system_info;
    u32 length = 0;
    GetLogicalProcessorInformationEx(RelationProcessorCore, nullptr, (PDWORD)&length);
    u8* cpu_information_buffer = NewArray<u8>(length);
    GetLogicalProcessorInformationEx(RelationProcessorCore, // *sigh* 
      (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)cpu_information_buffer, (PDWORD)&length);
    
    u32 offset = 0;
    
    u32 all_cpus_count = 0; // all logical cpus
    u32 physical_cpu_count = 0;
    u32 max_performance = 0;
    u32 performance_core_count = 0;
    
    while (offset < length) {
      SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* cpu_information 
        = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(cpu_information_buffer + offset);
      
      offset += cpu_information->Size;
      
      u32 count_per_group_physical = 1;
      u32 value = (u32)cpu_information->Processor.GroupMask->Mask;
      u32 count_per_group = __popcnt(value); // logical
      if (cpu_information->Relationship != RelationProcessorCore) continue;
      
      if (cpu_information->Processor.EfficiencyClass > max_performance) {
        max_performance = cpu_information->Processor.EfficiencyClass;
        performance_core_count = count_per_group_physical;
      } else if (cpu_information->Processor.EfficiencyClass == max_performance) {
        performance_core_count += count_per_group_physical;
      }
      
      physical_cpu_count += count_per_group_physical;
      all_cpus_count += count_per_group;  
    }
  
    info->physical_core_count = (s32)physical_cpu_count;
    info->primary_core_count  = (s32)performance_core_count;
    info->secondary_core_count = info->physical_core_count - info->primary_core_count;
  } 

  { OS_System_Info* info = &global_win32_state.system_info;
    info->monitors.allocator = default_allocator();
    u8 buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0};
    DWORD size = MAX_COMPUTERNAME_LENGTH + 1;
    if(GetComputerNameA((char*)buffer, &size)) {
      string machine_name_temp = string(size, buffer);
      info->machine_name = copy_string(machine_name_temp);
    }
  }
  
  { OS_Process_Info* info = &global_win32_state.process_info;
    info->windows.allocator = default_allocator();    
    DWORD length = GetCurrentDirectoryW(0, 0);
    // This can be freed later when we call temp_reset();
    u16* memory = NewArray<u16>(temp(), length + 1);
    length = GetCurrentDirectoryW(length + 1, (WCHAR*)memory);
    info->working_path = wide_to_utf8(memory, length);
    Assert(is_valid(info->working_path));
  }
  
  // Setup event arena, allocators for Array<> types.
  if (!global_win32_state.process_info.event_arena) {
    global_win32_state.process_info.event_arena = bootstrap_arena(Arena_Reserve::Size_64K);
  }
  
  // [ ] Get Working directory (info->working_path)
  // [ ] GetEnvironmentStringsW
  temp_reset();
}

C_LINKAGE DWORD OS_Windows_Thread_Entry_Point (void* parameter) {
  Thread* thread = (Thread*)parameter;

  set_thread_context(thread->context);
  
  DWORD result = (DWORD)thread->proc(thread);
  
  return result;
}

// Individual Thread API
#define thread_task(T) (T*)thread->data;

internal bool thread_init (Thread* thread, Thread_Proc proc, string thread_name) {
  Assert(thread != nullptr && proc != nullptr);
  
  DWORD windows_thread_id = 0;

  HANDLE windows_thread = CreateThread(nullptr, 0, OS_Windows_Thread_Entry_Point, 
    thread, CREATE_SUSPENDED, &windows_thread_id);
  
  if (windows_thread == 0 || windows_thread == INVALID_HANDLE_VALUE) {
    return false;
  }
  
  s64 this_thread_index = InterlockedIncrement(&next_thread_index);
  // 2. #NewContext Setup NEW thread local context
  ExpandableArena* arena_ex = bootstrap_expandable_arena(Arena_Reserve::Size_64M);
  push_arena(arena_ex);
  
  // #NOTE: we don't assign thread_local_context until we hit the #thread_entry_point
  thread->context = New<Thread_Context>();
  thread->context->temp           = bootstrap_expandable_arena(Arena_Reserve::Size_2M);
  thread->context->arena          = arena_ex;
  thread->context->allocator      = allocator(arena_ex);
  thread->context->thread_idx     = (s32)this_thread_index;
  // #NOTE: This will disappear once the thread is de-initted. If we want this string, copy it!
  thread->context->thread_name    = copy_string(thread_name);
  thread->context->log_builder    = new_string_builder(Arena_Reserve::Size_64M);
  thread->context->string_builder = new_string_builder(Arena_Reserve::Size_2M);
  thread->context->error_arena    = bootstrap_arena(Arena_Reserve::Size_64M);
  thread->context->logger         = {default_logger_proc, &default_logger};
  
  thread->context->parent_thread_context = thread_context();
  
  thread->os_thread.windows_thread    = windows_thread;
  thread->os_thread.windows_thread_id = windows_thread_id;
  
  thread->proc = proc;
  thread->index = this_thread_index;
  
  thread_context()->child_threads.allocator = allocator(thread_context()->arena);
  array_add(thread_context()->child_threads, thread);
  
  return true;
}

internal void thread_deinit (Thread* thread,bool zero_thread) {
  // Move errors from thread to parent thread
  push_errors_to_parent_thread(thread->context);
  
  if (thread->os_thread.windows_thread) {
    CloseHandle(thread->os_thread.windows_thread);
    thread->os_thread.windows_thread = nullptr;
  }
  
  array_reset(*thread->context->log_builder);
  free_string_builder(thread->context->log_builder);
  arena_delete(thread->context->error_arena);
  arena_delete(thread->context->temp);
  arena_delete(thread->context->arena); // must come last because thread->context is allocated with this arena!
  
  if (zero_thread) memset(thread, 0, sizeof(Thread));
}

internal void thread_start (Thread* thread, void* thread_data) {
  if (thread_data) thread->data = thread_data;
  ResumeThread(thread->os_thread.windows_thread);
}

internal bool thread_is_done (Thread* thread, s32 milliseconds) {
  Assert(milliseconds >= -1);
  
  DWORD result = WaitForSingleObject(thread->os_thread.windows_thread, (DWORD)milliseconds);
  return result != WAIT_TIMEOUT;
}

// #thread_primitives
internal void mutex_init (Mutex* mutex) {
  InitializeCriticalSection(&mutex->csection);
}
internal void mutex_destroy (Mutex* mutex) {
  DeleteCriticalSection(&mutex->csection);
}
internal void lock (Mutex* mutex) {
  EnterCriticalSection(&mutex->csection);
}
internal void unlock (Mutex* mutex) {
  LeaveCriticalSection(&mutex->csection);
}
internal void semaphore_init (Semaphore* sem, s32 initial_value) {
  Assert(initial_value >= 0);
  sem->event = CreateSemaphoreW(nullptr, initial_value, 0x7fffffff, nullptr);
}
internal void semaphore_destroy (Semaphore* sem) {
  CloseHandle(sem->event);
}
internal void signal (Semaphore* sem) {
  ReleaseSemaphore(sem->event, 1, nullptr);
}

internal Wait_For_Result wait_for (Semaphore* sem, s32 milliseconds) {
  DWORD res = 0;
  if (milliseconds < 0) {
    res = WaitForSingleObject(sem->event, INFINITE);
  } else {
    res = WaitForSingleObject(sem->event, (u32)milliseconds);
  }
  
  Assert(res != WAIT_FAILED);
  
  if (res == WAIT_OBJECT_0) return Wait_For_Result::SUCCESS;
  if (res == WAIT_TIMEOUT)  return Wait_For_Result::TIMEOUT;
  
  return Wait_For_Result::ERROR;
}

internal void condition_variable_init (Condition_Variable* cv) {
  InitializeConditionVariable(&cv->condition_variable);
}
internal void condition_variable_destroy (Condition_Variable* cv) {
  // No action required.
}
internal void wait (Condition_Variable* cv, Mutex* mutex, s32 wait_time_ms) {
  SleepConditionVariableCS(&cv->condition_variable, &mutex->csection, (DWORD)wait_time_ms);
}
internal void wake (Condition_Variable* cv) {
  WakeConditionVariable(&cv->condition_variable);
}
internal void wake_all (Condition_Variable* cv) {
  WakeAllConditionVariable(&cv->condition_variable);
}

internal string get_error_string (OS_Error_Code error_code) {
  u16* lpMsgBuf;
  bool success = (bool)FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                    nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
                    (LPWSTR)&lpMsgBuf, 0, nullptr);
  if (!success) { return ""; }
  
  push_allocator(temp());
  
  string result = wide_to_utf8(lpMsgBuf);
  
  LocalFree(lpMsgBuf);
  
  return trim_right(result);
}

internal void os_log_error () {
  OS_Error_Code error_code = GetLastError();
  log_error(" > GetLastError code: %d, %s", error_code, get_error_string(error_code).data);
}

internal bool file_is_valid (File file) {
  if (file.handle == INVALID_HANDLE_VALUE) return false;
  if (file.handle == 0) return false;
  
  return true;
}

internal File file_open (string file_path, bool for_writing, bool keep_existing_content, bool log_errors) {
  HANDLE handle;
  
  push_allocator(temp()); // for utf8 -> wide conversions:
  
  if (for_writing) {
    u32 creation = (keep_existing_content) ? OPEN_ALWAYS : CREATE_ALWAYS;
    handle = CreateFileW(
      (LPCWSTR)utf8_to_wide(file_path).data, 
      FILE_GENERIC_READ | FILE_GENERIC_WRITE,
      FILE_SHARE_READ, 
      nullptr, creation, 0, nullptr);
  } else {
    u32 creation = OPEN_EXISTING;
    handle = CreateFileW(
      (LPCWSTR)utf8_to_wide(file_path).data,
      FILE_GENERIC_READ,
      FILE_SHARE_READ, 
      nullptr, creation, 0, nullptr);
  }
  
  if (handle == INVALID_HANDLE_VALUE && log_errors) {
    // OS_Error_Code error_code = GetLastError();
    log_error("Could not open file `%s`", file_path);
    os_log_error();
  }
  
  File file;
  file.handle = handle;
  
  return file;
}

internal void file_close (File* file) {
  CloseHandle(file->handle);
}

internal bool file_read (File file, u8* data, s64 bytes_to_read_count, s64* bytes_read_count) {
  // ignore bytes_read_count if null.
  if (data == nullptr) {
    log_error("file_read called with null destination pointer.");
    os_log_error();
    if (bytes_read_count) (*bytes_read_count) = 0;
    return false;
  }
  if (bytes_to_read_count <= 0) {
    if (bytes_read_count) (*bytes_read_count) = 0;
    return false;
  }
  
  bool read_success = false;
  s64 total_read = 0;
  
  // loop to read more data than can be specified by the DWORD param ReadFile takes:
  while (total_read < bytes_to_read_count) {
    s64 remaining = bytes_to_read_count - total_read;
    DWORD to_read;
    if (remaining <= 0x7FFFFFFF) {
      to_read = (DWORD)remaining;
    } else {
      to_read = 0x7FFFFFFF; // 2147483647 bytes ~2GB
    }
    
    DWORD single_read_length = 0;
    read_success = (bool)ReadFile(file.handle, data + total_read, to_read, &single_read_length, nullptr);
    
    total_read += single_read_length;
    if (!read_success || single_read_length == 0) {
      break;
    }
  }
  
  if (bytes_read_count) (*bytes_read_count) = total_read;
  return read_success;
}

internal bool file_length (File file, s64* length) {
  if (length == nullptr) {
    log_error("Calling file_length with null `length` param!");
    return false;
  }
  if (!file_is_valid(file)) { return false; }
  s64 size;
  bool success = (bool)GetFileSizeEx(file.handle, (PLARGE_INTEGER)&size);
  
  (*length) = size;
  
  return true;
}

internal bool file_length (string file_path, s64* length) {
  if (length == nullptr) {
    log_error("Calling file_length with null `length` param!");
    return false;
  }
  File f = file_open(file_path);
  
  if (!file_is_valid(f)) { return false; }
  bool success = file_length(f, length);
  
  file_close(&f);
  
  return success;
}

internal s64 file_current_position (File file) {
  constexpr s64 invalid_file_position = -1;
  if (!file_is_valid(file)) { return invalid_file_position; }
  s64 offset = 0;
  LARGE_INTEGER liDistanceToMove;
  bool result = (bool)SetFilePointerEx(file.handle, liDistanceToMove, (PLARGE_INTEGER)&offset, FILE_CURRENT);
  if (!result) { return invalid_file_position; }
  return (s64)offset;
}

internal bool file_set_position (File file, s64 position) {
  if (!file_is_valid(file)) { return false; }
  if (position < 0) { Assert(false); return false; }
  
  LARGE_INTEGER position_li; position_li.QuadPart = position;
  return (bool)SetFilePointerEx(file.handle, position_li, nullptr, FILE_BEGIN);
}

ArrayView<u8> read_entire_file (File file, bool add_null_terminator) {
  ArrayView<u8> file_data;

  bool result = file_length(file, &file_data.count);
  if (!result) return {};
  result = file_set_position(file, 0);
  if (!result) return {};
  
  s64 total_file_size = file_data.count;
  
  if (add_null_terminator) {
    total_file_size += 1;
  }
  
  file_data.data = NewArray<u8>(total_file_size, false);
  if (file_data.data == nullptr) return {};  
  
  s64 bytes_read = 0;
  result = file_read(file, file_data.data, file_data.count, &bytes_read);
  if (!result) {
    array_free(file_data);
    return {};
  }
  
  if (add_null_terminator) {
    file_data[total_file_size-1] = 0;
  }
  
  Assert(bytes_read == file_data.count);
  file_data.count = bytes_read;
  
  return file_data;
}

internal ArrayView<u8> read_entire_file (string file_path, bool add_null_terminator, bool log_errors) {
  File f = file_open(file_path, false, false, log_errors);
  if (!file_is_valid(f)) return {};
  
  ArrayView<u8> file_data = read_entire_file(f, add_null_terminator);
  
  file_close(&f);
  
  return file_data;
}

internal bool file_write (File* file, void* data, s64 length) {
  // @incomplete - deal with inputs > 32 bits (>2GB)
  u32 length_u32 = (u32)length;
  Assert(length == length_u32);
  
  u32 bytes_written;
  bool result = (bool)WriteFile(file->handle, data, length_u32, (LPDWORD)&bytes_written, nullptr);
  
  return result;
}

force_inline bool file_write (File* file, ArrayView<u8> view) {
  return file_write(file, view.data, view.count);
}

internal bool write_entire_file (string file_path, void* file_data, s64 count) {
  File f = file_open(file_path, true, false);
  if (!file_is_valid(f)) return false;
  
  bool result = file_write(&f, file_data, count);
  
  file_close(&f);
  return result;
}

internal bool write_entire_file (string file_path, ArrayView<u8> file_data) {
  return write_entire_file(file_path, file_data.data, file_data.count);
}

internal LRESULT // see os_w32_wnd_proc from raddebugger.
win32_wnd_proc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  LRESULT result = 0;
  bool good = true;
  
  Arena* event_arena = global_win32_state.process_info.event_arena;
  Assert(event_arena != nullptr);
  
  switch (uMsg) {
    default: {
      result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
    } break;
    case WM_CLOSE: {
      ExitProcess(0); // #temp.
    } break;
  }
  
  return result;
}

internal bool file_exists (string file_path) {
  push_allocator(temp());
  
  DWORD result = GetFileAttributesW((LPCWSTR)utf8_to_wide(file_path).data);
  
  return (result != INVALID_FILE_ATTRIBUTES);
}

internal BOOL
monitor_enum_proc (HMONITOR hMonitor, HDC hdc, RECT* rect, LPARAM data) {
  Monitor monitor = {};
  monitor.left   = rect->left;
  monitor.top    = rect->top;
  monitor.right  = rect->right;
  monitor.bottom = rect->bottom;
  
  monitor.monitor_info.cbSize = sizeof(MONITORINFO);
  
  GetMonitorInfoW(hMonitor, &monitor.monitor_info);
  monitor.primary = !!(monitor.monitor_info.dwFlags & 0x1);
  monitor.present = true;
  
  array_add(global_win32_state.system_info.monitors, monitor);
  
  return true;
}

// #TODO(Low Priority): how do I setup a callback if monitors configuration is changed?
// we handle WM_DISPLAYCHANGE or WM_DEVICECHANGE and call EnumDisplayMonitors again.
internal void os_enumerate_monitors () {
  if (!global_win32_state.system_info.monitors_enumerated) {
    // should reset array?
    if (!EnumDisplayMonitors(nullptr, nullptr, monitor_enum_proc, 0)) {
      log_fatal_error("Failed to enumerate monitors\n");
      Assert(false); // Failed to enumerate monitors
      ExitProcess(1);
    }
    
    global_win32_state.system_info.monitors_enumerated = true;
  }
}

Window_Dimensions platform_get_centered_window_dimensions (bool open_on_largest_monitor=false) {
  os_enumerate_monitors();
  
  Assert(global_win32_state.system_info.monitors.count > 0); // must have at least 1 monitor!
  
  Array<Monitor> monitors = global_win32_state.system_info.monitors;
  Monitor monitor = monitors[0];
  
  if (open_on_largest_monitor) {
    s64 max_area = 0;
    for (s64 i = 0; i < monitors.count; i += 1) {
      s64 width  = monitors[i].right  - monitors[i].left;
      s64 height = monitors[i].bottom - monitors[i].top;
      s64 area   = width * height;
      
      if (max_area < area) {
        monitor = monitors[i];
        max_area = area;
      } else if (max_area == area && monitors[i].primary) {
        // if monitors are the same dimension, then just use primary monitor
        monitor = monitors[i];
      }
    }
  } else { // Opens on whatever monitor is marked as "primary."
    for (s64 i = 0; i < monitors.count; i += 1) {
      if (monitors[i].primary) {
        monitor = monitors[i];
        break;
      }
    }
  }
  
  s64 monitor_width  = monitor.right  - monitor.left;
  s64 monitor_height = monitor.bottom - monitor.top;
  
  s64 window_width  = (s64)((f64)monitor_width / 1.125);
  s64 window_height = (s64)((f64)monitor_height / 1.25);
  s64 window_x  = (monitor.left + (monitor_width  / 2) - (window_width / 2));
  s64 window_y  = (monitor.top  + (monitor_height / 2) - (window_height / 2));
  
  Window_Dimensions dimensions = {
    (s32)window_x,
    (s32)window_y,
    (s32)window_width,
    (s32)window_height,
  };
  
  return dimensions;
}

bool Win32_Set_Main_Icon () {
  Window_Info* info = get_main_window_pointer();
  if (info->icon) {
    HICON old_icon = (HICON)SendMessage(info->window, WM_SETICON, ICON_BIG, (LPARAM)info->icon);
    if (old_icon) DestroyIcon(old_icon);
    return true;
  }
  
  return false;
}

#define ICON_CONTEXT_MENU_ITEM_ID 5011
#define MAIN_WINDOW_TRAY_ICON_ID 5001
#define WM_TRAYICON WM_USER + 1 // our own value to identify when receiving a message.

bool Win32_Set_Tray_Icon (string tooltip_text) {
  Window_Info* info = get_main_window_pointer();
  if (info->icon_minimized) {
    push_allocator(temp());
    wstring tooltip_text_wide = utf8_to_wide(tooltip_text);
    Assert(tooltip_text_wide.count < 128);
    
    info->nid.cbSize = sizeof(NOTIFYICONDATAW);
    info->nid.hWnd = info->window;
    info->nid.uID  = MAIN_WINDOW_TRAY_ICON_ID;
    info->nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    info->nid.uCallbackMessage = WM_TRAYICON; // Custom message when interacting with the tray icon
    info->nid.hIcon = info->icon_minimized;
    memcpy(info->nid.szTip, tooltip_text_wide.data, tooltip_text_wide.count);
    
    bool success = Shell_NotifyIconW(NIM_ADD, &info->nid);
    if (success) {
      info->tray_icon_added = true;
      return true;
    }
  }
  
  return false;
}

bool Win32_Show_Tray_Icon () {
  Window_Info* info = get_main_window_pointer();
  if (info && info->tray_icon_added) {
    return Shell_NotifyIconW(NIM_ADD, &info->nid);
  }
  return false;
}

bool Win32_Hide_Tray_Icon () {
  Window_Info* info = get_main_window_pointer();
  if (info && info->tray_icon_added) {
    return Shell_NotifyIconW(NIM_DELETE, &info->nid);
  }
  return false;
}

bool Win32_Hide_Window_Titlebar () {
  Window_Info* info = get_main_window_pointer(); 
  if (info && info->window) {
    LONG style = GetWindowLongW(info->window, GWL_STYLE);
    style &= (LONG)(~(WS_CAPTION | WS_SYSMENU));
    SetWindowLongW(info->window, GWL_STYLE, style);
    SetWindowPos(info->window, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
    return true;
  }
  return false;
}

bool Win32_Show_Window_Titlebar () {
  Window_Info* info = get_main_window_pointer(); 
  if (info && info->window) {
    LONG style = GetWindowLongW(info->window, GWL_STYLE);
    style |= (LONG)(WS_CAPTION | WS_SYSMENU);
    SetWindowLongW(info->window, GWL_STYLE, style);
    SetWindowPos(info->window, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
    return true;
  }
  return false;
}

void Win32_Minimize_Window_To_Tray (Window_Info* info) {
  if (info && info->window) {
    ShowWindow(info->window, SW_HIDE);
    Win32_Show_Tray_Icon();
    info->minimized_to_tray = true;
  }
}

void Win32_Restore_Window_From_Tray (Window_Info* info) {
  if (info && info->window && info->minimized_to_tray) {
    ShowWindow(info->window, SW_RESTORE);
    Win32_Hide_Tray_Icon();
    info->minimized_to_tray = false;
  }
}

// Win32_Restore_Window_From_Tray();
void Win32_Bring_Window_To_Foreground (Window_Info* info) {
  Win32_Restore_Window_From_Tray(info);
  if (info && info->window) {
    if (os_window_is_minimized(info->window)) {
      ShowWindow(info->window, SW_RESTORE);
    }
    SetForegroundWindow(info->window);
  }
}

bool Win32_Load_Main_Window_Icon_Minimized (string icon_path) {
  HICON result = (HICON)LoadImageW(nullptr, (LPCWSTR)utf8_to_wide(icon_path).data, IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
  Window_Info* info = get_main_window_pointer();
  info->icon_minimized = result;
  
  return (result != nullptr);
}

bool Win32_Load_Main_Window_Icon (string icon_path) {
  HICON result = (HICON)LoadImageW(nullptr, (LPCWSTR)utf8_to_wide(icon_path).data, IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
  Window_Info* info = get_main_window_pointer();
  info->icon = result;

  return (result != nullptr);
}

// #window_creation -> put API in OS_Win32.h
// Instead of returning WindowType, return the handle + other information.
bool os_create_window (string new_window_name, Window_Type parent, bool center_window, bool open_on_largest_monitor, bool display_window, void* wnd_proc_override) {
  local_persist string class_name = "Win32_Window_Class";
    
  WNDCLASSEXW wc = {};
  if (!global_win32_state.process_info.window_class_initialized) {
    global_win32_state.process_info.window_class_initialized = true;
    
    wc.cbSize = sizeof(WNDCLASSEXW);
    wc.style  = CS_HREDRAW | CS_VREDRAW;
    if (!wnd_proc_override) {
      wc.lpfnWndProc   = win32_wnd_proc;
    } else {
      wc.lpfnWndProc   = (WNDPROC)wnd_proc_override;
    }
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = nullptr;
    wc.hIcon         = get_main_window().icon;
    wc.hCursor       = LoadCursorW(nullptr, (LPCWSTR)IDC_ARROW);
    wc.hbrBackground = nullptr;
    wc.lpszMenuName  = nullptr;
    wc.lpszClassName = (LPCWSTR)utf8_to_wide(class_name).data;
    
    if (RegisterClassExW(&wc) == 0) {
      log_error("RegisterClassExW Failed.");
      return false;
    }
  }
  
  Window_Dimensions wd = { 100, 100, 640, 480 };
  
  if (center_window) {
    wd = platform_get_centered_window_dimensions(open_on_largest_monitor);
    log("[Window_Dimensions] location: (%d, %d); size: %dx%d px", 
      wd.window_x, wd.window_y, wd.window_width, wd.window_height);
  }
  
  HWND hwnd = CreateWindowExW(
    WS_EX_APPWINDOW,
    wc.lpszClassName,
    (LPCWSTR)utf8_to_wide(new_window_name).data,
    WS_OVERLAPPEDWINDOW,
    wd.window_x, wd.window_y, wd.window_width, wd.window_height,
    nullptr, nullptr, nullptr, nullptr
  );
  
  if (!hwnd) {
    Assert(false);
    DestroyWindow(hwnd);
    return false;
  }
  
  // Display the window:
  if (display_window) {
    UpdateWindow(hwnd);
    ShowWindow(hwnd, SW_SHOW);
  }
  
  // Save window to stack
  Window_Info info = {};
  info.window               = hwnd;
  info.initial_dimensions   = wd;
  info.is_main_window       = (parent == nullptr);
  
  // Should we mutex this? Seems very unlikely we'll ever need to call this
  // from multiple threads at the same time. 
  array_add(global_win32_state.process_info.windows, info);
  
  return true;
}

Window_Info get_main_window () {
  Array<Window_Info> windows = global_win32_state.process_info.windows;
  if (windows.count <= 0) {
    return {};
  }
  
  for (s64 i = 0; i < windows.count; i += 1) {
    if (windows[i].is_main_window) {
      return windows[i];
    }
  }
  
  return {};
}

Window_Info* get_main_window_pointer () {
  s64 window_count = global_win32_state.process_info.windows.count;
  
  for (s64 i = 0; i < window_count; i += 1) {
    if (global_win32_state.process_info.windows[i].is_main_window) {
      return &global_win32_state.process_info.windows[i];
    }
  }
  
  return nullptr;
}

Window_Info* get_window_info (Window_Type window) {
  s64 window_count = global_win32_state.process_info.windows.count;
  
  for (s64 i = 0; i < window_count; i += 1) {
    if (global_win32_state.process_info.windows[i].window == window) {
      return &global_win32_state.process_info.windows[i];
    }
  }
  
  return nullptr;
}

bool get_window_dimensions(Window_Info* info, s32* width, s32* height) {
  if (info == nullptr || width == nullptr || height == nullptr) return false;
  Assert(width && height);
  
  RECT c = {};
  bool success = GetClientRect(info->window, &c);
  
  if (!success) {
    (*width)  = 0;
    (*height) = 0;
    return false;
  }
  
  (*width)  = (s32)(c.right  - c.left);
  (*height) = (s32)(c.bottom - c.top);
  
  return true;
}

bool os_window_is_minimized (Window_Type window) {
  return (bool)IsIconic(window);
}

bool os_main_window_is_minimized () {
  return os_window_is_minimized(get_main_window().window);
}

s32 os_cpu_logical_core_count () {
  OS_System_Info* info = &global_win32_state.system_info;
  return info->logical_processor_count;
}

s32 os_cpu_physical_core_count () {
  OS_System_Info* info = &global_win32_state.system_info;
  return info->physical_core_count;
}

s32 os_cpu_primary_core_count () {
  OS_System_Info* info = &global_win32_state.system_info;
  return info->primary_core_count;
}

s32 os_cpu_secondary_core_count () {
  OS_System_Info* info = &global_win32_state.system_info;
  return info->secondary_core_count;
}

// #Drives
constexpr u64 Win32_Max_Path_Length = 260;
bool Win32_Discover_Drives () {
  push_allocator(default_allocator());
  // Initialize drive_table if necessary.
  Table<string, OS_Drive*>* drive_table = get_drive_table();
  if (!drive_table->allocated) {
    drive_table->allocator        = default_allocator();
    // #TODO(Low priority): #hash_table need a macro for initializing with string keys!
    drive_table->hash_function    = string_hash_function_fnv1a;
    drive_table->compare_function = string_keys_match;
    s64 slots_to_allocate = 64;
    table_init(drive_table, slots_to_allocate);
  }
  
  u16 lpBuf[1024];
  u32 result_length = GetLogicalDriveStringsW(1024, (LPWSTR)lpBuf);
  if (!result_length) {
    log_error("GetLogicalDriveStringsW failed!");
    os_log_error();
    return false;
  }
  
  bool completed = false;
  s32 current_index = 0;
  
  while (true) {
    completed = completed || (current_index >= 1024) || lpBuf[current_index] == 0;
    if (completed) break;
    
    u16* logical_drive = lpBuf + current_index;
    
    string drive_label = wide_to_utf8(logical_drive);
    
    u64 total_number_of_bytes;u64 total_number_of_free_bytes;
    bool result = GetDiskFreeSpaceExW((LPCWSTR)logical_drive, nullptr, 
          (PULARGE_INTEGER)&total_number_of_bytes, (PULARGE_INTEGER)&total_number_of_free_bytes);
    
    Win32_Drive_Type drive_type = (Win32_Drive_Type)GetDriveTypeW((LPCWSTR)logical_drive);
    
    log("Found %s drive, type %s", drive_label.data, to_string(drive_type).data);
    
    current_index += (s32)(drive_label.count + 1);
    
    bool just_added = false;
    OS_Drive** drive_ptr = table_find_or_add(drive_table, drive_label, &just_added);
    OS_Drive* drive = New<OS_Drive>();
    (*drive_ptr) = drive; // fill table slot with data.
    
    if (!just_added) { // delete old strings before updating
      // This is silly, but there's a small chance the volume has been renamed so...
      string_free(drive->label); // this is actually just stupid.
      string_free(drive->volume_name);
    }
    
    u16 volume_name[Win32_Max_Path_Length] = {};
    u16 file_system_name[Win32_Max_Path_Length] = {};
    DWORD serial_number = 0; DWORD max_comp_len = 0; DWORD file_system_flags = 0;
    if (GetVolumeInformationW((LPCWSTR)logical_drive, (LPWSTR)volume_name, 
        Win32_Max_Path_Length, &serial_number, &max_comp_len, &file_system_flags, 
        (LPWSTR)file_system_name, Win32_Max_Path_Length)) {
      drive->label = drive_label;
      if (volume_name[0] == 0) {
        drive->volume_name = copy_string("Local Disk");
      } else {
        drive->volume_name = wide_to_utf8(volume_name);
      }
      if (drive->volume_name == "") { drive->volume_name = copy_string("Local Disk"); } // Probably redundant??
      
      drive->type = (Win32_Drive_Type)drive_type;
      { push_allocator(temp());
        drive->file_system = Win32_filesystem_from_string(wide_to_utf8(file_system_name));
      }
      drive->serial_number = serial_number;
      drive->max_component_length = max_comp_len;
      drive->file_system_flags = file_system_flags;
      drive->is_present = true;
      push_allocator(temp());
      log(" - volume name: %s", drive->volume_name.data);
      log(" - file_system: %s", wide_to_utf8(file_system_name).data);
    } else {
      log_error("GetVolumeInformationW failed! (drive label: %s)", drive_label.data);
      os_log_error();
      drive->is_present = false;
    }
  }
  
  return true;
}

// Drive label includes `:\`
bool Win32_Drive_Exists (string drive_label) {
  push_allocator(temp());
  LPCWSTR drive_label_wide = (LPCWSTR)utf8_to_wide(drive_label).data;
  UINT type = GetDriveTypeW(drive_label_wide);
  return (type != DRIVE_UNKNOWN && type != DRIVE_NO_ROOT_DIR);
  // Alternative method:
  // return (bool)GetVolumeInformationW(drive_label_wide, nullptr, 0, nullptr, nullptr, nullptr, nullptr, 0);
}

string Win32_drive_letter (string any_path) {
  // #TODO: remove leading `\\.\` if present, assert if drive letter is invalid.
  // we copy so it is null-terminated, and can be used as %s in format_string.
  return copy_string({1, any_path.data});
}

string os_get_machine_name () {
  constexpr u8 WIN32_MAX_COMPUTER_LENGTH_NAME = 31;
  u16 buffer[WIN32_MAX_COMPUTER_LENGTH_NAME + 1];
  
  u32 count = WIN32_MAX_COMPUTER_LENGTH_NAME + 1;
  if (GetComputerNameW((LPWSTR)buffer, (LPDWORD)&count)) {
    return wide_to_utf8(buffer);
  }
  
  return "";
}

// #TODO: #window_creation #window_manipulation
// [ ] resize_window
// [ ] position_window
// [ ] toggle_fullscreen
// [ ] get_dimensions

// #TODO: #window_interaction (mouse/keyboard)
// [ ] get_mouse_pointer_position
// [ ] ... What APIs do I need for Keyboard

// #FileEnumerationST

struct STFE_Results {
  Serializer* strings; // Serializer?
  ArenaArray<u32>* offsets;
  ArenaArray<s16>* lengths;
  ArenaArray<u64>* sizes;
  ArenaArray<u64>* modtimes; 
};

void init (STFE_Results* results) {
  results->strings = (Serializer*)arena_array_new<u8> (1024*1024*4*16, Arena_Reserve::Size_2G);
  results->offsets = arena_array_new<u32>(1024*1024*4, Arena_Reserve::Size_2G);
  results->lengths = arena_array_new<s16>(1024*1024*4, Arena_Reserve::Size_2G);
  results->sizes    = arena_array_new<u64>(1024*1024*4, Arena_Reserve::Size_2G);
  results->modtimes = arena_array_new<u64>(1024*1024*4, Arena_Reserve::Size_2G);
}

void STFE_Results_Free (STFE_Results* results) {
  arena_array_free(*results->strings);
  arena_array_free(*results->offsets);
  arena_array_free(*results->lengths);
  arena_array_free(*results->sizes);
  arena_array_free(*results->modtimes);
}

struct ST_File_Enumeration { // global state
  ArrayView<OS_Drive*> drives;
  Thread* master_thread;
  
  STFE_Results dirs;
  STFE_Results files;
  s32 directories_enumerated; // going sequentially
  bool thread_started;
  bool thread_completed;
  
  f64 start_time;
  f64 end_time;
};

global ST_File_Enumeration* stfe;

void free_stfe_and_reset () {
  push_allocator(default_allocator());
  
  array_free(stfe->drives);
  
  internal_free(stfe->master_thread);
  
  STFE_Results_Free(&stfe->dirs);
  STFE_Results_Free(&stfe->files);
  
  internal_free(stfe);
  stfe = nullptr; // final step
}

string add_record (ST_File_Enumeration* stfe, string full_path, bool is_directory, WIN32_FIND_DATAW* find_data) {
  // return the string copy!
  if (is_directory) {
    STFE_Results* r = &stfe->dirs;
    u32 offset = AddString_NoCount(r->strings, full_path.data, (s16)full_path.count);
    array_add((*r->offsets), offset);
    array_add((*r->lengths), (s16)full_path.count);
    // No size for directories.    
    u64 modtime = FILETIME_to_ticks(find_data->ftLastWriteTime);
    array_add((*r->modtimes), modtime);
    
    string path_copy = {full_path.count, &r->strings->data[offset]};
    return path_copy;
  } else {
    STFE_Results* r = &stfe->files;
    u32 offset = AddString_NoCount(r->strings, full_path.data, (s16)full_path.count);
    array_add((*r->offsets), offset);
    array_add((*r->lengths), (s16)full_path.count);
    
    u64 size = ((u64)find_data->nFileSizeHigh << 32) | ((u64)find_data->nFileSizeLow & 0xFFFFFFFF);
    u64 modtime = FILETIME_to_ticks(find_data->ftLastWriteTime);
    array_add((*r->sizes), size);
    array_add((*r->modtimes), modtime);
    
    string path_copy = {full_path.count, &r->strings->data[offset]};
    return path_copy;
  }
  
  Assert(false);
  return {};
}

s32 count_paths (ST_File_Enumeration* stfe) {
  STFE_Results* r = &stfe->dirs;
  return (s32)r->offsets->count;
}
s32 count_files (ST_File_Enumeration* stfe) {
  STFE_Results* r = &stfe->files;
  return (s32)r->offsets->count;
}

// #UI #TEMP - just for visualization!
string get_file_copy (ST_File_Enumeration* stfe, s64 index) {
  STFE_Results* r = &stfe->files;
  Assert(index >= 0 && index < count_files(stfe));
  s64 strlength = (*r->lengths)[index];
  u32 offset    = (*r->offsets)[index];
  u8* string_ptr = &r->strings->data[offset];
  string file = {strlength, string_ptr};
  return copy_string(file);
}

string get_file_string_view (ST_File_Enumeration* stfe, s64 index) {
  STFE_Results* r = &stfe->files;
  s64 strlength = (*r->lengths)[index];
  u32 offset    = (*r->offsets)[index];
  u8* string_ptr = &r->strings->data[offset];
  string file = {strlength, string_ptr};
  return file;
}

string get_path_copy (ST_File_Enumeration* stfe, s64 index) {
  STFE_Results* r = &stfe->dirs;
  Assert(index >= 0 && index < count_paths(stfe));
  s64 strlength = (*r->lengths)[index];
  u32 offset    = (*r->offsets)[index];
  u8* string_ptr = &r->strings->data[offset];
  string path = {strlength, string_ptr};
  return copy_string(path);
}
s64 get_file_size_bytes (ST_File_Enumeration* stfe, s64 index) {
  STFE_Results* r = &stfe->files;
  return (s64)(*r->sizes)[index];
}

FILETIME get_file_modtime (ST_File_Enumeration* stfe, s64 index) {
  STFE_Results* r = &stfe->files;
  FILETIME ft;
  memcpy(&ft, &(*r->modtimes)[index], sizeof(u64));
  return ft;
}

FILETIME get_path_modtime (ST_File_Enumeration* stfe, s64 index) {
  STFE_Results* r = &stfe->dirs;
  FILETIME ft;
  memcpy(&ft, &(*r->modtimes)[index], sizeof(u64));
  return ft;
}

s64 win32_file_enum_thread_proc (Thread* thread) {
  auto task = thread_task(ST_File_Enumeration);
  init(&task->dirs);
  init(&task->files);
  
  // Allocates to thread_context->arena, which is cleaned up 
  // when the thread completes. see: thread_deinit
  Array<string> paths_to_enumerate;
  
  for_each(d, task->drives) {
    string parent_directory = task->drives[d]->label; // includes a trailing slash
    if (parent_directory.data[2] == (u8)'\\') {
      parent_directory.count -= 1; //#hack to quickly remove trailing slash.
    }
    array_add(paths_to_enumerate, parent_directory);
    
    while (paths_to_enumerate.count > 0) {
      push_allocator(temp());
      auto_release_temp();
      // This needs to be null-terminated:
      // #TODO: Replace this #LIFO array with an arena-backed FIFO stack (singly linked-list).
      string next_directory = copy_string(pop(paths_to_enumerate)); // LIFO. maybe not the best way?
      wstring wildcard_name = utf8_to_wide(format_string("%s\\*", next_directory.data));
      
      WIN32_FIND_DATAW find_data;
      HANDLE h = FindFirstFileExW((LPCWSTR)wildcard_name.data, FindExInfoBasic, &find_data, 
                  FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH);
      if (h == INVALID_HANDLE_VALUE) {
        log_error("FindFirstFileExW failed for %s", wide_to_utf8(wildcard_name.data, (s32)wildcard_name.count).data);
        os_log_error();
        continue;
      }
      
      while (true) { auto_release_temp();
        string name = wide_to_utf8((u16*)find_data.cFileName);
        bool should_continue = (name.count == 0 || name == "." || name == "..");
        if (should_continue) {
          bool success = FindNextFileW(h, &find_data);
          if (!success) { break; }
          continue;
        }
        
        bool is_directory = (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
        string full_path = format_string("%s\\%s", next_directory.data, name.data);
        string full_path_copy = add_record(task, full_path, is_directory, &find_data);
        if (is_directory) {
          array_add(paths_to_enumerate, full_path_copy);
        }
        
        bool success = FindNextFileW(h, &find_data);
        if (!success) break;
      } // while (true) -> FindNextFileW
      
      FindClose(h);
    } // while (parent_directory)
  } // for_each(d, drives)
  
  task->end_time = GetUnixTimestamp();
  
  return 0;
}

void os_run_file_enumeration_single_threaded () {
  push_allocator(default_allocator());
  
  stfe = New<ST_File_Enumeration>();
  (*stfe) = {
    os_get_available_drives(),
    New<Thread>(),
    STFE_Results(), STFE_Results(),
    0, true, false, GetUnixTimestamp(), 0
  };
  
  string thread_name = "Single Thread Enumeration - Master Thread";
  bool success = thread_init(stfe->master_thread, win32_file_enum_thread_proc, thread_name);
  if (!success) {
    log_error("Failed to initialize thread (stft->master_thread)");
    os_log_error();
  }
  Assert(success);
  thread_start(stfe->master_thread, stfe);
}

constexpr u32 STFE_Magic_Number = 0x19075fee;

bool Serialize_ST_File_Enumeration (string file_path) {
  Timed_Block_Print("Serialize_ST_File_Enumeration");
  File f = file_open(file_path, true, false, true);
  if (!file_is_valid(f)) return false;
  bool success = true;
  // #TODO #Serialization Unfortunately, there's a lot of needless copying here
  // it would be a lot nicer if we could just write-file in place. idk how to do that though ;_;
  Serializer* s = new_serializer(Arena_Reserve::Size_64G);
  Add(s, (u32)STFE_Magic_Number);
  Add(s, (s32)stfe->drives.count);
  // Dirs:
  STFE_Results* r = &stfe->dirs;
  AddArray(s, to_view(*r->strings));
  AddArray(s, to_view(*r->offsets));
  AddArray(s, to_view(*r->lengths));
  AddArray(s, to_view(*r->modtimes));
  
  // Files:
  r = &stfe->files;
  AddArray(s, to_view(*r->strings));
  AddArray(s, to_view(*r->offsets));
  AddArray(s, to_view(*r->lengths));
  AddArray(s, to_view(*r->sizes));
  AddArray(s, to_view(*r->modtimes));
  
  success = file_write(&f, to_view(*s));
  reset_serializer(s);
  
  file_close(&f);
  free_serializer(s);
  
  return success;
}

bool Deserialize_ST_File_Enumeration (string file_path) {
  Timed_Block_Print("Deserialize_ST_File_Enumeration");
  push_allocator(default_allocator());
  if (!stfe) stfe = New<ST_File_Enumeration>();
  (*stfe) = {
    {},
    {},
    STFE_Results(), STFE_Results(),
    0, false, false, GetUnixTimestamp(), 0
  };
  
  push_allocator(temp());
  auto_release_temp();
  
  Deserializer deserializer = read_entire_file(file_path, false, true);
  if (deserializer.count == 0) return false;
  auto d = &deserializer;
  
  u32 magic_number; s32 drive_count;
  Read(d, &magic_number);
  Assert(magic_number == STFE_Magic_Number);
  Read(d, &drive_count);
  
  init(&stfe->dirs);
  init(&stfe->files);
  
  STFE_Results* r = &stfe->dirs;
  ReadToArenaArray(d, r->strings);
  ReadToArenaArray(d, r->offsets);
  ReadToArenaArray(d, r->lengths);
  ReadToArenaArray(d, r->modtimes);
  r = &stfe->files;
  ReadToArenaArray(d, r->strings);
  ReadToArenaArray(d, r->offsets);
  ReadToArenaArray(d, r->lengths);
  ReadToArenaArray(d, r->sizes);
  ReadToArenaArray(d, r->modtimes);
  
  stfe->thread_started = true;
  stfe->thread_completed = true;
  stfe->end_time = GetUnixTimestamp();
  
  return true;
}

// #USNJrnl stuff:

// This should work even if our other indices are not ready yet!
bool USN_Journal_Monitoring_Ready(OS_Drive* drive) {
  return (drive->jrnl.hVol != nullptr && drive->jrnl.hVol != INVALID_HANDLE_VALUE);
}

void Win32_Enable_USN_Journal_Monitoring (ArrayView<OS_Drive*> drives) {
  push_allocator(temp());
  // #TODO: Put any relevant data into Win32_Drive.
  for_each(d, drives) {
    OS_Drive* drive = drives[d];
    if (drive->jrnl.no_permission) continue;
    if (USN_Journal_Monitoring_Ready(drive)) continue;
    string drive_letter = Win32_drive_letter(drive->label);
    string create_file_target = format_string("\\\\.\\%s:", drive_letter.data);
    drive->jrnl.hVol = CreateFileA((LPCSTR)create_file_target.data, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
                          OPEN_EXISTING, 0, nullptr);
    if (drive->jrnl.hVol == INVALID_HANDLE_VALUE) {
      log_error("CreateFileA failed on target %s", create_file_target.data);
      os_log_error();
      drive->jrnl.no_permission = true;
    }
  }
}

void Query_USN_Journal (ArrayView<OS_Drive*> drives) {
  Win32_Enable_USN_Journal_Monitoring(drives);
  for_each(d, drives) {
    OS_Drive* drive = drives[d];
    if (!USN_Journal_Monitoring_Ready(drive)) continue;
    USN_JOURNAL_DATA_V0 usn_jd;
    DWORD bytes_returned;
    BOOL ok = DeviceIoControl(drive->jrnl.hVol, FSCTL_QUERY_USN_JOURNAL,
            nullptr, 0,
            &usn_jd, sizeof(usn_jd),
            &bytes_returned,
            nullptr);
    if (!ok) {
      log_error("DeviceIoControl failed on target %s", drive->label.data);
      os_log_error();
      return;
    }
    log("[DeviceIoControl] target %s", drive->label.data);
    log("  > Journal ID: %llu", usn_jd.UsnJournalID);
    log("  > First USN: %llu", usn_jd.FirstUsn);
    debug_break(); // #TODO #continue
  }
}
