//
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco 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)
//
// Official repository: https://github.com/boostorg/url
//

#ifndef BOOST_URL_GRAMMAR_IMPL_RANGE_HPP
#define BOOST_URL_GRAMMAR_IMPL_RANGE_HPP

#include <boost/url/detail/except.hpp>
#include <boost/url/detail/empty_value.hpp>
#include <boost/url/grammar/error.hpp>
#include <boost/url/grammar/recycled.hpp>
#include <boost/assert.hpp>
#include <boost/static_assert.hpp>
#include <exception>
#include <iterator>
#include <new>

#include <stddef.h> // ::max_align_t

namespace boost {
namespace urls {
namespace grammar {

// VFALCO This could be reused for
// other things that need to type-erase

//------------------------------------------------
//
// any_rule
//
//------------------------------------------------

// base class for the type-erased rule pair
template<class T>
struct range<T>::
    any_rule
{
    virtual
    ~any_rule() = default;

    virtual
    void
    move(void* dest) noexcept
    {
        ::new(dest) any_rule(
            std::move(*this));
    }

    virtual
    void
    copy(void* dest) const noexcept
    {
        ::new(dest) any_rule(*this);
    }

    virtual
    result<T>
    first(
        char const*&,
        char const*) const noexcept
    {
        return error_code{};
    }

    virtual
    result<T> 
    next(
        char const*&,
        char const*) const noexcept
    {
        return error_code{};
    }
};

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

// small
template<class T>
template<class R, bool Small>
struct range<T>::impl1
    : any_rule
    , private urls::detail::empty_value<R>
{
    explicit
    impl1(R const& next) noexcept
        : urls::detail::empty_value<R>(
            urls::detail::empty_init,
            next)
    {
    }

private:
    impl1(impl1&&) noexcept = default;
    impl1(impl1 const&) noexcept = default;

    void
    move(void* dest
        ) noexcept override
    {
        ::new(dest) impl1(
            std::move(*this));
    }

    void
    copy(void* dest
        ) const noexcept override
    {
        ::new(dest) impl1(*this);
    }

    result<T>
    first(
        char const*& it,
        char const* end)
            const noexcept override
    {
        return grammar::parse(
            it, end, this->get());
    }

    result<T>
    next(
        char const*& it,
        char const* end)
            const noexcept override
    {
        return grammar::parse(
            it, end, this->get());
    }
};

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

// big
template<class T>
template<class R>
struct range<T>::impl1<R, false>
    : any_rule
{
    explicit
    impl1(R const& next) noexcept
    {
        ::new(p_->addr()) impl{next};
    }

private:
    struct impl
    {
        R r;
    };

    recycled_ptr<
        aligned_storage<impl>> p_;

    impl1(impl1&&) noexcept = default;
    impl1(impl1 const&) noexcept = default;

    impl const&
    get() const noexcept
    {
        return *reinterpret_cast<
            impl const*>(p_->addr());
    }

    ~impl1()
    {
        if(p_)
            get().~impl();
    }

    void
    move(void* dest
        ) noexcept override
    {
        ::new(dest) impl1(
            std::move(*this));
    }

    void
    copy(void* dest
        ) const noexcept override
    {
        ::new(dest) impl1(*this);
    }

    result<T>
    first(
        char const*& it,
        char const* end)
            const noexcept override
    {
        return grammar::parse(
            it, end, this->get().r);
    }

    result<T>
    next(
        char const*& it,
        char const* end)
            const noexcept override
    {
        return grammar::parse(
            it, end, this->get().r);
    }
};

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

// small
template<class T>
template<
    class R0, class R1, bool Small>
struct range<T>::impl2
    : any_rule
    , private urls::detail::empty_value<R0, 0>
    , private urls::detail::empty_value<R1, 1>
{
    impl2(
        R0 const& first,
        R1 const& next) noexcept
        : urls::detail::empty_value<R0,0>(
            urls::detail::empty_init, first)
        , urls::detail::empty_value<R1,1>(
            urls::detail::empty_init, next)
    {
    }

private:
    impl2(impl2&&) noexcept = default;
    impl2(impl2 const&) noexcept = default;

    void
    move(void* dest
        ) noexcept override
    {
        ::new(dest) impl2(
            std::move(*this));
    }

    void
    copy(void* dest
        ) const noexcept override
    {
        ::new(dest) impl2(*this);
    }

    result<T>
    first(
        char const*& it,
        char const* end)
            const noexcept override
    {
        return grammar::parse(it, end,
            urls::detail::empty_value<
                R0,0>::get());
    }

    result<T>
    next(
        char const*& it,
        char const* end)
            const noexcept override
    {
        return grammar::parse(it, end,
            urls::detail::empty_value<
                R1,1>::get());
    }
};

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

// big
template<class T>
template<
    class R0, class R1>
struct range<T>::impl2<R0, R1, false>
    : any_rule
{
    impl2(
        R0 const& first,
        R1 const& next) noexcept
    {
        ::new(p_->addr()) impl{
            first, next};
    }

private:
    struct impl
    {
        R0 first;
        R1 next;
    };

    recycled_ptr<
        aligned_storage<impl>> p_;

    impl2(impl2&&) noexcept = default;
    impl2(impl2 const&) noexcept = default;

    impl const&
    get() const noexcept
    {
        return *reinterpret_cast<
            impl const*>(p_->addr());
    }

    ~impl2()
    {
        if(p_)
            get().~impl();
    }

    void
    move(void* dest
        ) noexcept override
    {
        ::new(dest) impl2(
            std::move(*this));
    }

    void
    copy(void* dest
        ) const noexcept override
    {
        ::new(dest) impl2(*this);
    }

    result<T>
    first(
        char const*& it,
        char const* end)
            const noexcept override
    {
        return grammar::parse(
            it, end, get().first);
    }

    result<T>
    next(
        char const*& it,
        char const* end)
            const noexcept override
    {
        return grammar::parse(
            it, end, get().next);
    }
};

//------------------------------------------------
//
// iterator
//
//------------------------------------------------

template<class T>
class range<T>::
    iterator
{
public:
    using value_type = T;
    using reference = T const&;
    using pointer = void const*;
    using difference_type =
        std::ptrdiff_t;
    using iterator_category =
        std::forward_iterator_tag;

    iterator() = default;
    iterator(
        iterator const&) noexcept = default;
    iterator& operator=(
        iterator const&) noexcept = default;

    reference
    operator*() const noexcept
    {
        return *rv_;
    }

    bool
    operator==(
        iterator other) const noexcept
    {
        // can't compare iterators
        // from different containers!
        BOOST_ASSERT(r_ == other.r_);

        return p_ == other.p_;
    }

    bool
    operator!=(
        iterator other) const noexcept
    {
        return !(*this == other);
    }

    iterator&
    operator++() noexcept
    {
        BOOST_ASSERT(
            p_ != nullptr);
        auto const end =
            r_->s_.data() +
            r_->s_.size();
        rv_ = r_->get().next(p_, end);
        if( !rv_ )
            p_ = nullptr;
        return *this;
    }

    iterator
    operator++(int) noexcept
    {
        auto tmp = *this;
        ++*this;
        return tmp;
    }

private:
    friend class range<T>;

    range<T> const* r_ = nullptr;
    char const* p_ = nullptr;
    result<T> rv_;

    iterator(
        range<T> const& r) noexcept
        : r_(&r)
        , p_(r.s_.data())
    {
        auto const end =
            r_->s_.data() +
            r_->s_.size();
        rv_ = r_->get().first(p_, end);
        if( !rv_ )
            p_ = nullptr;
    }

    constexpr
    iterator(
        range<T> const& r,
        int) noexcept
        : r_(&r)
        , p_(nullptr)
    {
    }
};

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

template<class T>
template<class R>
range<T>::
range(
    string_view s,
    std::size_t n,
    R const& next)
    : s_(s)
    , n_(n)
{
    BOOST_STATIC_ASSERT(
        sizeof(impl1<R, false>) <=
            BufferSize);

    ::new(&get()) impl1<R,
        sizeof(impl1<R, true>) <=
            BufferSize>(next);
}

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

template<class T>
template<
    class R0, class R1>
range<T>::
range(
    string_view s,
    std::size_t n,
    R0 const& first,
    R1 const& next)
    : s_(s)
    , n_(n)
{
    BOOST_STATIC_ASSERT(
        sizeof(impl2<R0, R1, false>) <=
            BufferSize);

    ::new(&get()) impl2<R0, R1,
        sizeof(impl2<R0, R1, true>
            ) <= BufferSize>(
                first, next);
}

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

template<class T>
range<T>::
~range()
{
    get().~any_rule();
}

template<class T>
range<T>::
range() noexcept
{
    ::new(&get()) any_rule{};
    char const* it = nullptr;
    get().first(it, nullptr);
    get().next(it, nullptr);
}

template<class T>
range<T>::
range(
    range&& other) noexcept
    : s_(other.s_)
    , n_(other.n_)
{
    other.s_ = {};
    other.n_ = {};
    other.get().move(&get());
    other.get().~any_rule();
    ::new(&other.get()) any_rule{};
}

template<class T>
range<T>::
range(
    range const& other) noexcept
    : s_(other.s_)
    , n_(other.n_)
{
    other.get().copy(&get());
}

template<class T>
auto
range<T>::
operator=(
    range&& other) noexcept ->
        range&
{
    s_ = other.s_;
    n_ = other.n_;
    other.s_ = {};
    other.n_ = 0;
    // VFALCO we rely on nothrow move
    // construction here, but if necessary we
    // could move to a local buffer first.
    get().~any_rule();
    other.get().move(&get());
    other.get().~any_rule();
    ::new(&other.get()) any_rule{};
    return *this;
}

template<class T>
auto
range<T>::
operator=(
    range const& other) noexcept ->
        range&
{
    s_ = other.s_;
    n_ = other.n_;
    // VFALCO we rely on nothrow copy
    // construction here, but if necessary we
    // could construct to a local buffer first.
    get().~any_rule();
    other.get().copy(&get());
    return *this;
}

template<class T>
auto
range<T>::
begin() const noexcept ->
    iterator
{
    return { *this };
}

template<class T>
auto
range<T>::
end() const noexcept ->
    iterator
{
    return { *this, 0 };
}

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

template<class R>
auto
range_rule_t<R>::
parse(
    char const*& it,
    char const* end) const ->
        result<value_type>
{
    using T = typename R::value_type;

    std::size_t n = 0;
    auto const it0 = it;
    auto it1 = it;
    auto rv = (grammar::parse)(
        it, end, next_);
    if( !rv )
    {
        if(rv.error() != error::end_of_range)
        {
            // rewind unless error::end_of_range
            it = it1;
        }
        if(n < N_)
        {
            // too few
            BOOST_URL_RETURN_EC(
                error::mismatch);
        }
        // good
        return range<T>(
            string_view(it0, it - it0),
                n, next_);
    }
    for(;;)
    {
        ++n;
        it1 = it;
        rv = (grammar::parse)(
            it, end, next_);
        if( !rv )
        {
            if(rv.error() != error::end_of_range)
            {
                // rewind unless error::end_of_range
                it = it1;
            }
            break;
        }
        if(n >= M_)
        {
            // too many
            BOOST_URL_RETURN_EC(
                error::mismatch);
        }
    }
    if(n < N_)
    {
        // too few
        BOOST_URL_RETURN_EC(
            error::mismatch);
    }
    // good
    return range<T>(
        string_view(it0, it - it0),
            n, next_);
}

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

template<class R0, class R1>
auto
range_rule_t<R0, R1>::
parse(
    char const*& it,
    char const* end) const ->
        result<range<typename
            R0::value_type>>
{
    using T = typename R0::value_type;

    std::size_t n = 0;
    auto const it0 = it;
    auto it1 = it;
    auto rv = (grammar::parse)(
        it, end, first_);
    if( !rv )
    {
        if(rv.error() != error::end_of_range)
        {
            // rewind unless error::end_of_range
            it = it1;
        }
        if(n < N_)
        {
            // too few
            BOOST_URL_RETURN_EC(
                error::mismatch);
        }
        // good
        return range<T>(
            string_view(it0, it - it0),
                n, first_, next_);
    }
    for(;;)
    {
        ++n;
        it1 = it;
        rv = (grammar::parse)(
            it, end, next_);
        if( !rv )
        {
            if(rv.error() != error::end_of_range)
            {
                // rewind unless error::end_of_range
                it = it1;
            }
            break;
        }
        if(n >= M_)
        {
            // too many
            BOOST_URL_RETURN_EC(
                error::mismatch);
        }
    }
    if(n < N_)
    {
        // too few
        BOOST_URL_RETURN_EC(
            error::mismatch);
    }
    // good
    return range<T>(
        string_view(it0, it - it0),
            n, first_, next_);
}

} // grammar
} // urls
} // boost

#endif
