//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_IMPL_TEXT_PROTOCOL_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_IMPL_TEXT_PROTOCOL_HPP

#include <boost/mysql/blob_view.hpp>
#include <boost/mysql/datetime.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/metadata.hpp>
#include <boost/mysql/string_view.hpp>

#include <boost/mysql/detail/datetime.hpp>

#include <boost/mysql/impl/internal/protocol/impl/bit_deserialization.hpp>
#include <boost/mysql/impl/internal/protocol/impl/deserialization_context.hpp>
#include <boost/mysql/impl/internal/protocol/impl/protocol_types.hpp>
#include <boost/mysql/impl/internal/protocol/impl/span_string.hpp>

#include <boost/assert.hpp>
#include <boost/charconv/from_chars.hpp>

#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <system_error>

namespace boost {
namespace mysql {
namespace detail {

inline deserialize_errc deserialize_text_field(string_view from, const metadata& meta, field_view& output);

}
}  // namespace mysql
}  // namespace boost

//
// Implementation
//

namespace boost {
namespace mysql {
namespace detail {

// Constants
BOOST_INLINE_CONSTEXPR unsigned max_decimals = 6u;
BOOST_INLINE_CONSTEXPR unsigned time_max_hour = 838;

// Integers
template <class T>
inline deserialize_errc deserialize_text_value_int_impl(string_view from, field_view& to)
{
    // Iterators
    const char* begin = from.data();
    const char* end = begin + from.size();

    // Convert
    T v;
    auto res = charconv::from_chars(from.data(), from.data() + from.size(), v);

    // Check
    if (res.ec != std::errc() || res.ptr != end)
        return deserialize_errc::protocol_value_error;

    // Done
    to = field_view(v);
    return deserialize_errc::ok;
}

inline deserialize_errc deserialize_text_value_int(string_view from, field_view& to, const metadata& meta)
{
    return meta.is_unsigned() ? deserialize_text_value_int_impl<std::uint64_t>(from, to)
                              : deserialize_text_value_int_impl<std::int64_t>(from, to);
}

// Floating points
template <class T>
inline deserialize_errc deserialize_text_value_float(string_view from, field_view& to)
{
    // Iterators
    const char* begin = from.data();
    const char* end = begin + from.size();

    // Convert
    T val;
    auto res = charconv::from_chars(begin, end, val);

    // Check. SQL std forbids nan and inf
    if (res.ec != std::errc() || res.ptr != end || std::isnan(val) || std::isinf(val))
        return deserialize_errc::protocol_value_error;

    // Done
    to = field_view(val);
    return deserialize_errc::ok;
}

// Strings
inline deserialize_errc deserialize_text_value_string(string_view from, field_view& to)
{
    to = field_view(from);
    return deserialize_errc::ok;
}

inline deserialize_errc deserialize_text_value_blob(string_view from, field_view& to) noexcept
{
    to = field_view(to_span(from));
    return deserialize_errc::ok;
}

// Date/time types
template <class IntType>
inline deserialize_errc deserialize_fixed_width_number(
    const char*& it,
    const char* end,
    IntType& to,
    std::size_t size
)
{
    auto res = charconv::from_chars(it, end, to);
    if (res.ec != std::errc() || res.ptr != it + size)
        return deserialize_errc::protocol_value_error;
    it = res.ptr;
    return deserialize_errc::ok;
}

inline deserialize_errc check_separator(const char*& it, const char* end, char sep)
{
    if (it == end || *it++ != sep)
        return deserialize_errc::protocol_value_error;
    return deserialize_errc::ok;
}

inline deserialize_errc deserialize_text_ymd(const char*& it, const char* end, date& to)
{
    // Year
    std::uint16_t year = 0;
    auto err = deserialize_fixed_width_number(it, end, year, 4);
    if (err != deserialize_errc::ok)
        return err;

    // Separator
    err = check_separator(it, end, '-');
    if (err != deserialize_errc::ok)
        return err;

    // Month
    std::uint8_t month = 0;
    err = deserialize_fixed_width_number(it, end, month, 2);
    if (err != deserialize_errc::ok)
        return err;

    // Separator
    err = check_separator(it, end, '-');
    if (err != deserialize_errc::ok)
        return err;

    // Day
    std::uint8_t day = 0;
    err = deserialize_fixed_width_number(it, end, day, 2);
    if (err != deserialize_errc::ok)
        return err;

    // Range check for individual components. MySQL doesn't allow invidiual components
    // to be out of range, although they may be zero or representing an invalid date
    if (year > max_year || month > max_month || day > max_day)
        return deserialize_errc::protocol_value_error;

    to = date(year, month, day);
    return deserialize_errc::ok;
}

inline deserialize_errc deserialize_microsecond(
    const char*& it,
    const char* end,
    std::uint32_t& output,
    unsigned decimals
)
{
    // Sanitize decimals
    decimals = (std::min)(decimals, max_decimals);

    if (decimals)
    {
        // Microsecond separator
        auto err = check_separator(it, end, '.');
        if (err != deserialize_errc::ok)
            return err;

        // Microseconds. Depending on decimals, this has a variable width.
        // For instance, with decimals = 2, a value could be '.92', meaning 920000 microseconds.
        // Max decimals is 6 (guaranteed by sanitize_decimals).
        // Right pad the input string with zeros and convert

        // Size check
        if (it + decimals > end)
            return deserialize_errc::protocol_value_error;

        // Right pad with zeros
        char micros_buff[6] = {'0', '0', '0', '0', '0', '0'};
        std::memcpy(micros_buff, it, decimals);

        // Parse
        const char* buff_begin = micros_buff;
        const char* buff_end = micros_buff + sizeof(micros_buff);
        err = deserialize_fixed_width_number(buff_begin, buff_end, output, 6);
        if (err != deserialize_errc::ok)
            return err;

        // Update iterator
        it += decimals;
    }

    return deserialize_errc::ok;
}

inline deserialize_errc deserialize_text_value_date(string_view from, field_view& to)
{
    // Iterators
    const char* it = from.data();
    const char* end = it + from.size();

    // Deserialize
    date d;
    auto err = deserialize_text_ymd(it, end, d);
    if (err != deserialize_errc::ok)
        return err;

    // Size check
    if (it != end)
        return deserialize_errc::protocol_value_error;

    // Done
    to = field_view(d);
    return deserialize_errc::ok;
}

inline deserialize_errc deserialize_text_value_datetime(
    string_view from,
    field_view& to,
    const metadata& meta
)
{
    // Iterators
    const char* it = from.data();
    const char* end = it + from.size();

    // Deserialize date part
    date d;
    auto err = deserialize_text_ymd(it, end, d);
    if (err != deserialize_errc::ok)
        return err;

    // Separator
    err = check_separator(it, end, ' ');
    if (err != deserialize_errc::ok)
        return err;

    // Hour
    std::uint8_t hour = 0;
    err = deserialize_fixed_width_number(it, end, hour, 2);
    if (err != deserialize_errc::ok)
        return err;

    // Separator
    err = check_separator(it, end, ':');
    if (err != deserialize_errc::ok)
        return err;

    // Minute
    std::uint8_t minute = 0;
    err = deserialize_fixed_width_number(it, end, minute, 2);
    if (err != deserialize_errc::ok)
        return err;

    // Separator
    err = check_separator(it, end, ':');
    if (err != deserialize_errc::ok)
        return err;

    // Second
    std::uint8_t second = 0;
    err = deserialize_fixed_width_number(it, end, second, 2);
    if (err != deserialize_errc::ok)
        return err;

    // Microsecond
    std::uint32_t microsecond = 0;
    err = deserialize_microsecond(it, end, microsecond, meta.decimals());
    if (err != deserialize_errc::ok)
        return err;

    // Size check
    if (it != end)
        return deserialize_errc::protocol_value_error;

    // Validity check. Although MySQL allows invalid and zero datetimes, it doesn't allow
    // individual components to be out of range.
    if (hour > max_hour || minute > max_min || second > max_sec || microsecond > max_micro)
    {
        return deserialize_errc::protocol_value_error;
    }

    to = field_view(datetime(d.year(), d.month(), d.day(), hour, minute, second, microsecond));
    return deserialize_errc::ok;
}

inline deserialize_errc deserialize_text_value_time(string_view from, field_view& to, const metadata& meta)
{
    // Iterators
    const char* it = from.data();
    const char* end = it + from.size();

    // Sign
    if (it == end)
        return deserialize_errc::protocol_value_error;
    bool is_negative = *it == '-';
    if (is_negative)
        ++it;

    // Hours
    std::uint16_t hours = 0;
    auto res = charconv::from_chars(it, end, hours);
    if (res.ec != std::errc())
        return deserialize_errc::protocol_value_error;
    auto hour_num_chars = res.ptr - it;
    if (hour_num_chars != 2 && hour_num_chars != 3)  // may take between 2 and 3 chars
        return deserialize_errc::protocol_value_error;
    it = res.ptr;

    // Separator
    auto err = check_separator(it, end, ':');
    if (err != deserialize_errc::ok)
        return err;

    // Minute
    std::uint8_t minute = 0;
    err = deserialize_fixed_width_number(it, end, minute, 2);
    if (err != deserialize_errc::ok)
        return err;

    // Separator
    err = check_separator(it, end, ':');
    if (err != deserialize_errc::ok)
        return err;

    // Second
    std::uint8_t second = 0;
    err = deserialize_fixed_width_number(it, end, second, 2);
    if (err != deserialize_errc::ok)
        return err;

    // Microsecond
    std::uint32_t microsecond = 0;
    err = deserialize_microsecond(it, end, microsecond, meta.decimals());
    if (err != deserialize_errc::ok)
        return err;

    // Size check
    if (it != end)
        return deserialize_errc::protocol_value_error;

    // Range check
    if (hours > time_max_hour || minute > max_min || second > max_sec || microsecond > max_micro)
        return deserialize_errc::protocol_value_error;

    // Sum it
    auto t = std::chrono::hours(hours) + std::chrono::minutes(minute) + std::chrono::seconds(second) +
             std::chrono::microseconds(microsecond);
    if (is_negative)
    {
        t = -t;
    }

    // Done
    to = field_view(t);
    return deserialize_errc::ok;
}

}  // namespace detail
}  // namespace mysql
}  // namespace boost

boost::mysql::detail::deserialize_errc boost::mysql::detail::deserialize_text_field(
    string_view from,
    const metadata& meta,
    field_view& output
)
{
    switch (meta.type())
    {
    case column_type::tinyint:
    case column_type::smallint:
    case column_type::mediumint:
    case column_type::int_:
    case column_type::bigint:
    case column_type::year: return deserialize_text_value_int(from, output, meta);
    case column_type::bit: return deserialize_bit(from, output);
    case column_type::float_: return deserialize_text_value_float<float>(from, output);
    case column_type::double_: return deserialize_text_value_float<double>(from, output);
    case column_type::timestamp:
    case column_type::datetime: return deserialize_text_value_datetime(from, output, meta);
    case column_type::date: return deserialize_text_value_date(from, output);
    case column_type::time: return deserialize_text_value_time(from, output, meta);
    // True string types
    case column_type::char_:
    case column_type::varchar:
    case column_type::text:
    case column_type::enum_:
    case column_type::set:
    case column_type::decimal:
    case column_type::json: return deserialize_text_value_string(from, output);
    // Blobs and anything else
    case column_type::binary:
    case column_type::varbinary:
    case column_type::blob:
    case column_type::geometry:
    default: return deserialize_text_value_blob(from, output);
    }
}

#endif
