#include "fast_float/fast_float.h"

#include <ios>
#include <iostream>
#include <random>
#include <sstream>
#include <string>
#include <system_error>
#include <utility>
#include <vector>

#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) ||     \
    defined(sun) || defined(__sun)
// Anything at all that is related to cygwin, msys and so forth will
// always use this fallback because we cannot rely on it behaving as normal
// gcc.
#include <locale>

// workaround for CYGWIN
double cygwin_strtod_l(char const *start, char **end) {
  double d;
  std::stringstream ss;
  ss.imbue(std::locale::classic());
  ss << start;
  ss >> d;
  if (ss.fail()) {
    *end = nullptr;
  }
  if (ss.eof()) {
    ss.clear();
  }
  auto nread = ss.tellg();
  *end = const_cast<char *>(start) + nread;
  return d;
}

float cygwin_strtof_l(char const *start, char **end) {
  float d;
  std::stringstream ss;
  ss.imbue(std::locale::classic());
  ss << start;
  ss >> d;
  if (ss.fail()) {
    *end = nullptr;
  }
  if (ss.eof()) {
    ss.clear();
  }
  auto nread = ss.tellg();
  *end = const_cast<char *>(start) + nread;
  return d;
}
#endif

std::pair<double, bool> strtod_from_string(char const *st) {
  double d;
  char *pr;
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) ||     \
    defined(sun) || defined(__sun)
  d = cygwin_strtod_l(st, &pr);
#elif defined(_WIN32)
  static _locale_t c_locale = _create_locale(LC_ALL, "C");
  d = _strtod_l(st, &pr, c_locale);
#else
  static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL);
  d = strtod_l(st, &pr, c_locale);
#endif
  if (st == pr) {
    std::cerr << "strtod_l could not parse '" << st << std::endl;
    return std::make_pair(0, false);
  }
  return std::make_pair(d, true);
}

std::pair<float, bool> strtof_from_string(char *st) {
  float d;
  char *pr;
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) ||     \
    defined(sun) || defined(__sun)
  d = cygwin_strtof_l(st, &pr);
#elif defined(_WIN32)
  static _locale_t c_locale = _create_locale(LC_ALL, "C");
  d = _strtof_l(st, &pr, c_locale);
#else
  static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL);
  d = strtof_l(st, &pr, c_locale);
#endif
  if (st == pr) {
    std::cerr << "strtof_l could not parse '" << st << std::endl;
    return std::make_pair(0.0f, false);
  }
  return std::make_pair(d, true);
}

bool tester() {
  std::random_device rd;
  std::mt19937 gen(rd());
  for (int q = 18; q <= 27; q++) {
    std::cout << "q = " << -q << std::endl;
    uint64_t power5 = 1;
    for (int k = 0; k < q; k++) {
      power5 *= 5;
    }
    uint64_t low_threshold = 0x20000000000000 / power5 + 1;
    uint64_t threshold = 0xFFFFFFFFFFFFFFFF / power5;
    std::uniform_int_distribution<uint64_t> dis(low_threshold, threshold);
    for (size_t i = 0; i < 10000; i++) {
      uint64_t mantissa = dis(gen) * power5;
      std::stringstream ss;
      ss << mantissa;
      ss << "e";
      ss << -q;
      std::string to_be_parsed = ss.str();
      std::pair<double, bool> expected_double =
          strtod_from_string(to_be_parsed.c_str());
      double result_value;
      auto result = fast_float::from_chars(
          to_be_parsed.data(), to_be_parsed.data() + to_be_parsed.size(),
          result_value);
      if (result.ec != std::errc() &&
          result.ec != std::errc::result_out_of_range) {
        std::cout << to_be_parsed << std::endl;
        std::cerr << " I could not parse " << std::endl;
        return false;
      }
      if (result_value != expected_double.first) {
        std::cout << to_be_parsed << std::endl;
        std::cerr << std::hexfloat << result_value << std::endl;
        std::cerr << std::hexfloat << expected_double.first << std::endl;
        std::cerr << " Mismatch " << std::endl;
        return false;
      }
    }
  }
  return true;
}

int main() {
  if (tester()) {
    std::cout << std::endl;
    std::cout << "all ok" << std::endl;
    return EXIT_SUCCESS;
  }
  std::cerr << std::endl;
  std::cerr << "errors were encountered" << std::endl;
  return EXIT_FAILURE;
}
