struct ExplorerUI {
  // u8 search_input[64];
  // u8 secondary_input[64];
};

struct Explorer { 
  // A bunch of flags?
  // Icon cache?
  // Array<Image_Data> frame_textures;
};

struct Ex1_NTFS_Enumeration {
  b32 initialized;
  b32 threads_started;
  
  ArrayView<Thread> threads;
  Array<Thread*> threads_in_flight;
};

// #Ex1_global_state
global ExplorerUI explorer_ui;
global Explorer explorer;
global Ex1_NTFS_Enumeration ex1_ntfs;

#define HOTKEY_ID_BRING_TO_FOREGROUND 1
#define VK_SPACE_KEY_CODE 0x20
// #define HOTKEY_ID_HIDE_TITLEBAR

void Ex1_Unregister_Global_Hotkeys () {
  UnregisterHotKey(nullptr, HOTKEY_ID_BRING_TO_FOREGROUND);
}

bool Ex1_Register_Global_Hotkeys () {
  if (!RegisterHotKey(nullptr, HOTKEY_ID_BRING_TO_FOREGROUND, MOD_CONTROL|MOD_SHIFT|MOD_ALT, VK_SPACE_KEY_CODE)) {
    Assert(false);
    log_error("Failed to register global hotkey Ctrl-Shift-Alt-Space");
    return false;
  }
  return true;
}

#define USE_CTRL true
#define USE_SHIFT true
#define USE_ALT true
#define TRIGGER_ONCE true
#define TRIGGER_EVERY_FRAME false
#define KEY_UNUSED   false

// #program_hotkeys (Not specific to Ex1)
global Key_Combination program_exit_hotkey     { ImGuiKey_W, USE_CTRL, USE_SHIFT,  KEY_UNUSED, TRIGGER_ONCE };
global Key_Combination program_minimize_hotkey { ImGuiKey_W, USE_CTRL, KEY_UNUSED, KEY_UNUSED, TRIGGER_ONCE };

global Key_Combination decrease_font_size_hotkey { ImGuiKey_Minus, USE_CTRL, KEY_UNUSED, KEY_UNUSED, TRIGGER_ONCE };
global Key_Combination increase_font_size_hotkey { ImGuiKey_Equal, USE_CTRL, KEY_UNUSED, KEY_UNUSED, TRIGGER_ONCE };

bool Ex1_check_key_combinations() {
  update_global_keyboard_state();
  // #program_hotkeys
  if (check_key_combination(&program_exit_hotkey)) {
    return true;
  }
  if (check_key_combination(&program_minimize_hotkey)) {
    Win32_Minimize_Window_To_Tray(get_main_window_pointer());
  }
  if (check_key_combination(&decrease_font_size_hotkey)) { // see ImGui_Show_Font_Info.
    imgui_default_font.current_size = clamp<s64>(imgui_default_font.current_size - 1, 0, 5);    
  }
  if (check_key_combination(&increase_font_size_hotkey)) {
    imgui_default_font.current_size = clamp<s64>(imgui_default_font.current_size + 1, 0, 5);
  }
  // #Ex1_hotkeys
  return false;
}

// #Workspaces are FOR DEVELOPMENT ONLY.
struct Ex1_Workspace {
  s32 path_select;
  s32 file_select;
  
  RadixSort file_size_radix;
  RadixSort file_modtime_radix;
  RadixSort dir_modtime_radix;
  bool sort_completed;
  
  // Reordered strings:
  ArrayView<string> files_sorted_by_size;
  ArrayView<string> files_sorted_by_modtime;
};

global Ex1_Workspace ex1w;

void free_ex1_workspace_and_reset () {
  if (ex1w.sort_completed) {
    push_allocator(default_allocator());
    
    radix_sort_free(&ex1w.file_size_radix);
    radix_sort_free(&ex1w.file_modtime_radix);
    radix_sort_free(&ex1w.dir_modtime_radix);
    
    array_free(ex1w.files_sorted_by_size);
    array_free(ex1w.files_sorted_by_modtime);
    
    zero_struct(&ex1w);
  }
}

// #TODO: Move all sort stuff to OS_Win32?
// Make a general version of this that takes two ArrayView<T> and reorders.
// There's no need to do this until we have the filtered results.
void os_win32_reorder_files_by_radix (RadixSort* r, ArrayView<string>* files, bool reverse_order=false) {
  Timed_Block_Print("os_win32_reorder_files_by_radix");
  // Where are my source files!?
  (*files) = ArrayView<string>(r->ranks.count);
  for_each(f, (*files)) {
    // (*files)[f] = get_file_copy(stfe, r->ranks[f]);
    (*files)[f] = get_file_string_view(stfe, r->ranks[f]);
  }
}

void Ex1_show_enumeration_workspace () { using namespace ImGui;
  push_imgui_window("Enumerated Data Workspace");

  if (!ex1w.sort_completed) {
    push_allocator(default_allocator());
    Timed_Block_Print("radix_sort_u64: file sizes, file modtimes, directory modtimes");
    ArrayView<u64> sizes = to_view(*stfe->files.sizes);
    radix_sort_u64(&ex1w.file_size_radix, sizes.data, (u32)sizes.count);
    ArrayView<u64> file_modtimes = to_view(*stfe->files.modtimes);
    radix_sort_u64(&ex1w.file_modtime_radix, file_modtimes.data, (u32)file_modtimes.count);
    ArrayView<u64> dirs_modtimes = to_view(*stfe->dirs.modtimes);
    radix_sort_u64(&ex1w.dir_modtime_radix, dirs_modtimes.data, (u32)dirs_modtimes.count);
    // Create ArrayView<string>, ArrayView<u64> sizes, and ArrayView<u64> modtimes
    os_win32_reorder_files_by_radix(&ex1w.file_size_radix, &ex1w.files_sorted_by_size);
    os_win32_reorder_files_by_radix(&ex1w.file_modtime_radix, &ex1w.files_sorted_by_modtime);
    // reordering by the rank permutations generated by RadixSort.
    ex1w.sort_completed = true;
  }
  
  if (!ex1w.sort_completed) { return; }
  
  SeparatorText("Files ordered by modtime");
  s32 file_count = (s32)ex1w.files_sorted_by_modtime.count;
  SliderInt("Select file index", &ex1w.file_select, 0, file_count-1);
  string file_name = copy_string(ex1w.files_sorted_by_modtime[ex1w.file_select]);
  Text("%s", file_name.data);
  u32 radix_index = rank(&ex1w.file_modtime_radix, ex1w.file_select);
  Text("date modified: %s", format_time_datetime(get_file_modtime(stfe, radix_index)).data);

  SeparatorText("Files ordered by size");
  file_count = (s32)ex1w.files_sorted_by_size.count;
  // SliderInt("Select file index", &ex1w.file_select, 0, file_count-1);
  file_name = copy_string(ex1w.files_sorted_by_size[ex1w.file_select]);
  Text("%s", file_name.data);
  radix_index = rank(&ex1w.file_size_radix, ex1w.file_select);
  Text("size: %s", format_bytes(get_file_size_bytes(stfe, radix_index)).data);
  // Text("date modified: %s", format_time_datetime(get_file_modtime(stfe, radix_index)).data);
    
  if (Button("Count unique UTF-8 characters")) {
    count_unique_utf8_chars();
  }
  
  Text("unique_codepoints_utf32.count: %", unique_codepoints_utf32.count);
  
  for_each(u, unique_codepoints_utf32) {
    Text("[%d] Code point as hex: 0x%X", u, unique_codepoints_utf32[u]);
  }
  
  Text("files_sorted_by_size size in bytes: %lld", ex1w.files_sorted_by_size.count * sizeof(string));
  Text("files_sorted_by_modtime size in bytes: %lld", ex1w.files_sorted_by_modtime.count * sizeof(string));
  
  for (s64 i = 1; i < 128; i += 1) {
    u8 cstring[2] = {};
    cstring[0] = (u8)i;
    cstring[1] = 0;
    Text("codepoint[0x%X]: %s, count: %lld", i, cstring, count_ascii_codepoints[i]);
  }
}

void Ex1_Control_Panel () { using namespace ImGui;
  f64 frame_time = GetUnixTimestamp();
  Table<string, OS_Drive*>* drive_table = get_drive_table();

  push_imgui_window("Control Panel");
  
  // if (Button("Debug break")) { debug_break(); }
  if (/*Button("Discover drives") ||*/!table_is_valid(drive_table)) { Win32_Discover_Drives(); }
  
  // Text("ntfs_workspace_files_loaded: %s", ntfs_workspace_files_loaded()? "true": "false");
  // if (ntfs_workspace_files_loaded()) { 
  //   Ex1_show_ntfs_workspace();
  //   return;
  // }
  
  bool all_drives_enumerated = stfe && stfe->thread_completed;
  push_allocator(temp());
  ArrayView<OS_Drive*> drives = os_get_available_drives(); // only includes drives that are ready.

  // if (!USN_Journal_Monitoring_Ready(drives[0]) && Button("Enable USN Monitoring for all drives")) {
  //   Win32_Enable_USN_Journal_Monitoring(drives);
  // }
  // if (USN_Journal_Monitoring_Ready(drives[0]) && Button("Query USN Journal")) {
  //   Query_USN_Journal(drives);
  // }
  
  if (!all_drives_enumerated) {
    // Text("drive_table is valid: %d", table_is_valid(drive_table));
    for_each(i, drives) {
      OS_Drive* drive = drives[i];
      
      Text(" > [%d] drive letter: %s (is_present: %d)", i + 1, drive->label.data, drive->is_present);
      if (drive->time_to_enumerate != 0) {
        SameLine();
        Text("Enumerated in %.2f seconds", drive->time_to_enumerate);
      }
      // SameLine();
      // if (Button(format_cstring("Read NTFS MFT Raw##%s", drive->label.data))) {
      //   push_arena(thread_context()->arena);
      //   Error* error = NTFS_MFT_read_raw(drive);
      // }
    }
  }
  
  s32 drives_enumerated = 0;
  for_each(d, drives) {
    OS_Drive* drive = drives[d];
    if (!drives[d]->time_to_enumerate) { continue; }
    if (!drives[d]->data) { continue; }
    
    if (drives[d]->data->paths.offsets->count > 0) {
      drives_enumerated += 1;
    }
  }
  // bool all_drives_enumerated = !ex1_ntfs.threads_in_flight.count
  //   && (drives_enumerated == drives.count);
  
  string file_path = format_string_temp("D:/TempSync/Filesystem_Data/%s_DriveData.bin", os_get_machine_name().data);
  // string file_path = "D:/TempSync/Filesystem_Data/MUSA-PC3_DriveData.bin";
  Text("fixed file_path: %s", file_path.data);
  if (!all_drives_enumerated && file_exists(file_path)) { // #autoload
    Deserialize_ST_File_Enumeration(file_path);
  }
  if (drives.count > 0 && !all_drives_enumerated && file_exists(file_path) && Button("Load from file (this machine)")) {
    Deserialize_ST_File_Enumeration(file_path);
  //   Deserialize_Win32_Drives(file_path);
  }
  
  // if (file_enum_multithreading_started()) {
  //   if (thread_is_done(drive_enumeration->master_thread)) {
  //     push_allocator(default_allocator());
  //     // Thread* thread = drive_enumeration->master_thread;
  //     // auto task = thread_task(Drive_Enumeration);
  //     // Nothing to free?
  //     thread_deinit(drive_enumeration->master_thread, true);
  //   }
  // }
  
  // #FileEnumerationST
  if (stfe && stfe->thread_started) {
    // print dirs enumerated, etc
    if (!stfe->thread_completed) Text("Enumeration Thread Active (elapsed: %s)", format_time_seconds(frame_time-stfe->start_time).data);
    if (stfe->dirs.offsets) {
      s64 dirs_enumerated = stfe->dirs.offsets->count;
      Text("Dirs enumerated: %lld", dirs_enumerated);
      // if (!stfe->thread_completed) Text("Current dir: %s", get_last_path_copy(stfe).data);
    }
    if (stfe->files.offsets) {
      s64 files_enumerated = stfe->files.offsets->count;
      Text("Files enumerated: %lld", files_enumerated);
      // if (!stfe->thread_completed) Text("Current file: %s", get_last_file_copy(stfe).data);
    }
    
    if (stfe->thread_completed) {
      Text("String bytes stored: %s", format_bytes(stfe->dirs.strings->count + stfe->files.strings->count).data);
      Text("Elapsed time: %s", format_time_seconds(stfe->end_time-stfe->start_time).data);
    }
  }
  if (stfe && stfe->thread_started && !stfe->thread_completed) {
    Assert(stfe->master_thread != nullptr);
    if (thread_is_done(stfe->master_thread)) {
      thread_deinit(stfe->master_thread, true);
      stfe->thread_completed = true;
    }
  }
  
  if (all_drives_enumerated && Button("Save enumerated data")) {
    if (!Serialize_ST_File_Enumeration(file_path)) {
      log_error("Failed to write enumerated files");
      os_log_error();
    }
  }
  
  if (all_drives_enumerated && Button("Reset State")) {
    free_ex1_workspace_and_reset();
    free_stfe_and_reset();
    return;
  }
  
  if (all_drives_enumerated) {
    Ex1_show_enumeration_workspace();
  }
  
  if (drives.count > 0 && !all_drives_enumerated && Button("Enumerate all NTFS drives")) { //  && ex1_ntfs.initialized
    os_run_file_enumeration_single_threaded();
    // os_run_file_enumeration_multithreaded(); // #disabled for now
    return;
  }
}

void ImGui_Debug_Panel () { using namespace ImGui;
  push_allocator(temp());
  
  Begin("Debug Panel");
  // #cpuid
  Text("[cpus] physical: %d, logical: %d, primary: %d, secondary: %d", os_cpu_physical_core_count(), os_cpu_logical_core_count(), os_cpu_primary_core_count(), os_cpu_secondary_core_count());
#if BUILD_DEBUG
  SeparatorText("Default Allocator Allocations");
  { lock_guard(&allocator_mutex);
    // Replace this with two sliders min and max to filter allocations?
    Checkbox("Show small allocations (<1kB)", &default_allocator_show_small_allocations);
    auto allocations = to_view(get_general_allocator_data()->allocations);
    Text("%s in %lld allocations", 
      format_bytes(get_general_allocator_data()->total_bytes_allocated).data,
      allocations.count);
    for_each(a, allocations) {
      if (!default_allocator_show_small_allocations && allocations[a].size < 1024) {
        continue;
      }
      Text(" [%lld] ptr: %p (size: %s, alignment: %d)", 
        a, allocations[a].memory, format_bytes(allocations[a].size).data, allocations[a].alignment);
    }
  }
  SeparatorText("Arenas in Use");
  { lock_guard(&arenas_in_use_mutex);
    Assert(arenas_in_use.allocated > 0);
    for_each(a, arenas_in_use) {
      auto arena = arenas_in_use[a];
      if (!is_valid(arena)) continue;
      Text("[%d], source %s:%d (%s)", a, arena->file_path.data, arena->line_number, arena->function_name.data);
    }
  }
#endif // BUILD_DEBUG
  SeparatorText("Child Threads");
  SeparatorText("Errors");
  ArrayView<Error*> errors = get_all_errors(thread_context());
  if (errors.count && Button("Clear all errors")) {
    clear_errors(thread_context());
    errors.count = 0;
  }
  for_each(e, errors) {
    auto button_label = format_cstring("Clear##%d", e);
    if (Button(button_label)) {
      clear_error(thread_context(), errors[e]);
      continue;
    }
    SameLine();
    Text(" [%d] %s", e, to_string(errors[e]).data);
  }
  Spacing();
  if (Button("Push some error")) {
    log_warning("This is a warning.");
    log_error("... and this is an error.");
  }
  
  End();
}