//
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@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_IMPL_ARRAY_HPP
#define BOOST_JSON_IMPL_ARRAY_HPP

#include <boost/json/value.hpp>
#include <boost/json/detail/except.hpp>
#include <algorithm>
#include <stdexcept>
#include <type_traits>

BOOST_JSON_NS_BEGIN

//----------------------------------------------------------

struct alignas(value)
    array::table
{
    std::uint32_t size = 0;
    std::uint32_t capacity = 0;

    constexpr table();

    value&
    operator[](std::size_t pos) noexcept
    {
        return (reinterpret_cast<
            value*>(this + 1))[pos];
    }

    BOOST_JSON_DECL
    static
    table*
    allocate(
        std::size_t capacity,
        storage_ptr const& sp);

    BOOST_JSON_DECL
    static
    void
    deallocate(
        table* p,
        storage_ptr const& sp);
};

//----------------------------------------------------------

class array::revert_construct
{
    array* arr_;

public:
    explicit
    revert_construct(
        array& arr) noexcept
        : arr_(&arr)
    {
    }

    ~revert_construct()
    {
        if(! arr_)
            return;
        arr_->destroy();
    }

    void
    commit() noexcept
    {
        arr_ = nullptr;
    }
};

//----------------------------------------------------------

class array::revert_insert
{
    array* arr_;
    std::size_t const i_;
    std::size_t const n_;

public:
    value* p;

    BOOST_JSON_DECL
    revert_insert(
        const_iterator pos,
        std::size_t n,
        array& arr);

    BOOST_JSON_DECL
    ~revert_insert();

    value*
    commit() noexcept
    {
        auto it =
            arr_->data() + i_;
        arr_ = nullptr;
        return it;
    }
};

//----------------------------------------------------------

void
array::
relocate(
    value* dest,
    value* src,
    std::size_t n) noexcept
{
    if(n == 0)
        return;
    std::memmove(
        static_cast<void*>(dest),
        static_cast<void const*>(src),
        n * sizeof(value));
}

//----------------------------------------------------------
//
// Construction
//
//----------------------------------------------------------

template<class InputIt, class>
array::
array(
    InputIt first, InputIt last,
    storage_ptr sp)
    : array(
        first, last,
        std::move(sp),
        iter_cat<InputIt>{})
{
    BOOST_STATIC_ASSERT(
        std::is_constructible<value,
            decltype(*first)>::value);
}

//----------------------------------------------------------
//
// Modifiers
//
//----------------------------------------------------------

template<class InputIt, class>
auto
array::
insert(
    const_iterator pos,
    InputIt first, InputIt last) ->
        iterator
{
    BOOST_STATIC_ASSERT(
        std::is_constructible<value,
            decltype(*first)>::value);
    return insert(pos, first, last,
        iter_cat<InputIt>{});
}

template<class Arg>
auto
array::
emplace(
    const_iterator pos,
    Arg&& arg) ->
        iterator
{
    BOOST_ASSERT(
        pos >= begin() &&
        pos <= end());
    value jv(
        std::forward<Arg>(arg),
        storage());
    return insert(pos, pilfer(jv));
}

template<class Arg>
value&
array::
emplace_back(Arg&& arg)
{
    value jv(
        std::forward<Arg>(arg),
        storage());
    return push_back(pilfer(jv));
}

//----------------------------------------------------------
//
// Element access
//
//----------------------------------------------------------

value&
array::
at(std::size_t pos)
{
    if(pos >= t_->size)
        detail::throw_out_of_range(
            BOOST_CURRENT_LOCATION);
    return (*t_)[pos];
}

value const&
array::
at(std::size_t pos) const
{
    if(pos >= t_->size)
        detail::throw_out_of_range(
            BOOST_CURRENT_LOCATION);
    return (*t_)[pos];
}

value&
array::
operator[](std::size_t pos) noexcept
{
    BOOST_ASSERT(pos < t_->size);
    return (*t_)[pos];
}

value const&
array::
operator[](std::size_t pos) const noexcept
{
    BOOST_ASSERT(pos < t_->size);
    return (*t_)[pos];
}

value&
array::
front() noexcept
{
    BOOST_ASSERT(t_->size > 0);
    return (*t_)[0];
}

value const&
array::
front() const noexcept
{
    BOOST_ASSERT(t_->size > 0);
    return (*t_)[0];
}

value&
array::
back() noexcept
{
    BOOST_ASSERT(
        t_->size > 0);
    return (*t_)[t_->size - 1];
}

value const&
array::
back() const noexcept
{
    BOOST_ASSERT(
        t_->size > 0);
    return (*t_)[t_->size - 1];
}

value*
array::
data() noexcept
{
    return &(*t_)[0];
}

value const*
array::
data() const noexcept
{
    return &(*t_)[0];
}

value const*
array::
if_contains(
    std::size_t pos) const noexcept
{
    if( pos < t_->size )
        return &(*t_)[pos];
    return nullptr;
}

value*
array::
if_contains(
    std::size_t pos) noexcept
{
    if( pos < t_->size )
        return &(*t_)[pos];
    return nullptr;
}

//----------------------------------------------------------
//
// Iterators
//
//----------------------------------------------------------

auto
array::
begin() noexcept ->
    iterator
{
    return &(*t_)[0];
}

auto
array::
begin() const noexcept ->
    const_iterator
{
    return &(*t_)[0];
}

auto
array::
cbegin() const noexcept ->
    const_iterator
{
    return &(*t_)[0];
}

auto
array::
end() noexcept ->
    iterator
{
    return &(*t_)[t_->size];
}

auto
array::
end() const noexcept ->
    const_iterator
{
    return &(*t_)[t_->size];
}

auto
array::
cend() const noexcept ->
    const_iterator
{
    return &(*t_)[t_->size];
}

auto
array::
rbegin() noexcept ->
    reverse_iterator
{
    return reverse_iterator(end());
}

auto
array::
rbegin() const noexcept ->
    const_reverse_iterator
{
    return const_reverse_iterator(end());
}

auto
array::
crbegin() const noexcept ->
    const_reverse_iterator
{
    return const_reverse_iterator(end());
}

auto
array::
rend() noexcept ->
    reverse_iterator
{
    return reverse_iterator(begin());
}

auto
array::
rend() const noexcept ->
    const_reverse_iterator
{
    return const_reverse_iterator(begin());
}

auto
array::
crend() const noexcept ->
    const_reverse_iterator
{
    return const_reverse_iterator(begin());
}

//----------------------------------------------------------
//
// Capacity
//
//----------------------------------------------------------

std::size_t
array::
size() const noexcept
{
    return t_->size;
}

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

std::size_t
array::
capacity() const noexcept
{
    return t_->capacity;
}

bool
array::
empty() const noexcept
{
    return t_->size == 0;
}

void
array::
reserve(
    std::size_t new_capacity)
{
    // never shrink
    if(new_capacity <= t_->capacity)
        return;
    reserve_impl(new_capacity);
}

//----------------------------------------------------------
//
// private
//
//----------------------------------------------------------

template<class InputIt>
array::
array(
    InputIt first, InputIt last,
    storage_ptr sp,
    std::input_iterator_tag)
    : sp_(std::move(sp))
    , t_(&empty_)
{
    revert_construct r(*this);
    while(first != last)
    {
        reserve(size() + 1);
        ::new(end()) value(
            *first++, sp_);
        ++t_->size;
    }
    r.commit();
}

template<class InputIt>
array::
array(
    InputIt first, InputIt last,
    storage_ptr sp,
    std::forward_iterator_tag)
    : sp_(std::move(sp))
{
    std::size_t n =
        std::distance(first, last);
    t_ = table::allocate(n, sp_);
    t_->size = 0;
    revert_construct r(*this);
    while(n--)
    {
        ::new(end()) value(
            *first++, sp_);
        ++t_->size;
    }
    r.commit();
}

template<class InputIt>
auto
array::
insert(
    const_iterator pos,
    InputIt first, InputIt last,
    std::input_iterator_tag) ->
        iterator
{
    BOOST_ASSERT(
        pos >= begin() && pos <= end());
    if(first == last)
        return data() + (pos - data());
    array temp(first, last, sp_);
    revert_insert r(
        pos, temp.size(), *this);
    relocate(
        r.p,
        temp.data(),
        temp.size());
    temp.t_->size = 0;
    return r.commit();
}

template<class InputIt>
auto
array::
insert(
    const_iterator pos,
    InputIt first, InputIt last,
    std::forward_iterator_tag) ->
        iterator
{
    std::size_t n =
        std::distance(first, last);
    revert_insert r(pos, n, *this);
    while(n--)
    {
        ::new(r.p) value(*first++);
        ++r.p;
    }
    return r.commit();
}

BOOST_JSON_NS_END

#endif
