Source File: ex53.cpp

#include <fmt/core.h>
#include <chrono>
#include <thread>
#include <string>
#include <optional>
#include <expected>
#include <variant>
#include <ranges>

enum class find_error {
  not_found, empty_list
};

using FoundNumbers = std::vector<std::tuple<size_t, int>>;

void handle_error(find_error err) {
  switch(err) {
    case find_error::not_found:
      fmt::println("Error not found.");
      break;
    case find_error::empty_list:
      fmt::println("Empty list, nothing to find.");
      break;
    default:
      fmt::println("Unknown error code: {}", int(err));
  }
}

std::optional<FoundNumbers> find_int(const std::vector<int>& numbers, int num) {
  auto it = std::ranges::find(numbers, num);
  FoundNumbers found;

  for(const auto& [i, value] : std::views::enumerate(numbers)) {
    // found it, because iterator is not equal to """end""" LOL
    if(value == num) {
      found.emplace_back(it - numbers.begin(), value);
    }
  }

  if(found.empty()) return std::nullopt;

  return found;
}


FoundNumbers must_find_throws(const std::vector<int>& numbers, int num) {
  if(numbers.empty()) throw std::invalid_argument("No empty list allowed.");
  FoundNumbers found;

  for(const auto& [i, value] : std::views::enumerate(numbers)) {
    if(value == num) found.emplace_back(i, value);
  }

  if(found.empty()) throw std::length_error("No found.");

  return found;
}

std::expected<FoundNumbers, find_error> must_find_expected(const std::vector<int>& numbers, int num) {
  if(numbers.empty()) return std::unexpected(find_error::empty_list);
  FoundNumbers found;

  for(const auto& [i, value] : std::views::enumerate(numbers)) {
    if(value == num) found.emplace_back(i, value);
  }

  if(found.empty()) return std::unexpected(find_error::not_found);

  return found;
}

std::expected<FoundNumbers, find_error> must_find_horror(const std::vector<int>& numbers, int num) {
  using namespace std::ranges;

  if(numbers.empty()) {
    return std::unexpected{find_error::empty_list};
  }

  // remove const and watch the horror show
  auto matched = [&](const auto &tuple) -> bool {
    auto [i, value] = tuple;
    return value == num;
  };

  auto found_nums = numbers | views::enumerate | views::filter(matched) | to<FoundNumbers>();

  if(found_nums.empty()) {
    return std::unexpected{find_error::not_found};
  }

  return found_nums;
}

int main() {
  std::vector<int> numbers{1,2,3,3,4,5,10};

  if(auto found = find_int(numbers, 100)) {
    fmt::println("Found {} count numbers", found->size());
  } else {
    fmt::println("Not found.");
  }

  auto needs = must_find_expected(numbers, 3);
  if(needs) {
    fmt::println("Found {} count", needs->size());
  } else {
    handle_error(needs.error());
  }

  std::vector<int> empty;
  auto bad_empty = must_find_expected(empty, 1000);
  handle_error(bad_empty.error());

  try {
    auto found_bs = must_find_throws(empty, 3);
  } catch(const std::exception& e) {
    fmt::println("ERROR: {}", e.what());
  }

  auto bad = must_find_horror(numbers, 3);
  if(bad) {
    fmt::println("The horrific version found {}", bad->size());
  } else {
    handle_error(bad.error());
  }
}