#include "grouped_finder.h"

#include <mutex>
#include <thread>
#include <vector>

using std::mutex, std::vector, std::thread, std::string, std::string_view;

GroupedFinder::GroupedFinder(const WordList &word_list) {
  for (const auto &word : word_list) {
    groups_[word.front()].push_back(&word);
  }
}

WordRefList GroupedFinder::find_prefix(string_view search_prefix) const {
  const auto group = groups_.find(search_prefix.front());
  if (group == groups_.cend()) {
    return {};
  }

  const auto word_list = group->second;
  const size_t word_list_size = word_list.size();
  const size_t thread_count =
      std::min<size_t>(std::thread::hardware_concurrency(), word_list_size);
  const size_t words_per_thread = word_list_size / thread_count;

  WordRefList search_results;
  mutex search_results_mutex;

  vector<thread> search_threads;
  for (size_t thread_index = 0; thread_index < thread_count; ++thread_index) {
    const bool is_last_thread = thread_index == thread_count - 1;
    const size_t start_index = thread_index * words_per_thread;
    const size_t end_index =
        is_last_thread ? word_list_size : start_index + words_per_thread;

    search_threads.emplace_back([&, start_index, end_index] {
      WordRefList local_results;

      for (size_t word_index = start_index; word_index < end_index;
           ++word_index) {
        const auto *current_word = word_list[word_index];
        if (current_word->starts_with(search_prefix)) {
          local_results.push_back(current_word);
        }
      }

      if (!local_results.empty()) {
        const std::lock_guard lock(search_results_mutex);
        std::move(local_results.begin(), local_results.end(),
                  std::back_inserter(search_results));
      }
    });
  }

  for (auto &thread : search_threads) {
    thread.join();
  }

  return search_results;
}
