//
// 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_STRING_HPP
#define BOOST_JSON_IMPL_STRING_HPP

#include <utility>

namespace boost {
namespace json {

string::
string(
    detail::key_t const&,
    string_view s,
    storage_ptr sp)
    : sp_(std::move(sp))
    , impl_(detail::key_t{},
        s, sp_)
{
}

string::
string(
    detail::key_t const&,
    string_view s1,
    string_view s2,
    storage_ptr sp)
    : sp_(std::move(sp))
    , impl_(detail::key_t{},
        s1, s2, sp_)
{
}

template<class InputIt, class>
string::
string(
    InputIt first,
    InputIt last,
    storage_ptr sp)
    : sp_(std::move(sp))
    , impl_(first, last, sp_,
        iter_cat<InputIt>{})
{
}

template<class InputIt, class>
string&
string::
assign(
    InputIt first,
    InputIt last)
{
    assign(first, last,
        iter_cat<InputIt>{});
    return *this;
}

template<class InputIt, class>
string&
string::
append(InputIt first, InputIt last)
{
    append(first, last,
        iter_cat<InputIt>{});
    return *this;
}

// KRYSTIAN TODO: this can be done without copies when
// reallocation is not needed, when the iterator is a
// FowardIterator or better, as we can use std::distance
template<class InputIt, class>
auto
string::
insert(
    size_type pos,
    InputIt first,
    InputIt last) ->
        string&
{
    struct cleanup
    {
        detail::string_impl& s;
        storage_ptr const& sp;

        ~cleanup()
        {
            s.destroy(sp);
        }
    };

    // We use the default storage because
    // the allocation is immediately freed.
    storage_ptr dsp;
    detail::string_impl tmp(
        first, last, dsp,
        iter_cat<InputIt>{});
    cleanup c{tmp, dsp};
    std::memcpy(
        impl_.insert_unchecked(pos, tmp.size(), sp_),
        tmp.data(),
        tmp.size());
    return *this;
}

// KRYSTIAN TODO: this can be done without copies when
// reallocation is not needed, when the iterator is a
// FowardIterator or better, as we can use std::distance
template<class InputIt, class>
auto
string::
replace(
    const_iterator first,
    const_iterator last,
    InputIt first2,
    InputIt last2) ->
        string&
{
    struct cleanup
    {
        detail::string_impl& s;
        storage_ptr const& sp;

        ~cleanup()
        {
            s.destroy(sp);
        }
    };

    // We use the default storage because
    // the allocation is immediately freed.
    storage_ptr dsp;
    detail::string_impl tmp(
        first2, last2, dsp,
        iter_cat<InputIt>{});
    cleanup c{tmp, dsp};
    std::memcpy(
        impl_.replace_unchecked(
            first - begin(),
            last - first,
            tmp.size(),
            sp_),
        tmp.data(),
        tmp.size());
    return *this;
}

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

template<class InputIt>
void
string::
assign(
    InputIt first,
    InputIt last,
    std::random_access_iterator_tag)
{
    auto dest = impl_.assign(static_cast<
        size_type>(last - first), sp_);
    while(first != last)
        *dest++ = *first++;
}

template<class InputIt>
void
string::
assign(
    InputIt first,
    InputIt last,
    std::input_iterator_tag)
{
    if(first == last)
    {
        impl_.term(0);
        return;
    }
    detail::string_impl tmp(
        first, last, sp_,
        std::input_iterator_tag{});
    impl_.destroy(sp_);
    impl_ = tmp;
}

template<class InputIt>
void
string::
append(
    InputIt first,
    InputIt last,
    std::random_access_iterator_tag)
{

    auto const n = static_cast<
        size_type>(last - first);
    char* out = impl_.append(n, sp_);
#if defined(_MSC_VER) && _MSC_VER <= 1900
    while( first != last )
        *out++ = *first++;
#else
    std::copy(first, last, out);
#endif
}

template<class InputIt>
void
string::
append(
    InputIt first,
    InputIt last,
    std::input_iterator_tag)
{
    struct cleanup
    {
        detail::string_impl& s;
        storage_ptr const& sp;

        ~cleanup()
        {
            s.destroy(sp);
        }
    };

    // We use the default storage because
    // the allocation is immediately freed.
    storage_ptr dsp;
    detail::string_impl tmp(
        first, last, dsp,
        std::input_iterator_tag{});
    cleanup c{tmp, dsp};
    std::memcpy(
        impl_.append(tmp.size(), sp_),
        tmp.data(), tmp.size());
}

char&
string::at(std::size_t pos, source_location const& loc)
{

    auto const& self = *this;
    return const_cast< char& >( self.at(pos, loc) );
}

} // namespace json
} // namespace boost

#endif
