// -*- C++ -*-
//===------------------------------ span ---------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is dual licensed under the MIT and the University of Illinois Open
// Source Licenses. See LICENSE.TXT for details.
//
//===---------------------------------------------------------------------===//


/*!\file
// \brief Provides std::span from the C++20 standard library.
// \see https://en.cppreference.com/w/cpp/container/span
*/

//!\cond
#pragma once

#if __has_include(<span>)
#include <span>
#endif // __has_include(<span>)

#if !defined(__cpp_lib_span)
#include <array>        // for array
#include <cstddef>      // for ptrdiff_t
#include <iterator>     // for iterators
#include <type_traits>  // for remove_cv, etc

#include <seqan3/std/iterator>       // for iterator concepts
#include <seqan3/std/ranges>         // for range concepts

namespace std
{
inline constexpr size_t dynamic_extent = -1;
template <typename span_tp, size_t span_extent = dynamic_extent> class span;

template <class span_tp>
struct is_span_impl : public false_type {};

template <class span_tp, size_t span_extent>
struct is_span_impl<span<span_tp, span_extent>> : public true_type {};

template <class span_tp>
struct is_span : public is_span_impl<remove_cv_t<span_tp>> {};

template <class span_tp>
struct is_std_array_impl : public false_type {};

template <class span_tp, size_t span_sz>
struct is_std_array_impl<array<span_tp, span_sz>> : public true_type {};

template <class span_tp>
struct is_std_array : public is_std_array_impl<remove_cv_t<span_tp>> {};

} // namespace std

#if RANGE_V3_VERSION >= 1000
namespace ranges
{
template<typename span_tp, std::size_t span_sz>
inline constexpr bool enable_borrowed_range<std::span<span_tp, span_sz>> = true;

// we follow the definition of the ranges library for enable_view:
// https://github.com/ericniebler/range-v3/blob/6103268542c27210dbc3f99f1fa0c1e348b52184/include/range/v3/range/concepts.hpp#L201
template<typename span_tp, std::size_t span_sz>
inline constexpr bool enable_view<std::span<span_tp, span_sz>> = span_sz + 1 < 2;
} // namespace ranges
#endif // RANGE_V3_VERSION >= 1000

namespace std
{
template <typename span_tp, size_t span_extent>
class span {
public:
//  constants and types
    using element_type           = span_tp;
    using value_type             = remove_cv_t<span_tp>;
    using index_type             = size_t;
    using difference_type        = ptrdiff_t;
    using pointer                = span_tp *;
    using const_pointer          = const span_tp *; // not in standard
    using reference              = span_tp &;
    using const_reference        = const span_tp &; // not in standard
    using iterator               = value_type *;
    using const_iterator         = value_type const *;
    using reverse_iterator       = std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;

    static constexpr index_type extent = span_extent;
    static_assert(span_extent >= 0, "Can't have a span with an extent < 0");

// [span.cons], span constructors, copy, assignment, and destructor
    constexpr span() noexcept : data_{nullptr}
    { static_assert(span_extent == 0, "Can't default construct a statically sized span with size > 0"); }

    constexpr span           (const span&) noexcept = default;
    constexpr span& operator=(const span&) noexcept = default;

    template <contiguous_iterator iterator_t>
        requires is_convertible_v<remove_reference_t<iter_reference_t<iterator_t>>(*)[], element_type(*)[]>
    constexpr span(iterator_t f, index_type count) noexcept : data_{addressof(*f)}
        { (void) count; assert(span_extent == count); } // "size mismatch in span's constructor (f, count)"

    template <contiguous_iterator iterator_t, typename sentinel_t>
        requires sized_sentinel_for<sentinel_t, iterator_t> &&
                 is_convertible_v<remove_reference_t<iter_reference_t<iterator_t>>(*)[], element_type(*)[]> &&
                 (!is_convertible_v<sentinel_t, size_t>)
    constexpr span(iterator_t f, sentinel_t l) noexcept : data_{addressof(*f)}
        { (void) l; assert(span_extent == distance(f, l)); } // "size mismatch in span's constructor (first, last)"

    constexpr span(element_type (&arr)[span_extent])          noexcept : data_{arr} {}
    constexpr span(array<value_type, span_extent>& arr) noexcept : data_{arr.data()} {}
    constexpr span(const array<value_type, span_extent>& arr) noexcept : data_{arr.data()} {}

    template <class OtherElementType>
    inline constexpr span(const span<OtherElementType, span_extent>& other,
                       enable_if_t<
                          is_convertible_v<OtherElementType(*)[], element_type (*)[]>,
                          nullptr_t> = nullptr)
        : data_{other.data()} {}

    template <class OtherElementType>
    inline constexpr span(const span<OtherElementType, dynamic_extent>& other,
                       enable_if_t<
                          is_convertible_v<OtherElementType(*)[], element_type (*)[]>,
                          nullptr_t> = nullptr) noexcept
        : data_{other.data()} { assert(span_extent == other.size()); } // "size mismatch in span's constructor (other span)"

//  ~span() noexcept = default;

    template <size_t count>
    inline constexpr span<element_type, count> first() const noexcept
    {
        static_assert(count >= 0, "Count must be >= 0 in span::first()");
        static_assert(count <= span_extent, "Count out of range in span::first()");
        return {data(), count};
    }

    template <size_t count>
    inline constexpr span<element_type, count> last() const noexcept
    {
        static_assert(count >= 0, "Count must be >= 0 in span::last()");
        static_assert(count <= span_extent, "Count out of range in span::last()");
        return {data() + size() - count, count};
    }

    constexpr span<element_type, dynamic_extent> first(index_type count) const noexcept
    {
        assert(count >= 0 && count <= size()); // "Count out of range in span::first(count)"
        return {data(), count};
    }

    constexpr span<element_type, dynamic_extent> last(index_type count) const noexcept
    {
        assert(count >= 0 && count <= size()); // "Count out of range in span::last(count)"
        return {data() + size() - count, count};
    }

    template <size_t offset, size_t count = dynamic_extent>
    inline constexpr auto subspan() const noexcept
        -> span<element_type, count != dynamic_extent ? count : span_extent - offset>
    {
        static_assert(offset >= 0 && offset <= size(), "Offset out of range in span::subspan()");
        return {data() + offset, count == dynamic_extent ? size() - offset : count};
    }

    inline constexpr span<element_type, dynamic_extent>
       subspan(index_type offset, index_type count = dynamic_extent) const noexcept
    {
        assert( offset >= 0 && offset <= size()); // "Offset out of range in span::subspan(offset, count)"
        assert((count  >= 0 && count  <= size()) || count == dynamic_extent); // "Count out of range in span::subspan(offset, count)"
        if (count == dynamic_extent)
            return {data() + offset, size() - offset};
        assert(offset + count <= size()); // "count + offset out of range in span::subspan(offset, count)"
        return {data() + offset, count};
    }

    constexpr index_type size()       const noexcept { return span_extent; }
    constexpr index_type size_bytes() const noexcept { return span_extent * sizeof(element_type); }
    constexpr bool empty()            const noexcept { return span_extent == 0; }

    constexpr reference front() const noexcept
    {
        return *begin();
    }

    constexpr reference back() const noexcept
    {
        return *(end() - 1);
    }

    constexpr reference operator[](index_type idx) const noexcept
    {
        assert(idx >= 0 && idx < size()); // "span<T,N>[] index out of bounds"
        return data_[idx];
    }

    constexpr reference operator()(index_type idx) const noexcept
    {
        assert(idx >= 0 && idx < size()); // "span<T,N>() index out of bounds"
        return data_[idx];
    }

    constexpr pointer data()                         const noexcept { return data_; }

// [span.iter], span iterator support
    constexpr iterator                 begin() const noexcept { return iterator(data()); }
    constexpr iterator                   end() const noexcept { return iterator(data() + size()); }
    constexpr const_iterator          cbegin() const noexcept { return const_iterator(data()); }
    constexpr const_iterator            cend() const noexcept { return const_iterator(data() + size()); }
    constexpr reverse_iterator        rbegin() const noexcept { return reverse_iterator(end()); }
    constexpr reverse_iterator          rend() const noexcept { return reverse_iterator(begin()); }
    constexpr const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(cend()); }
    constexpr const_reverse_iterator   crend() const noexcept { return const_reverse_iterator(cbegin()); }

    constexpr void swap(span &other) noexcept
    {
        pointer p = data_;
        data_ = other.data_;
        other.data_ = p;
    }

    span<const byte, span_extent * sizeof(element_type)> as_bytes() const noexcept
    { return {reinterpret_cast<const byte *>(data()), size_bytes()}; }

    span<byte, span_extent * sizeof(element_type)> as_writeable_bytes() const noexcept
    { return {reinterpret_cast<byte *>(data()), size_bytes()}; }

private:
    pointer data_;

};

template <typename span_tp>
class span<span_tp, dynamic_extent> {
private:

public:
//  constants and types
    using element_type           = span_tp;
    using value_type             = remove_cv_t<span_tp>;
    using index_type             = size_t;
    using difference_type        = ptrdiff_t;
    using pointer                = span_tp *;
    using const_pointer          = const span_tp *; // not in standard
    using reference              = span_tp &;
    using const_reference        = const span_tp &; // not in standard
    using iterator               = value_type *;
    using const_iterator         = value_type const *;
    using reverse_iterator       = std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;

    static constexpr index_type extent = dynamic_extent;

// [span.cons], span constructors, copy, assignment, and destructor
    constexpr span() noexcept : data_{nullptr}, size_{0} {}

    constexpr span           (const span&) noexcept = default;
    constexpr span& operator=(const span&) noexcept = default;

    template <contiguous_iterator iterator_t>
        requires is_convertible_v<remove_reference_t<iter_reference_t<iterator_t>>(*)[], element_type(*)[]>
    constexpr span(iterator_t f, index_type count) noexcept : data_{addressof(*f)}, size_{count}
    {}

    template <contiguous_iterator iterator, typename sentinel_t>
        requires sized_sentinel_for<sentinel_t, iterator> &&
                 is_convertible_v<remove_reference_t<iter_reference_t<iterator>>(*)[], element_type(*)[]> &&
                 (!is_convertible_v<sentinel_t, size_t>)
    constexpr span(iterator f, sentinel_t l) noexcept : data_{addressof(*f)}, size_{static_cast<index_type>(distance(f, l))}
    {}

    template <size_t span_sz>
    inline constexpr span(element_type (&arr)[span_sz]) noexcept : data_{arr}, size_{span_sz} {}

    template <size_t span_sz>
    inline constexpr span(array<value_type, span_sz>& arr) noexcept : data_{arr.data()}, size_{span_sz} {}

    template <size_t span_sz>
    inline constexpr span(const array<value_type, span_sz>& arr) noexcept : data_{arr.data()}, size_{span_sz} {}

    template <typename range_t>
        requires (!same_as<remove_reference_t<remove_cv_t<range_t>>, span>) && // guard for recursive instantiation in constructible_from
                 (!is_span<remove_reference_t<range_t>>::value) &&
                 (!is_std_array<remove_reference_t<range_t>>::value) &&
                 (!is_array_v<remove_reference_t<range_t>>) &&
                 is_convertible_v<remove_reference_t<iter_reference_t<ranges::iterator_t<range_t>>>(*)[],
                                  element_type(*)[]> &&
                 ranges::contiguous_range<range_t> &&
                 ranges::sized_range<range_t> &&
                 (ranges::borrowed_range<range_t> || is_const_v<element_type>)
    constexpr span(range_t && rng) : data_{ranges::data(rng)}, size_{static_cast<index_type>(ranges::size(rng))}
    {}

    template <class OtherElementType, size_t _OtherExtent>
    inline constexpr span(const span<OtherElementType, _OtherExtent>& other,
                       enable_if_t<
                          is_convertible_v<OtherElementType(*)[], element_type (*)[]>,
                          nullptr_t> = nullptr) noexcept
        : data_{other.data()}, size_{other.size()} {}

//    ~span() noexcept = default;

    template <size_t count>
    inline constexpr span<element_type, count> first() const noexcept
    {
        static_assert(count >= 0, "Count must be >= 0 in span::first()");
        assert(count <= size());
        return {data(), count};
    }

    template <size_t count>
    inline constexpr span<element_type, count> last() const noexcept
    {
        static_assert(count >= 0, "Count must be >= 0 in span::last()");
        assert(count <= size());
        return {data() + size() - count, count};
    }

    constexpr span<element_type, dynamic_extent> first(index_type count) const noexcept
    {
        assert(count >= 0 && count <= size());
        return {data(), count};
    }

    constexpr span<element_type, dynamic_extent> last (index_type count) const noexcept
    {
        assert(count >= 0 && count <= size());
        return {data() + size() - count, count};
    }

    template <size_t offset, size_t count = dynamic_extent>
    inline constexpr span<span_tp, dynamic_extent> subspan() const noexcept
    {
        assert(offset >= 0 && offset <= size());
        assert(count == dynamic_extent || offset + count <= size());
        return {data() + offset, count == dynamic_extent ? size() - offset : count};
    }

    constexpr span<element_type, dynamic_extent>
    inline subspan(index_type offset, index_type count = dynamic_extent) const noexcept
    {
        assert(offset >= 0 && offset <= size());
        assert(count  >= 0 && count  <= size());
        if (count == dynamic_extent)
            return {data() + offset, size() - offset};
        assert(offset + count <= size());
        return {data() + offset, count};
    }

    constexpr index_type size()       const noexcept { return size_; }
    constexpr index_type size_bytes() const noexcept { return size_ * sizeof(element_type); }
    constexpr bool empty()            const noexcept { return size_ == 0; }

    constexpr reference front() const noexcept
    {
        return *begin();
    }

    constexpr reference back() const noexcept
    {
        return *(end() - 1);
    }

    constexpr reference operator[](index_type idx) const noexcept
    {
        assert(idx < size());
        return data_[idx];
    }

    constexpr reference operator()(index_type idx) const noexcept
    {
        assert(idx < size());
        return data_[idx];
    }

    constexpr pointer data()                         const noexcept { return data_; }

// [span.iter], span iterator support
    constexpr iterator                 begin() const noexcept { return iterator(data()); }
    constexpr iterator                   end() const noexcept { return iterator(data() + size()); }
    constexpr const_iterator          cbegin() const noexcept { return const_iterator(data()); }
    constexpr const_iterator            cend() const noexcept { return const_iterator(data() + size()); }
    constexpr reverse_iterator        rbegin() const noexcept { return reverse_iterator(end()); }
    constexpr reverse_iterator          rend() const noexcept { return reverse_iterator(begin()); }
    constexpr const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(cend()); }
    constexpr const_reverse_iterator   crend() const noexcept { return const_reverse_iterator(cbegin()); }

    constexpr void swap(span &other) noexcept
    {
        pointer p = data_;
        data_ = other.data_;
        other.data_ = p;

        index_type sz = size_;
        size_ = other.size_;
        other.size_ = sz;
    }

    span<const byte, dynamic_extent> as_bytes() const noexcept
    { return {reinterpret_cast<const byte *>(data()), size_bytes()}; }

    span<byte, dynamic_extent> as_writeable_bytes() const noexcept
    { return {reinterpret_cast<byte *>(data()), size_bytes()}; }

private:
    pointer    data_;
    index_type size_;
};

template <typename span_tp, size_t extend>
constexpr auto begin(span<span_tp, extend> s) noexcept
{
    return s.begin();
}

template <typename span_tp, size_t extend>
constexpr auto end(span<span_tp, extend> s) noexcept
{
    return s.end();
}

//  as_bytes & as_writeable_bytes
template <class span_tp, size_t span_extent>
    auto as_bytes(span<span_tp, span_extent> s) noexcept
    -> decltype(s.as_bytes())
    { return s.as_bytes(); }

template <class span_tp, size_t span_extent>
    auto as_writeable_bytes(span<span_tp, span_extent> s) noexcept
    -> typename enable_if<!is_const_v<span_tp>, decltype(s.as_writeable_bytes())>::type
    { return s.as_writeable_bytes(); }

template <class span_tp, size_t span_extent>
    constexpr void swap(span<span_tp, span_extent> &lhs, span<span_tp, span_extent> &rhs) noexcept
    { lhs.swap(rhs); }

//  Deduction guides
template <contiguous_iterator iterator_t>
    span(iterator_t, size_t) -> span<remove_reference_t<iter_reference_t<iterator_t>>>;

template <contiguous_iterator iterator_t, typename sentinel_t>
    requires sized_sentinel_for<sentinel_t, iterator_t>
    span(iterator_t, sentinel_t) -> span<remove_reference_t<iter_reference_t<iterator_t>>>;

template <class span_tp, size_t span_sz>
    span(span_tp (&)[span_sz]) -> span<span_tp, span_sz>;

template <class span_tp, size_t span_sz>
    span(array<span_tp, span_sz>&) -> span<span_tp, span_sz>;

template <class span_tp, size_t span_sz>
    span(const array<span_tp, span_sz>&) -> span<const span_tp, span_sz>;

template <ranges::contiguous_range range_t>
    span(range_t &&) -> span<ranges::range_value_t<range_t>>;
} // namespace std

#endif // !defined(__cpp_lib_span)
//!\endcond
