#include <cmath>

#include <pqxx/transaction>

#include "helpers.hxx"

namespace
{
/// Test conversions for some floating-point type.
template<typename T> void infinity_test()
{
  T const inf{std::numeric_limits<T>::infinity()};
  static constexpr auto big_num{static_cast<T>(999999999)};

  auto inf_string = pqxx::to_string(inf);
  T back_conversion;
  pqxx::from_string(inf_string, back_conversion);
  PQXX_CHECK_LESS(big_num, back_conversion);

  inf_string = pqxx::to_string(-inf);
  pqxx::from_string(inf_string, back_conversion);
  PQXX_CHECK_LESS(back_conversion, -big_num);
}

void test_infinities(pqxx::test::context &)
{
  infinity_test<float>();
  infinity_test<double>();

  // Skip this test when building for a Valgrind run.  The tool doesn't seem
  // to support long double.
#if !defined(PQXX_VALGRIND)
  infinity_test<long double>();
#endif
}


/// Reproduce bug #262: repeated float conversions break without charconv.
template<typename T> void bug_262()
{
  pqxx::connection cx;
  cx.prepare("stmt", "select cast($1 as float)");
  pqxx::work tr{cx};

  // We must use the same float type both for passing the value to the
  // statement and for retrieving result of the statement execution.  This is
  // due to an internal stringstream being instantiated as a a parameterized
  // thread-local singleton.  So, there are separate stream<float>,
  // stream<double>, stream<long double>, but every such instance is a
  // singleton. We should use only one of them for this test.

  pqxx::row row;

  // Nothing bad here, select a float value.
  // The stream is clear, so just fill it with the value and extract str().
  row = tr.exec("SELECT 1.0").one_row();

  // This works properly, but as we parse the value from the stream, the
  // seeking cursor moves towards the EOF. When the inevitable EOF happens
  // 'eof' flag is set in the stream and 'good' flag is unset.
  std::ignore = row[0].as<T>();

  // The second try. Select a float value again. The stream is not clean, so
  // we need to put an empty string into its buffer {stream.str("");}. This
  // resets the seeking cursor to 0. Then we will put the value using
  // operator<<().
  // ...
  // ...
  // OOPS. stream.str("") does not reset 'eof' flag and 'good' flag! We are
  // trying to read from EOF! This is no good.
  // Throws on unpatched pqxx v6.4.5
  row = tr.exec("SELECT 2.0").one_row();

  // We won't get here without patch. The following statements are just for
  // demonstration of how are intended to work. If we
  // simply just reset the stream flags properly, this would work fine.
  // The most obvious patch is just explicitly stream.seekg(0).
  std::ignore = row[0].as<T>();
  row = tr.exec("SELECT 3.0").one_row();
  std::ignore = row[0].as<T>();
}


/// Test for bug #262.
void test_bug_262(pqxx::test::context &)
{
  bug_262<float>();
  bug_262<double>();
#if !defined(PQXX_VALGRIND)
  bug_262<long double>();
#endif
}


/// Test conversion of malformed floating-point values.
void test_bad_float(pqxx::test::context &)
{
  float x [[maybe_unused]]{};
  PQXX_CHECK_THROWS(x = pqxx::from_string<float>(""), pqxx::conversion_error);

  PQXX_CHECK_THROWS(
    x = pqxx::from_string<float>("Infancy"), pqxx::conversion_error);
  PQXX_CHECK_THROWS(
    x = pqxx::from_string<float>("-Infighting"), pqxx::conversion_error);

  PQXX_CHECK_THROWS(
    x = pqxx::from_string<float>("Nanny"), pqxx::conversion_error);
}


template<typename T> void test_float_length(T value)
{
  auto const text{pqxx::to_string(value)};
  PQXX_CHECK_GREATER_EQUAL(
    pqxx::size_buffer(value), std::size(text),
    std::format("Not enough buffer space for '{}'.", text));
}


/// Test conversion of long float values to strings.
void test_long_float(pqxx::test::context &)
{
  test_float_length(0.1f);
  test_float_length(0.1);

  test_float_length(std::numeric_limits<float>::denorm_min());
  test_float_length(-std::numeric_limits<float>::denorm_min());
  test_float_length(std::numeric_limits<float>::min());
  test_float_length(-std::numeric_limits<float>::min());
  test_float_length(std::numeric_limits<float>::max());
  test_float_length(-std::numeric_limits<float>::max());
  test_float_length(-std::nextafter(1.0f, 2.0f));

  test_float_length(std::numeric_limits<double>::denorm_min());
  test_float_length(-std::numeric_limits<double>::denorm_min());
  test_float_length(std::numeric_limits<double>::min());
  test_float_length(-std::numeric_limits<double>::min());
  test_float_length(std::numeric_limits<double>::max());
  test_float_length(-std::numeric_limits<double>::max());
  test_float_length(-std::nextafter(1.0, 2.0));

#if !defined(PQXX_VALGRIND)
  test_float_length(std::numeric_limits<long double>::denorm_min());
  test_float_length(-std::numeric_limits<long double>::denorm_min());
  test_float_length(std::numeric_limits<long double>::min());
  test_float_length(-std::numeric_limits<long double>::min());
  test_float_length(std::numeric_limits<long double>::max());
  test_float_length(-std::numeric_limits<long double>::max());
  test_float_length(-std::nextafter(1.0L, 2.0L));
#endif

  // Ahem.  I'm not proud of this.  We really can't assume much about the
  // floating-point types, but I'd really like to try a few things to see that
  // buffer sizes are in the right ballpark.  So, if "double" is at least 64
  // bits, check for some examples of long conversions.
  if constexpr (sizeof(double) >= 8)
  {
    auto constexpr awkward{-2.2250738585072014e-308};
    auto const text{pqxx::to_string(awkward)};
    PQXX_CHECK_LESS_EQUAL(
      std::size(text), 25u, text + " converted to too long a string.");
  }
  if constexpr (sizeof(double) <= 8)
  {
    auto const text{pqxx::to_string(0.99)};
    PQXX_CHECK_LESS_EQUAL(
      pqxx::size_buffer(0.99), 25u, text + " converted to too long a string.");
  }
}


PQXX_REGISTER_TEST(test_infinities);
PQXX_REGISTER_TEST(test_bug_262);
PQXX_REGISTER_TEST(test_bad_float);
PQXX_REGISTER_TEST(test_long_float);
} // namespace
