//
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2020 Krystian Stasiowski (sdkrystian@gmail.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)
//
// Official repository: https://github.com/boostorg/json
//

#ifndef BOOST_JSON_DETAIL_STRING_IMPL_HPP
#define BOOST_JSON_DETAIL_STRING_IMPL_HPP

#include <boost/json/detail/config.hpp>
#include <boost/json/kind.hpp>
#include <boost/json/storage_ptr.hpp>
#include <boost/json/detail/value.hpp>
#include <algorithm>
#include <iterator>

namespace boost {
namespace json {

class value;
class string;

namespace detail {

class string_impl
{
    struct table
    {
        std::uint32_t size;
        std::uint32_t capacity;
    };

#if BOOST_JSON_ARCH == 64
    static constexpr std::size_t sbo_chars_ = 14;
#elif BOOST_JSON_ARCH == 32
    static constexpr std::size_t sbo_chars_ = 10;
#else
# error Unknown architecture
#endif

    static
    constexpr
    kind
    short_string_ =
        static_cast<kind>(
            ((unsigned char)
            kind::string) | 0x80);

    static
    constexpr
    kind
    key_string_ =
        static_cast<kind>(
            ((unsigned char)
            kind::string) | 0x40);

    struct sbo
    {
        kind k; // must come first
        char buf[sbo_chars_ + 1];
    };

    struct pointer
    {
        kind k; // must come first
        table* t;
    };

    struct key
    {
        kind k; // must come first
        std::uint32_t n;
        char* s;
    };

    union
    {
        sbo s_;
        pointer p_;
        key k_;
    };

#if BOOST_JSON_ARCH == 64
    BOOST_STATIC_ASSERT(sizeof(sbo) <= 16);
    BOOST_STATIC_ASSERT(sizeof(pointer) <= 16);
    BOOST_STATIC_ASSERT(sizeof(key) <= 16);
#elif BOOST_JSON_ARCH == 32
    BOOST_STATIC_ASSERT(sizeof(sbo) <= 24);
    BOOST_STATIC_ASSERT(sizeof(pointer) <= 24);
    BOOST_STATIC_ASSERT(sizeof(key) <= 24);
#endif

public:
    static
    constexpr
    std::size_t
    max_size() noexcept
    {
        // max_size depends on the address model
        using min = std::integral_constant<std::size_t,
            std::size_t(-1) - sizeof(table)>;
        return min::value < BOOST_JSON_MAX_STRING_SIZE ?
            min::value : BOOST_JSON_MAX_STRING_SIZE;
    }

    BOOST_JSON_DECL
    string_impl() noexcept;

    BOOST_JSON_DECL
    string_impl(
        std::size_t new_size,
        storage_ptr const& sp);

    BOOST_JSON_DECL
    string_impl(
        key_t,
        string_view s,
        storage_ptr const& sp);

    BOOST_JSON_DECL
    string_impl(
        key_t,
        string_view s1,
        string_view s2,
        storage_ptr const& sp);

    BOOST_JSON_DECL
    string_impl(
        char** dest,
        std::size_t len,
        storage_ptr const& sp);

    template<class InputIt>
    string_impl(
        InputIt first,
        InputIt last,
        storage_ptr const& sp,
        std::random_access_iterator_tag)
        : string_impl(last - first, sp)
    {
        char* out = data();
#if defined(_MSC_VER) && _MSC_VER <= 1900
        while( first != last )
            *out++ = *first++;
#else
        std::copy(first, last, out);
#endif
    }

    template<class InputIt>
    string_impl(
        InputIt first,
        InputIt last,
        storage_ptr const& sp,
        std::input_iterator_tag)
        : string_impl(0, sp)
    {
        struct undo
        {
            string_impl* s;
            storage_ptr const& sp;

            ~undo()
            {
                if(s)
                    s->destroy(sp);
            }
        };

        undo u{this, sp};
        auto dest = data();
        while(first != last)
        {
            if(size() < capacity())
                size(size() + 1);
            else
                dest = append(1, sp);
            *dest++ = *first++;
        }
        term(size());
        u.s = nullptr;
    }

    std::size_t
    size() const noexcept
    {
        return s_.k == kind::string ?
            p_.t->size :
            sbo_chars_ -
                s_.buf[sbo_chars_];
    }

    std::size_t
    capacity() const noexcept
    {
        return s_.k == kind::string ?
            p_.t->capacity :
            sbo_chars_;
    }

    void
    size(std::size_t n)
    {
        if(s_.k == kind::string)
            p_.t->size = static_cast<
                std::uint32_t>(n);
        else
            s_.buf[sbo_chars_] =
                static_cast<char>(
                    sbo_chars_ - n);
    }

    BOOST_JSON_DECL
    static
    std::uint32_t
    growth(
        std::size_t new_size,
        std::size_t capacity);

    char const*
    release_key(
        std::size_t& n) noexcept
    {
        BOOST_ASSERT(
            k_.k == key_string_);
        n = k_.n;
        auto const s = k_.s;
        // prevent deallocate
        k_.k = short_string_;
        return s;
    }

    void
    destroy(
        storage_ptr const& sp) noexcept
    {
        if(s_.k == kind::string)
        {
            sp->deallocate(p_.t,
                sizeof(table) +
                    p_.t->capacity + 1,
                alignof(table));
        }
        else if(s_.k != key_string_)
        {
            // do nothing
        }
        else
        {
            BOOST_ASSERT(
                s_.k == key_string_);
            // VFALCO unfortunately the key string
            // kind increases the cost of the destructor.
            // This function should be skipped when using
            // monotonic_resource.
            sp->deallocate(k_.s, k_.n + 1);
        }
    }

    BOOST_JSON_DECL
    char*
    assign(
        std::size_t new_size,
        storage_ptr const& sp);

    BOOST_JSON_DECL
    char*
    append(
        std::size_t n,
        storage_ptr const& sp);

    BOOST_JSON_DECL
    void
    insert(
        std::size_t pos,
        const char* s,
        std::size_t n,
        storage_ptr const& sp);

    BOOST_JSON_DECL
    char*
    insert_unchecked(
        std::size_t pos,
        std::size_t n,
        storage_ptr const& sp);

    BOOST_JSON_DECL
    void
    replace(
        std::size_t pos,
        std::size_t n1,
        const char* s,
        std::size_t n2,
        storage_ptr const& sp);

    BOOST_JSON_DECL
    char*
    replace_unchecked(
        std::size_t pos,
        std::size_t n1,
        std::size_t n2,
        storage_ptr const& sp);

    BOOST_JSON_DECL
    void
    shrink_to_fit(
        storage_ptr const& sp) noexcept;

    void
    term(std::size_t n) noexcept
    {
        if(s_.k == short_string_)
        {
            s_.buf[sbo_chars_] =
                static_cast<char>(
                    sbo_chars_ - n);
            s_.buf[n] = 0;
        }
        else
        {
            p_.t->size = static_cast<
                std::uint32_t>(n);
            data()[n] = 0;
        }
    }

    char*
    data() noexcept
    {
        if(s_.k == short_string_)
            return s_.buf;
        return reinterpret_cast<
            char*>(p_.t + 1);
    }

    char const*
    data() const noexcept
    {
        if(s_.k == short_string_)
            return s_.buf;
        return reinterpret_cast<
            char const*>(p_.t + 1);
    }

    char*
    end() noexcept
    {
        return data() + size();
    }

    char const*
    end() const noexcept
    {
        return data() + size();
    }
};

template<class T>
string_view
to_string_view(T const& t) noexcept
{
    return string_view(t);
}

template<class T, class U>
using string_and_stringlike = std::integral_constant<bool,
    std::is_same<T, string>::value &&
    std::is_convertible<U const&, string_view>::value>;

template<class T, class U>
using string_comp_op_requirement
    = typename std::enable_if<
        string_and_stringlike<T, U>::value ||
        string_and_stringlike<U, T>::value,
        bool>::type;

} // detail
} // namespace json
} // namespace boost

#endif
