// -----------------------------------------------------------------------------------------------------
// Copyright (c) 2006-2020, Knut Reinert & Freie Universität Berlin
// Copyright (c) 2016-2020, Knut Reinert & MPI für molekulare Genetik
// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
// -----------------------------------------------------------------------------------------------------

#include <gtest/gtest.h>

#include <iostream>
#include <limits>

#include <seqan3/std/charconv>
#include <seqan3/std/concepts>

// =============================================================================
// std::from_chars for float, double and long double
// =============================================================================

template <typename T>
class from_char_real_test: public ::testing::Test { };

using real_types = ::testing::Types<float, double, long double>;

TYPED_TEST_SUITE(from_char_real_test, real_types, );

TYPED_TEST(from_char_real_test, real_numbers)
{
    std::setlocale(LC_NUMERIC, "C");
    {
        TypeParam val{};
        std::string str = "1234";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{1234});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "1.2e3";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{1200});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "1.2e-3";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{0.0012});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "1.e2";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{100});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "1.";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{1});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = ".2e3";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{200});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "2e3";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{2000});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "2";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{2});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "4em";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{4});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + 1);
    }

    {
        TypeParam val{};
        std::string str = "-1.2e3";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{-1200});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{42};
        std::string str = "-.3";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{-0.3});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{42};
        std::string str = "1.2e";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{1.2});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + 3);
    }

    {
        TypeParam val{42};
        std::string str = "0.0";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{0});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    // Read only until a certain position
    {
        TypeParam val{42};
        std::string str = "3.194357";
        auto res = std::from_chars(&str[0], &str[0] + 4, val);
        EXPECT_FLOAT_EQ(val, TypeParam{3.19});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + 4);
    }

    // Partial Parsing
    {
        TypeParam val{42};
        std::string str = "3.19abc";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{3.19});
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + 4);
    }
}

TYPED_TEST(from_char_real_test, infinity_value)
{
    {
        TypeParam val{};
        std::string str = "inf";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_EQ(val, std::numeric_limits<TypeParam>::infinity());
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "infinity";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_EQ(val, std::numeric_limits<TypeParam>::infinity());
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "INF";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_EQ(val, std::numeric_limits<TypeParam>::infinity());
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "INFINITY";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_EQ(val, std::numeric_limits<TypeParam>::infinity());
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

}

TYPED_TEST(from_char_real_test, nan_value)
{
    // Note:
    // According to the IEEE standard, NaN values have the odd property that
    // comparisons involving them are always false. That is, for a float f,
    // f != f will be true only if f is NaN.
    {
        TypeParam val{};
        std::string str = "nan";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_TRUE(std::isnan(val));
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "NAN";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_TRUE(std::isnan(val));
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "nan(abc)";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_TRUE(std::isnan(val));
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }

    {
        TypeParam val{};
        std::string str = "NAN(abc)";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_TRUE(std::isnan(val));
        EXPECT_EQ(res.ec, std::errc{});
        EXPECT_EQ(res.ptr, &str[0] + str.size());
    }
}

TYPED_TEST(from_char_real_test, non_valid_strings)
{
    {
        TypeParam val{42};
        std::string str = "e3";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{42});
        EXPECT_EQ(res.ec, std::errc::invalid_argument);
    }

    {
        TypeParam val{42};
        std::string str = "+1.2e3";
        auto res = std::from_chars(&str[0], &str[0] + str.size(), val);
        EXPECT_FLOAT_EQ(val, TypeParam{42});
        EXPECT_EQ(res.ec, std::errc::invalid_argument);
    }
}

// =============================================================================
// std::to_chars for float, double and long double
// =============================================================================

TYPED_TEST(from_char_real_test, to_chars)
{
    TypeParam val{120.3};
    std::array<char, 10> buffer{};

    auto res = std::to_chars(buffer.data(), buffer.data() + buffer.size(), val);

    EXPECT_EQ(res.ptr, &buffer[5]);
    EXPECT_EQ(res.ec, std::errc{});
    EXPECT_EQ((std::string_view{buffer.data(), 5}), std::string_view{"120.3"});
}
