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;

void ntfs_create_enumeration_threads (s32 thread_count) {
  if (!ex1_ntfs.initialized) { Timed_Block_Print("Thread initialization (ntfs)");
    ex1_ntfs.initialized = true;
    ex1_ntfs.threads = ArrayView<Thread>(thread_count);
    ex1_ntfs.threads_in_flight.allocator = GPAllocator();
    for_each(t, ex1_ntfs.threads) {
      string thread_name = format_string("ntfs_enumeration_thread#%d", t);
      bool success = thread_init(&ex1_ntfs.threads[t], ntfs_enumeration_thread_proc, thread_name);
      Assert(success);
    }
  }
}

#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 };

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());
  }
  // #Ex1_hotkeys
  return false;
}

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

  Begin("Control Panel");
  if (Button("Debug break")) { debug_break(); }
  if (Button("Discover drives") || !table_is_valid(drive_table)) {
    Win32_Discover_Drives();
  }
  
  Text("drive_table is valid: %d", table_is_valid(drive_table));
  push_allocator(temp());
  ArrayView<OS_Drive*> drives = os_get_available_drives(); // only includes drives that are ready.
  
  for_each(i, drives) {
    OS_Drive* drive = drives[i];
    
    Text(" > [%d] drive letter: %s (is_present: %d)", drives.count + 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);
  
  if (drives.count > 0 && !all_drives_enumerated && Button("Enumerate all NTFS drives")) { //  && ex1_ntfs.initialized
    // if drive count exceeds the number of threads, we need to group them so each thread 
    // can enumerate multiple drives. 
    // We need to distribute the drives across our available threads:
    push_allocator(GPAllocator());
    Array<ArrayView<OS_Drive*>> drive_split;
    drive_split.allocator = temp(); // this is only needed for this frame

    if (drives.count > os_cpu_physical_core_count()) {
      s32 thread_count = os_cpu_physical_core_count();
      array_resize(drive_split, thread_count);
      ntfs_create_enumeration_threads(thread_count);
      
      s32 threads_to_create = thread_count;
      s64 drives_per_thread = (drives.count / thread_count);
      s64 remainder = drives.count % thread_count;
      s64 current_drive = 0;
      
      for_each(d, drive_split) {
        if (d == drive_split.count) {
          drive_split[d] = ArrayView<OS_Drive*>(remainder);
        } else {
          drive_split[d] = ArrayView<OS_Drive*>(drives_per_thread);
        }
        
        for (s64 i = 0; i < drive_split[d].count; i += 1) {
          drive_split[d][i] = drives[current_drive];
          current_drive += 1;
        }
      }
      
      debug_break(); // #TODO: Check that the work has been distributed correctly.
    } else { // more threads than drives, or same amount
      s32 thread_count = (s32)drives.count;
      array_resize(drive_split, drives.count);
      ntfs_create_enumeration_threads(thread_count);
      
      for_each(d, drives) {
        auto drive = drives[d];
        drive_split[d] = ArrayView<OS_Drive*>(1); // Arrays of size one are sad :pensive:
        
        drive_split[d][0] = drive;
      }
    }
    
    s64 active_thread_count = drive_split.count;
    
    ex1_ntfs.threads_started = true;
    for (s64 t = 0; t < active_thread_count; t += 1) {
      Thread* thread = &ex1_ntfs.threads[t];
      Arena* thread_arena = next_arena(Arena_Reserve::Size_64K);
      push_arena(thread_arena);
      auto thread_data = New<NTFS_Enumeration_Task>();
        thread_data->pool = thread_arena;
        thread_data->drives = drive_split[t];
      thread_start(thread, thread_data);
      array_add(ex1_ntfs.threads_in_flight, thread);
    }
  }
  
  if (ex1_ntfs.threads_in_flight.count) {
    Text("Threads in flight: %d", ex1_ntfs.threads_in_flight.count);

    for_each(t, ex1_ntfs.threads_in_flight) {
      if (thread_is_done(ex1_ntfs.threads_in_flight[t])) {
        push_allocator(GPAllocator());
        
        Thread* thread = ex1_ntfs.threads_in_flight[t];
        auto task = thread_task(NTFS_Enumeration_Task);
        array_free(task->drives);

        // make sure to retreive any data you need to from here!
        release_arena(task->pool);
        
        thread_deinit(ex1_ntfs.threads_in_flight[t], false);
        array_unordered_remove_by_index(ex1_ntfs.threads_in_flight, t);
        t -= 1; // check this element index again!
      }
    }
  }
  
  if (ex1_ntfs.threads_started && !ex1_ntfs.threads_in_flight.count) {
    // All threads are complete, we're free to clean up remaining memory
    push_allocator(GPAllocator());
    array_free(ex1_ntfs.threads);
    array_free(ex1_ntfs.threads_in_flight);
    
    // Instead maybe we should just memset this to zero.
    reset_struct(&ex1_ntfs);
  }
  
  // How do I tell when all files are enumerated?
  // check drives[i]->data.paths.wstrings.count count?
  if (all_drives_enumerated && Button("Save drive data")) {
    string file_path = format_string("%s_DriveData.bin", os_get_machine_name().data);
    bool success = Serialize_Win32_Drives(drives, file_path);
    if (!success) { log_error("Failed to save Win32_Drive data"); }
  }
  
  if (all_drives_enumerated && Button("Clear all drive data")) {
    os_clear_drive_data();
  }
  
  End();
}

void ImGui_Debug_Panel () { using namespace ImGui;
  push_allocator(temp());
  
  Begin("Debug Panel");
  SeparatorText("ex1_ntfs");
  Text("Threads in flight count: %d", ex1_ntfs.threads_in_flight.count);
  for_each(i, ex1_ntfs.threads) {
    Text(" [%d] initialized: %d, has_context: %d, has_data: %d", 
      i, ex1_ntfs.threads[i].proc != nullptr, ex1_ntfs.threads[i].context != nullptr, ex1_ntfs.threads[i].data != nullptr);
  }
  // #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());
  SeparatorText("Arena In-Use List");
  for (u8 i = 0; i < Arena_Reserve_Count; i += 1) {
#if ARENA_DEBUG
    auto t = format_cstring(
      " [%s] in_use: %d, committed_bytes: %s", 
      format_bytes(Arena_Sizes[i], 0).data,
      arena_free_list->in_flight_count[i],
      format_bytes(committed_bytes(arena_free_list->in_flight[i])).data
      );
#else
    auto t = format_cstring(
      " [%s] in_use: %d, committed_bytes: %s", 
      format_bytes(Arena_Sizes[i], 0).data,
      arena_free_list->in_flight_count[i],
      "disabled in release mode"
      );
#endif
    Text(t);
  }
  SeparatorText("Arena Free List");
  for (u8 i = 0; i < Arena_Reserve_Count; i += 1) {
    auto t = format_cstring(
      " [%s] free: %d, committed_bytes: %s", 
      format_bytes(Arena_Sizes[i], 0).data,
      (s32)arena_free_list->free_table[i].count,
      format_bytes(committed_bytes(arena_free_list->free_table[i])).data
      );
    Text(t);
  }
  SeparatorText("Child Threads");
  SeparatorText("Errors");
  ArrayView<Error*> errors = get_all_errors(thread_context());
  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();
}