#include "bandit/bandit/bandit.h"
#include "../../src/cdo_options.h"
// BANDIT NEEDS TO BE INCLUDED FIRST!!!

#include "../../src/util_string.h"
#include <string>
#include <limits>  // std::reference_wrapper
#include <tuple>
#include <cmath>

using namespace snowhouse;
void
cdoExit()
{
}
template <typename T>
void
test_string_to_number_unsigned()
{
  T num_max = std::numeric_limits<T>::max();
  std::stringstream str_num_max;
  str_num_max << std::setprecision(std::numeric_limits<T>::max_digits10) << num_max;

  T num_min = std::numeric_limits<T>::min();
  std::stringstream str_num_min;
  str_num_min << std::setprecision(std::numeric_limits<T>::max_digits10) << num_min;

  std::vector<std::string> to_be_tested;
  std::vector<T> expected;

  to_be_tested = { "1", "0", "120", str_num_max.str() };
  expected = { 1, 0, 120, std::numeric_limits<T>::max() };
  for (std::size_t i = 0; i < to_be_tested.size(); i++)
    {
      auto msg = "correctly converts " + to_be_tested[i] + " to " + std::to_string(expected[i]);
      bandit::it(msg, [&]() {
        bool success;
        T result;
        std::tie(success, result) = string_to_integral<T>(to_be_tested[i]);
        AssertThat(success, Equals(true));
        AssertThat(result, Equals(expected[i]));
      });
    }
}

template <typename T>
void
test_string_to_number()
{
  T num_max = std::numeric_limits<T>::max();
  std::stringstream str_num_max;
  str_num_max << std::setprecision(std::numeric_limits<T>::max_digits10) << num_max;

  T num_min = std::numeric_limits<T>::min();
  std::stringstream str_num_min;
  str_num_min << std::setprecision(std::numeric_limits<T>::max_digits10) << num_min;

  std::vector<std::string> to_be_tested;
  std::vector<T> expected;

  to_be_tested = { "1", "0", str_num_max.str(), "-1", str_num_min.str() };
  expected = { 1, 0, std::numeric_limits<T>::max(), -1, std::numeric_limits<T>::min() };

  for (std::size_t i = 0; i < to_be_tested.size(); i++)
    {
      auto msg = "correctly converts " + to_be_tested[i] + " to " + std::to_string(expected[i]);
      bandit::it(msg, [&]() {
        bool success;
        T result;
        std::tie(success, result) = string_to_integral<T>(to_be_tested[i]);
        AssertThat(success, Equals(true));
        AssertThat(result, Equals(expected[i]));
      });
    }
}

template <typename T>
void
test_string_to_number_fl()
{
  T num_max = std::numeric_limits<T>::max();
  std::stringstream str_num_max;
  str_num_max << std::setprecision(std::numeric_limits<T>::max_digits10) << num_max;

  T num_min = std::numeric_limits<T>::min();
  std::stringstream str_num_min;
  str_num_min << std::setprecision(std::numeric_limits<T>::max_digits10) << num_min;

  std::vector<std::string> to_be_tested = { "0.0", "0", "0.0e1", "2.e3", "2.0e3", "2.0625", str_num_max.str(), str_num_min.str() };
  std::vector<T> expected = { 0, 0, 0, 2000, 2000, 2.0625, num_max, num_min };
  for (std::size_t i = 0; i < to_be_tested.size(); i++)
    {
      auto msg = "correctly converts " + to_be_tested[i] + " to " + get_scientific(expected[i]);
      bandit::it(msg, [&]() {
        bool success;
        T result;
        std::tie(success, result) = string_to_floating<T>(to_be_tested[i]);
        AssertThat(success, Equals(true));
        AssertThat(result, Equals(expected[i]));
      });
    }
}

template <auto func>
void
test_string_to_number_failures()
{
  std::vector<std::string> to_be_tested = { "a0.02", "2.e3a", "2.a0e3", "2ff.01f" };
  for (std::size_t i = 0; i < to_be_tested.size(); i++)
    {
      auto msg = "correctly throws error on " + to_be_tested[i];
      bandit::it(msg, [&]() {
        auto [success, result] = func(to_be_tested[i]);
        AssertThat(success, Equals(false));
        AssertThat(std::isnan(result), Equals(true));
      });
    }
}

template <auto func>
void
test_string_to_integral_failures()
{
  std::vector<std::string> to_be_tested = { "1.0", "a2", "2a", "2e" };
  for (std::size_t i = 0; i < to_be_tested.size(); i++)
    {
      auto msg = "correctly throws error on " + to_be_tested[i];
      bandit::it(msg, [&]() {
        auto [success, result] = func(to_be_tested[i]);
        AssertThat(success, Equals(false));
        AssertThat(std::numeric_limits<decltype(result)>::max(), Equals(result));
      });
    }
}

go_bandit([]() {
  //==============================================================================
  //
  //
  bandit::describe("Testing trim functions", []() {
    bandit::describe("Testing ltrim function", []() {
      std::string to_be_trimmed = "  spaces in front  ";
      std::string expected = "spaces in front  ";
      bandit::it("returns the right trim", [&]() {
        std::string result = Util::String::ltrim(to_be_trimmed);
        AssertThat(result, Equals(expected));
      });
    });
  });
  bandit::describe("Testing string_to_number", []() {
    bandit::describe("Testing string_to_number positive", []() {
      bandit::describe("Testing int", []() { test_string_to_number<int>(); });
      bandit::describe("Testing long", []() { test_string_to_number<long>(); });
      bandit::describe("Testing unsigned", []() { test_string_to_number_unsigned<unsigned>(); });
      bandit::describe("Testing float", []() { test_string_to_number_fl<float>(); });
      bandit::describe("Testing double", []() { test_string_to_number_fl<double>(); });
      bandit::describe("Testing long double", []() { test_string_to_number_fl<long double>(); });
    });

    bandit::describe("Testing string_to_number negatives", []() {
      bandit::describe("Testing errors for float", []() {
        test_string_to_number_failures<string_to_floating<float>>();
        bandit::it("correctly throws error on empty string with float", [&]() {
          auto [success_f, result_f] = string_to_floating<float>("");
          AssertThat(success_f, Equals(false));
          AssertThat(std::isnan(result_f), Equals(true));
        });
      });
      bandit::describe("Testing errors for double", []() { test_string_to_number_failures<string_to_floating<double>>();

        bandit::it("correctly throws error on empty string with double", [&]() {
          auto [success_d, result_d] = string_to_floating<double>("");
          AssertThat(success_d, Equals(false));
          AssertThat(std::isnan(result_d), Equals(true));
        });
          });

      bandit::describe("Testing errors for long double", []() {
        test_string_to_number_failures<string_to_floating<long double>>();

        bandit::it("correctly throws error on empty string with long double", [&]() {
          auto [success_d, result_d] = string_to_floating<long double>("");
          AssertThat(success_d, Equals(false));
          AssertThat(std::isnan(result_d), Equals(true));
        });
      });

      bandit::describe("Testing errors for int", []() {
        test_string_to_integral_failures<string_to_integral<int>>();
        bandit::it("correctly throws error on empty string with int", [&]() {
          auto [success_i, result_i] = string_to_integral<int>("");
          AssertThat(success_i, Equals(false));
          AssertThat(std::numeric_limits<int>::max(), Equals(result_i));
        });
      });

      bandit::describe("Testing errors for int", []() {
        test_string_to_integral_failures<string_to_integral<unsigned int>>();
        bandit::it("correctly throws error on empty string with unsigned int", [&]() {
          auto [success_ui, result_ui] = string_to_integral<unsigned int>("");
          AssertThat(success_ui, Equals(false));
          AssertThat(std::numeric_limits<unsigned int>::max(), Equals(result_ui));
        });
      });
    });
  });

  bandit::describe("Testing rtrim function", []() {
    std::string to_be_trimmed = "  spaces in back  ";
    std::string expected = "  spaces in back";
    bandit::it("returns the right trim", [&]() {
      std::string result = Util::String::rtrim(to_be_trimmed);
      AssertThat(result, Equals(expected));
    });
  });
  bandit::describe("Testing trim function", []() {
    std::string to_be_trimmed = "  no spaces  ";
    std::string expected = "no spaces";
    bandit::it("returns the right trim", [&]() {
      std::string result = Util::String::trim(to_be_trimmed);
      AssertThat(result, Equals(expected));
    });
  });

  bandit::describe("Testing function 'split_args'", []() {
    bandit::it("splits simple arguments", []() {
      std::string test_args = "abc,def,ghi";
      std::vector<std::string> expected = { "abc", "def", "ghi" };
      std::vector<std::string> result = split_args(test_args);
      AssertThat(result, EqualsContainer(expected));
    });
    bandit::it("allows for ',' to be escaped and removes the '\\'", []() {
      std::string test_args = "abc,\"def\\,ghi\",jkl";
      std::vector<std::string> expected = { "abc", "\"def,ghi\"", "jkl" };
      std::vector<std::string> result = split_args(test_args);
      AssertThat(result, EqualsContainer(expected));
    });
    bandit::it("handles leftover ' correctly", []() {
      std::string test_args = "abc,";
      AssertThrows(std::runtime_error, split_args(test_args));
    });
  });
});
int
main(int argc, char **argv)
{
  int result = bandit::run(argc, argv);

  return result;
}
