/*************************************************************************
 *
 * Copyright 2022 Realm Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **************************************************************************/

#ifndef REALM_UTIL_SPAN_HPP
#define REALM_UTIL_SPAN_HPP

#include <realm/util/assert.hpp>

#include <array>
#include <cstddef>
#include <iterator>
#include <limits>
#include <type_traits>

namespace realm::util {

// This is an implementation of C++20's std::span. This should be an exact
// drop-in replacement that can be deleted once we switch to building as C++20.
// See https://en.cppreference.com/w/cpp/container/span for documentation on this type.

inline constexpr size_t dynamic_extent = std::numeric_limits<size_t>::max();

template <typename T, size_t extent = dynamic_extent>
class Span;

} // namespace realm::util

namespace realm::_impl {

// SFINAE helpers: anything which has std::data() and std::size() and
// std::data() produces the correct type can be converted to Span, but
// std::array, C arrays, and Span have separate conversions which need to be
// used instead of the generic one.
template <typename T>
struct IsSpan : public std::false_type {};
template <typename T, size_t extent>
struct IsSpan<util::Span<T, extent>> : public std::true_type {};

template <typename T>
struct IsStdArray : public std::false_type {};
template <typename T, size_t size>
struct IsStdArray<std::array<T, size>> : public std::true_type {};

// msvc v19.28 hits an internal compiler error if these are inline in the
// template using them rather than type aliases. This appears to be fixed in
// newer versions
template <typename T>
using StdDataT = decltype(std::data(std::declval<T>()));
template <typename T>
using StdSizeT = decltype(std::size(std::declval<T>()));

template <typename T, typename U, typename A = StdDataT<T>, typename B = StdSizeT<T>>
constexpr bool is_span_compatible()
{
    return !IsSpan<std::remove_cv_t<T>>::value && !IsStdArray<std::remove_cv_t<T>>::value && !std::is_array_v<T> &&
           std::is_convertible_v<std::remove_pointer_t<decltype(std::data(std::declval<T&>()))>(*)[], U(*)[]>;
}

template <typename C, typename T>
using EnableIfSpanCompatible = std::enable_if_t<is_span_compatible<C, T>(), int>;
} // namespace realm::_impl

namespace realm::util {
template <typename T, size_t Extent>
class Span {
public:
    using element_type = T;
    using value_type = std::remove_cv_t<T>;
    using size_type = size_t;
    using difference_type = ptrdiff_t;
    using pointer = T*;
    using const_pointer = const T*;
    using reference = T&;
    using const_reference = const T&;
    using iterator = pointer;
    using reverse_iterator = std::reverse_iterator<iterator>;

    static constexpr size_type extent = Extent;

    template <size_t size = extent, std::enable_if_t<size == 0, int> = 0>
    constexpr Span() noexcept
    {
    }
    constexpr Span(const Span&) noexcept = default;
    constexpr Span& operator=(const Span&) noexcept = default;

    constexpr explicit Span(pointer ptr, size_type count)
        : m_data{ptr}
    {
        REALM_ASSERT(extent == count);
    }
    constexpr explicit Span(pointer begin, pointer end)
        : m_data{begin}
    {
        REALM_ASSERT(extent == std::distance(begin, end));
    }
#if 0 // VS 16.9 incorrectly rejects this. 16.10+ support it
    constexpr Span(element_type (&arr)[extent]) noexcept
        : m_data{arr}
    {
    }
#endif
    template <class U, std::enable_if_t<std::is_convertible_v<U (*)[], element_type (*)[]>, int> = 0>
    constexpr Span(std::array<U, extent>& arr) noexcept
        : m_data{arr.data()}
    {
    }
    template <class U, std::enable_if_t<std::is_convertible_v<const U (*)[], element_type (*)[]>, int> = 0>
    constexpr Span(const std::array<U, extent>& arr) noexcept
        : m_data{arr.data()}
    {
    }
    template <class Container, _impl::EnableIfSpanCompatible<Container, T> = 0>
    constexpr explicit Span(Container& c)
        : m_data{std::data(c)}
    {
        REALM_ASSERT(extent == std::size(c));
    }

    template <class Container, _impl::EnableIfSpanCompatible<const Container, T> = 0>
    constexpr explicit Span(const Container& c)
        : m_data{std::data(c)}
    {
        REALM_ASSERT(extent == std::size(c));
    }

    template <class U, std::enable_if_t<std::is_convertible_v<U (*)[], element_type (*)[]>, int> = 0>
    constexpr Span(const Span<U, extent>& other)
        : m_data{other.data()}
    {
    }

#if 0 // VS 16.9 incorrectly rejects this. 16.10+ support it
    template <class U, std::enable_if_t<std::is_convertible_v<U (*)[], element_type (*)[]>, int> = 0>
    constexpr explicit Span(const Span<U, dynamic_extent>& other) noexcept
        : m_data{other.data()}
    {
        REALM_ASSERT(extent == other.size());
    }
#endif

    template <size_t count>
    constexpr Span<element_type, count> first() const noexcept
    {
        static_assert(count <= extent);
        return Span<element_type, count>{data(), count};
    }

    template <size_t count>
    constexpr Span<element_type, count> last() const noexcept
    {
        static_assert(count <= extent);
        return Span<element_type, count>{data() + size() - count, count};
    }

    constexpr Span<element_type, dynamic_extent> first(size_type count) const noexcept
    {
        REALM_ASSERT(count <= size());
        return {data(), count};
    }

    constexpr Span<element_type, dynamic_extent> last(size_type count) const noexcept
    {
        REALM_ASSERT(count <= size());
        return {data() + size() - count, count};
    }

    template <size_t offset, size_t count = dynamic_extent>
    constexpr auto sub_span() const noexcept
    {
        static_assert(offset <= extent);
        static_assert(count == dynamic_extent || count <= extent - offset);

        using Ret = Span<element_type, count != dynamic_extent ? count : extent - offset>;
        return Ret{data() + offset, count == dynamic_extent ? size() - offset : count};
    }

    constexpr Span<element_type, dynamic_extent> sub_span(size_type offset,
                                                          size_type count = dynamic_extent) const noexcept
    {
        REALM_ASSERT(offset <= size());
        REALM_ASSERT(count <= size() || count == dynamic_extent);
        if (count == dynamic_extent)
            return {data() + offset, size() - offset};
        REALM_ASSERT(count <= size() - offset);
        return {data() + offset, count};
    }

    constexpr size_type size() const noexcept
    {
        return extent;
    }
    constexpr size_type size_bytes() const noexcept
    {
        return extent * sizeof(element_type);
    }
    constexpr bool empty() const noexcept
    {
        return extent == 0;
    }
    constexpr reference operator[](size_type idx) const noexcept
    {
        REALM_ASSERT(idx < size());
        return m_data[idx];
    }
    constexpr reference front() const noexcept
    {
        REALM_ASSERT(!empty());
        return m_data[0];
    }
    constexpr reference back() const noexcept
    {
        REALM_ASSERT(!empty());
        return m_data[size() - 1];
    }
    constexpr pointer data() const noexcept
    {
        return m_data;
    }
    constexpr iterator begin() const noexcept
    {
        return data();
    }
    constexpr iterator end() const noexcept
    {
        return data() + size();
    }
    constexpr reverse_iterator rbegin() const noexcept
    {
        return reverse_iterator(end());
    }
    constexpr reverse_iterator rend() const noexcept
    {
        return reverse_iterator(begin());
    }
    Span<const std::byte, extent * sizeof(element_type)> as_bytes() const noexcept
    {
        return Span<const std::byte, extent * sizeof(element_type)>{reinterpret_cast<const std::byte*>(data()),
                                                                    size_bytes()};
    }
    Span<std::byte, extent * sizeof(element_type)> as_writable_bytes() const noexcept
    {
        return Span<std::byte, extent * sizeof(element_type)>{reinterpret_cast<std::byte*>(data()), size_bytes()};
    }

private:
    pointer m_data = nullptr;
};

template <typename T>
class Span<T, dynamic_extent> {
public:
    using element_type = T;
    using value_type = std::remove_cv_t<T>;
    using size_type = size_t;
    using difference_type = ptrdiff_t;
    using pointer = T*;
    using const_pointer = const T*;
    using reference = T&;
    using const_reference = const T&;
    using iterator = pointer;
    using reverse_iterator = std::reverse_iterator<iterator>;

    static constexpr size_type extent = dynamic_extent;

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

    constexpr Span(pointer ptr, size_type count)
        : m_data{ptr}
        , m_size{count}
    {
    }
    constexpr Span(pointer f, pointer l)
        : m_data{f}
        , m_size{static_cast<size_t>(std::distance(f, l))}
    {
    }

    template <size_t size>
    constexpr Span(element_type (&arr)[size]) noexcept
        : m_data{arr}
        , m_size{size}
    {
    }

    template <class U, size_t size, std::enable_if_t<std::is_convertible_v<U (*)[], element_type (*)[]>, int> = 0>
    constexpr Span(std::array<U, size>& arr) noexcept
        : m_data{arr.data()}
        , m_size{size}
    {
    }

    template <class U, size_t size,
              std::enable_if_t<std::is_convertible_v<const U (*)[], element_type (*)[]>, int> = 0>
    constexpr Span(const std::array<U, size>& arr) noexcept
        : m_data{arr.data()}
        , m_size{size}
    {
    }

    template <class Container, _impl::EnableIfSpanCompatible<Container, T> = 0>
    constexpr Span(Container& c)
        : m_data{std::data(c)}
        , m_size{(size_type)std::size(c)}
    {
    }

    template <class Container, _impl::EnableIfSpanCompatible<const Container, T> = 0>
    constexpr Span(const Container& c)
        : m_data{std::data(c)}
        , m_size{(size_type)std::size(c)}
    {
    }

    template <class U, size_t E, std::enable_if_t<std::is_convertible_v<U (*)[], element_type (*)[]>, int> = 0>
    constexpr Span(const Span<U, E>& other) noexcept
        : m_data{other.data()}
        , m_size{other.size()}
    {
    }

    template <size_t count>
    constexpr Span<element_type, count> first() const noexcept
    {
        REALM_ASSERT(count <= m_size);
        return Span<element_type, count>{m_data, count};
    }

    template <size_t count>
    constexpr Span<element_type, count> last() const noexcept
    {
        REALM_ASSERT(count <= m_size);
        return Span<element_type, count>{m_data + m_size - count, count};
    }

    constexpr Span<element_type, dynamic_extent> first(size_type count) const noexcept
    {
        REALM_ASSERT(count <= m_size);
        return {m_data, count};
    }

    constexpr Span<element_type, dynamic_extent> last(size_type count) const noexcept
    {
        REALM_ASSERT(count <= m_size);
        return {m_data + m_size - count, count};
    }

    template <size_t offset, size_t count = dynamic_extent>
    constexpr Span<element_type, count> sub_span() const noexcept
    {
        REALM_ASSERT(offset <= m_size);
        REALM_ASSERT(count == dynamic_extent || count <= m_size - offset);
        return Span<element_type, count>{m_data + offset, count == dynamic_extent ? m_size - offset : count};
    }

    constexpr Span<element_type, dynamic_extent> sub_span(size_type offset,
                                                          size_type count = dynamic_extent) const noexcept
    {
        REALM_ASSERT(offset <= m_size);
        REALM_ASSERT(count <= m_size || count == dynamic_extent);
        if (count == dynamic_extent)
            return {m_data + offset, m_size - offset};
        REALM_ASSERT(count <= m_size - offset);
        return {m_data + offset, count};
    }

    constexpr size_type size() const noexcept
    {
        return m_size;
    }
    constexpr size_type size_bytes() const noexcept
    {
        return m_size * sizeof(element_type);
    }
    constexpr bool empty() const noexcept
    {
        return m_size == 0;
    }
    constexpr reference operator[](size_type idx) const noexcept
    {
        REALM_ASSERT(idx < m_size);
        return m_data[idx];
    }
    constexpr reference front() const noexcept
    {
        REALM_ASSERT(m_size);
        return m_data[0];
    }
    constexpr reference back() const noexcept
    {
        REALM_ASSERT(m_size);
        return m_data[m_size - 1];
    }
    constexpr pointer data() const noexcept
    {
        return m_data;
    }
    constexpr iterator begin() const noexcept
    {
        return m_data;
    }
    constexpr iterator end() const noexcept
    {
        return m_data + m_size;
    }
    constexpr reverse_iterator rbegin() const noexcept
    {
        return reverse_iterator(end());
    }
    constexpr reverse_iterator rend() const noexcept
    {
        return reverse_iterator(begin());
    }
    Span<const std::byte, dynamic_extent> as_bytes() const noexcept
    {
        return {reinterpret_cast<const std::byte*>(m_data), size_bytes()};
    }
    Span<std::byte, dynamic_extent> as_writable_bytes() const noexcept
    {
        return {reinterpret_cast<std::byte*>(m_data), size_bytes()};
    }

private:
    pointer m_data = nullptr;
    size_type m_size = 0;
};

template <typename T, size_t extent>
auto as_bytes(Span<T, extent> s) noexcept -> decltype(s.as_bytes())
{
    return s.as_bytes();
}

template <typename T, size_t extent>
auto as_writable_bytes(Span<T, extent> s) noexcept
    -> std::enable_if_t<!std::is_const_v<T>, decltype(s.as_writable_bytes())>
{
    return s.as_writable_bytes();
}

template <typename T, typename... Args>
constexpr auto unsafe_span_cast(Args&&... args)
{
    auto temp = Span(std::forward<Args>(args)...);
    return Span<T, decltype(temp)::extent>(reinterpret_cast<T*>(temp.data()), temp.size());
}

//  Deduction guides
template <typename T, size_t extent>
Span(T (&)[extent]) -> Span<T, extent>;

template <typename T, size_t extent>
Span(std::array<T, extent>&) -> Span<T, extent>;

template <typename T, size_t extent>
Span(const std::array<T, extent>&) -> Span<const T, extent>;

template <class Container>
Span(Container&) -> Span<typename Container::value_type>;

template <class Container>
Span(const Container&) -> Span<const typename Container::value_type>;

} // namespace realm::util

#endif // REALM_UTIL_SPAN_HPP
