//
// 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_RANGE_RULE_HPP
#define BOOST_URL_GRAMMAR_RANGE_RULE_HPP

#include <boost/url/detail/config.hpp>
#include <boost/url/error.hpp>
#include <boost/url/string_view.hpp>
#include <boost/url/grammar/parse.hpp>
#include <boost/url/grammar/type_traits.hpp>
#include <boost/static_assert.hpp>
#include <cstddef>
#include <iterator>
#include <type_traits>

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

namespace boost {
namespace urls {
namespace grammar {

/** A forward range of parsed elements

    Objects of this type are forward ranges
    returned when parsing using the
    @ref range_rule.
    Iteration is performed by re-parsing the
    underlying character buffer. Ownership
    of the buffer is not transferred; the
    caller is responsible for ensuring that
    the lifetime of the buffer extends until
    it is no longer referenced by the range.

    @note

    The implementation may use temporary,
    recycled storage for type-erasure. Objects
    of type `range` are intended to be used
    ephemerally. That is, for short durations
    such as within a function scope. If it is
    necessary to store the range for a long
    period of time or with static storage
    duration, it is necessary to copy the
    contents to an object of a different type.

    @tparam T The value type of the range

    @see
        @ref parse,
        @ref range_rule.
*/
template<class T>
class range
{
    // buffer size for type-erased rule
    static constexpr
        std::size_t BufferSize = 128;

    struct small_buffer
    {
        alignas(alignof(::max_align_t))
        unsigned char buf[BufferSize];

        void const* addr() const noexcept
        {
            return buf;
        }

        void* addr() noexcept
        {
            return buf;
        }
    };

    small_buffer sb_;
    string_view s_;
    std::size_t n_ = 0;

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

    struct any_rule;

    template<class R, bool>
    struct impl1;

    template<
        class R0, class R1, bool>
    struct impl2;

    template<
        class R0, class R1>
    friend struct range_rule_t;

    any_rule&
    get() noexcept
    {
        return *reinterpret_cast<
            any_rule*>(sb_.addr());
    }

    any_rule const&
    get() const noexcept
    {
        return *reinterpret_cast<
            any_rule const*>(
                sb_.addr());
    }

    template<class R>
    range(
        string_view s,
        std::size_t n,
        R const& r);

    template<
        class R0, class R1>
    range(
        string_view s,
        std::size_t n,
        R0 const& first,
        R1 const& next);

public:
    /** The type of each element of the range
    */
    using value_type = T;

    /** The type of each element of the range
    */
    using reference = T const&;

    /** The type of each element of the range
    */
    using const_reference = T const&;

    /** Provided for compatibility, unused
    */
    using pointer = void const*;

    /** The type used to represent unsigned integers
    */
    using size_type = std::size_t;

    /** The type used to represent signed integers
    */
    using difference_type = std::ptrdiff_t;

    /** A constant, forward iterator to elements of the range
    */
    class iterator;

    /** A constant, forward iterator to elements of the range
    */
    using const_iterator = iterator;

    /** Destructor
    */
    ~range();

    /** Constructor

        Default-constructed ranges have
        zero elements.

        @par Exception Safety
        Throws nothing.
    */
    range() noexcept;

    /** Constructor

        The new range references the
        same underlying character buffer.
        Ownership is not transferred; the
        caller is responsible for ensuring
        that the lifetime of the buffer
        extends until it is no longer
        referenced. The moved-from object
        becomes as if default-constructed.

        @par Exception Safety
        Throws nothing.
    */
    range(range&&) noexcept;

    /** Constructor

        The copy references the same
        underlying character buffer.
        Ownership is not transferred; the
        caller is responsible for ensuring
        that the lifetime of the buffer
        extends until it is no longer
        referenced.

        @par Exception Safety
        Throws nothing.
    */
    range(range const&) noexcept;

    /** Constructor

        After the move, this references the
        same underlying character buffer. Ownership
        is not transferred; the caller is responsible
        for ensuring that the lifetime of the buffer
        extends until it is no longer referenced.
        The moved-from object becomes as if
        default-constructed.

        @par Exception Safety
        Throws nothing.
    */
    range&
    operator=(range&&) noexcept;

    /** Assignment

        The copy references the same
        underlying character buffer.
        Ownership is not transferred; the
        caller is responsible for ensuring
        that the lifetime of the buffer
        extends until it is no longer
        referenced.

        @par Exception Safety
        Throws nothing.
    */
    range&
    operator=(range const&) noexcept;

    /** Return an iterator to the beginning
    */
    iterator begin() const noexcept;

    /** Return an iterator to the end
    */
    iterator end() const noexcept;

    /** Return true if the range is empty
    */
    bool
    empty() const noexcept
    {
        return n_ == 0;
    }

    /** Return the number of elements in the range
    */
    std::size_t
    size() const noexcept
    {
        return n_;
    }

    /** Return the matching part of the string
    */
    string_view
    string() const noexcept
    {
        return s_;
    }
};

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

#ifndef BOOST_URL_DOCS
template<
    class R0,
    class R1 = void>
struct range_rule_t;
#endif

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

/** Match a repeating number of elements

    Elements are matched using the passed rule.
    <br>
    Normally when the rule returns an error,
    the range ends and the input is rewound to
    one past the last character that matched
    successfully. However, if the rule returns
    the special value @ref error::end_of_range, the
    input is not rewound. This allows for rules
    which consume input without producing
    elements in the range. For example, to
    relax the grammar for a comma-delimited
    list by allowing extra commas in between
    elements.

    @par Value Type
    @code
    using value_type = range< typename Rule::value_type >;
    @endcode

    @par Example
    Rules are used with the function @ref parse.
    @code
    // range    = 1*( ";" token )

    result< range<string_view> > rv = parse( ";alpha;xray;charlie",
        range_rule(
            tuple_rule(
                squelch( delim_rule( ';' ) ),
                token_rule( alpha_chars ) ),
            1 ) );
    @endcode

    @par BNF
    @code
    range        = <N>*<M>next
    @endcode

    @par Specification
    @li <a href="https://datatracker.ietf.org/doc/html/rfc5234#section-3.6"
        >3.6.  Variable Repetition (rfc5234)</a>

    @param next The rule to use for matching
    each element. The range extends until this
    rule returns an error.

    @param N The minimum number of elements for
    the range to be valid. If omitted, this
    defaults to zero.

    @param M The maximum number of elements for
    the range to be valid. If omitted, this
    defaults to unlimited.

    @see
        @ref alpha_chars,
        @ref delim_rule,
        @ref error::end_of_range,
        @ref parse,
        @ref range,
        @ref tuple_rule,
        @ref squelch.
*/
#ifdef BOOST_URL_DOCS
template<class Rule>
constexpr
__implementation_defined__
range_rule(
    Rule next,
    std::size_t N = 0,
    std::size_t M =
        std::size_t(-1)) noexcept;
#else
template<class R>
struct range_rule_t<R>
{
    using value_type =
        range<typename R::value_type>;

    result<value_type>
    parse(
        char const*& it,
        char const* end) const;

private:
    constexpr
    range_rule_t(
        R const& next,
        std::size_t N,
        std::size_t M) noexcept
        : next_(next)
        , N_(N)
        , M_(M)
    {
    }

    template<class R_>
    friend
    constexpr
    range_rule_t<R_>
    range_rule(
        R_ const& next,
        std::size_t N,
        std::size_t M) noexcept;

    R const next_;
    std::size_t N_;
    std::size_t M_;
};

template<class Rule>
constexpr
range_rule_t<Rule>
range_rule(
    Rule const& next,
    std::size_t N = 0,
    std::size_t M =
        std::size_t(-1)) noexcept
{
    // If you get a compile error here it
    // means that your rule does not meet
    // the type requirements. Please check
    // the documentation.
    static_assert(
        is_rule<Rule>::value,
        "Rule requirements not met");

    return range_rule_t<Rule>{
        next, N, M};
}
#endif

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

/** Match a repeating number of elements

    Two rules are used for match. The rule
    `first` is used for matching the first
    element, while the `next` rule is used
    to match every subsequent element.
    <br>
    Normally when the rule returns an error,
    the range ends and the input is rewound to
    one past the last character that matched
    successfully. However, if the rule returns
    the special value @ref error::end_of_range, the
    input is not rewound. This allows for rules
    which consume input without producing
    elements in the range. For example, to
    relax the grammar for a comma-delimited
    list by allowing extra commas in between
    elements.

    @par Value Type
    @code
    using value_type = range< typename Rule::value_type >;
    @endcode

    @par Example
    Rules are used with the function @ref parse.
    @code
    // range    = [ token ] *( "," token )

    result< range< string_view > > rv = parse( "whiskey,tango,foxtrot",
        range_rule(
            token_rule( alpha_chars ),          // first
            tuple_rule(                      // next
                squelch( delim_rule(',') ),
                token_rule( alpha_chars ) ) ) );
    @endcode

    @par BNF
    @code
    range       = <1>*<1>first
                / first <N-1>*<M-1>next
    @endcode

    @par Specification
    @li <a href="https://datatracker.ietf.org/doc/html/rfc5234#section-3.6"
        >3.6.  Variable Repetition (rfc5234)</a>

    @param first The rule to use for matching
    the first element. If this rule returns
    an error, the range is empty.

    @param next The rule to use for matching
    each subsequent element. The range extends
    until this rule returns an error.

    @param N The minimum number of elements for
    the range to be valid. If omitted, this
    defaults to zero.

    @param M The maximum number of elements for
    the range to be valid. If omitted, this
    defaults to unlimited.

    @see
        @ref alpha_chars,
        @ref delim_rule,
        @ref error::end_of_range,
        @ref parse,
        @ref range,
        @ref tuple_rule,
        @ref squelch.
*/
#ifdef BOOST_URL_DOCS
template<
    class Rule1, class Rule2>
constexpr
__implementation_defined__
range_rule(
    Rule1 first,
    Rule2 next,
    std::size_t N = 0,
    std::size_t M =
        std::size_t(-1)) noexcept;
#else
template<class R0, class R1>
struct range_rule_t
{
    using value_type =
        range<typename R0::value_type>;

    result<value_type>
    parse(
        char const*& it,
        char const* end) const;

private:
    constexpr
    range_rule_t(
        R0 const& first,
        R1 const& next,
        std::size_t N,
        std::size_t M) noexcept
        : first_(first)
        , next_(next)
        , N_(N)
        , M_(M)
    {
    }

    template<
        class R0_, class R1_>
    friend
    constexpr
    auto
    range_rule(
        R0_ const& first,
        R1_ const& next,
        std::size_t N,
        std::size_t M) noexcept ->
#if 1
            typename std::enable_if<
                ! std::is_integral<R1_>::value,
                range_rule_t<R0_, R1_>>::type;
#else
        range_rule_t<R0_, R1_>;
#endif

    R0 const first_;
    R1 const next_;
    std::size_t N_;
    std::size_t M_;
};

template<
    class Rule1, class Rule2>
constexpr
auto
range_rule(
    Rule1 const& first,
    Rule2 const& next,
    std::size_t N = 0,
    std::size_t M =
        std::size_t(-1)) noexcept ->
#if 1
    typename std::enable_if<
        ! std::is_integral<Rule2>::value,
        range_rule_t<Rule1, Rule2>>::type
#else
    range_rule_t<Rule1, Rule2>
#endif
{
    // If you get a compile error here it
    // means that your rule does not meet
    // the type requirements. Please check
    // the documentation.
    static_assert(
        is_rule<Rule1>::value,
        "Rule requirements not met");
    static_assert(
        is_rule<Rule2>::value,
        "Rule requirements not met");

    // If you get a compile error here it
    // means that your rules do not have
    // the exact same value_type. Please
    // check the documentation.
    static_assert(
        std::is_same<
            typename Rule1::value_type,
            typename Rule2::value_type>::value,
        "Rule requirements not met");

    return range_rule_t<Rule1, Rule2>{
        first, next, N, M};
}
#endif

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

#include <boost/url/grammar/impl/range_rule.hpp>

#endif
