/**
 * Copyright 2013-2022 Software Radio Systems Limited
 *
 * This file is part of srsRAN.
 *
 * srsRAN is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * srsRAN is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * A copy of the GNU Affero General Public License can be found in
 * the LICENSE file in the top-level directory of this distribution
 * and at http://www.gnu.org/licenses/.
 *
 */

#ifndef SRSRAN_SPAN_H
#define SRSRAN_SPAN_H

#include <algorithm>
#include <array>
#include <cassert>
#include <iterator>
#include <type_traits>
#include <vector>

namespace srsran {

template <typename T>
class span;

namespace detail {

/// Helper traits used by SFINAE expressions in constructors.

template <typename... Ts>
struct make_void {
  typedef void type;
};

template <typename... Ts>
using void_t = typename make_void<Ts...>::type;

template <typename U>
struct is_span : std::false_type {};
template <typename U>
struct is_span<span<U> > : std::true_type {};

template <typename U>
struct is_std_array : std::false_type {};
template <typename U, std::size_t N>
struct is_std_array<std::array<U, N> > : std::true_type {};

template <typename U>
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<U>::type>::type;

template <class Container, class U, class = void>
struct is_container_compatible : public std::false_type {};
template <class Container, class U>
struct is_container_compatible<
    Container,
    U,
    void_t<
        // Check if the container type has data and size members.
        decltype(std::declval<Container>().data()),
        decltype(std::declval<Container>().size()),
        // Container should not be a span.
        typename std::enable_if<!is_span<remove_cvref_t<Container> >::value, int>::type,
        // Container should not be a std::array.
        typename std::enable_if<!is_std_array<remove_cvref_t<Container> >::value, int>::type,
        // Container should not be an array.
        typename std::enable_if<!std::is_array<remove_cvref_t<Container> >::value, int>::type,
        // Check type compatibility between the contained type and the span type.
        typename std::enable_if<
            std::is_convertible<typename std::remove_pointer<decltype(std::declval<Container>().data())>::type (*)[],
                                U (*)[]>::value,
            int>::type> > : public std::true_type {};

} // namespace detail

/// The class template span describes an object that can refer to a contiguous sequence of objects with the first
/// element of the sequence at position zero.
template <typename T>
class span
{
public:
  /// Member types.
  using element_type     = T;
  using value_type       = typename std::remove_cv<T>::type;
  using size_type        = std::size_t;
  using difference_type  = std::ptrdiff_t;
  using pointer          = element_type*;
  using const_pointer    = const element_type*;
  using reference        = element_type&;
  using const_reference  = const element_type&;
  using iterator         = pointer;
  using reverse_iterator = std::reverse_iterator<iterator>;

  /// Constructs an empty span with data() == nullptr and size() == 0.
  constexpr span() noexcept = default;

  /// Constructs a span that is a view over the range [ptr, ptr + len).
  constexpr span(pointer ptr, size_type len) noexcept : ptr(ptr), len(len) {}

  /// Constructs a span that is a view over the range [first, last).
  constexpr span(pointer first, pointer last) noexcept : ptr(first), len(last - first) {}

  /// Constructs a span that is a view over the array arr.
  template <std::size_t N>
  constexpr span(element_type (&arr)[N]) noexcept : ptr(arr), len(N)
  {}

  /// Constructs a span that is a view over the array arr.
  template <typename U,
            std::size_t N,
            typename std::enable_if<std::is_convertible<U (*)[], element_type (*)[]>::value, int>::type = 0>
  constexpr span(std::array<U, N>& arr) noexcept : ptr(arr.data()), len(N)
  {}

  /// Constructs a span that is a view over the array arr.
  template <typename U,
            std::size_t N,
            typename std::enable_if<std::is_convertible<const U (*)[], element_type (*)[]>::value, int>::type = 0>
  constexpr span(const std::array<U, N>& arr) noexcept : ptr(arr.data()), len(N)
  {}

  /// Constructs a span that is a view over the container c.
  template <typename Container,
            typename std::enable_if<detail::is_container_compatible<Container, element_type>::value, int>::type = 0>
  constexpr span(Container& c) noexcept : ptr(c.data()), len(c.size())
  {}

  /// Constructs a span that is a view over the container c.
  template <
      typename Container,
      typename std::enable_if<detail::is_container_compatible<const Container, element_type>::value, int>::type = 0>
  constexpr span(const Container& c) noexcept : ptr(c.data()), len(c.size())
  {}

  template <typename U, typename std::enable_if<std::is_convertible<U (*)[], element_type (*)[]>::value, int>::type = 0>
  constexpr span(const span<U>& other) noexcept : ptr(other.data()), len(other.size())
  {}

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

  ~span() noexcept = default;

  /// Returns the number of elements in the span.
  constexpr size_type size() const noexcept { return len; }

  /// Returns the size of the sequence in bytes.
  constexpr size_type size_bytes() const noexcept { return len * sizeof(element_type); }

  /// Checks if the span is empty.
  constexpr bool empty() const noexcept { return size() == 0; }

  /// Returns a reference to the first element in the span.
  /// NOTE: Calling front on an empty span results in undefined behavior.
  reference front() const
  {
    assert(!empty() && "called front with empty span");
    return *data();
  }

  /// Returns a reference to the last element in the span.
  /// NOTE: Calling back on an empty span results in undefined behavior.
  reference back() const
  {
    assert(!empty() && "called back with empty span");
    return *(data() + size() - 1);
  }

  /// Returns a reference to the idx-th element of the sequence.
  /// NOTE: The behavior is undefined if idx is out of range.
  reference operator[](size_type idx) const
  {
    assert(idx < len && "index out of bounds!");
    return ptr[idx];
  }

  /// Returns a pointer to the beginning of the sequence.
  constexpr pointer data() const noexcept { return ptr; }

  /// Returns an iterator to the first element of the span.
  constexpr iterator begin() const noexcept { return data(); }

  /// Returns an iterator to the element following the last element of the span.
  constexpr iterator end() const noexcept { return data() + size(); }

  /// Returns a reverse iterator to the first element of the reversed span.
  constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); }

  /// Returns a reverse iterator to the element following the last element of the reversed span.
  constexpr reverse_iterator rend() const noexcept { return reverse_iterator(begin()); }

  /// Obtains a span that is a view over the first count elements of this span.
  /// NOTE: The behavior is undefined if count > size().
  span<element_type> first(size_type count) const
  {
    assert(count <= size() && "count is out of range");
    return subspan(0, count);
  }

  /// Obtains a span that is a view over the last count elements of this span.
  /// NOTE: The behavior is undefined if count > size().
  span<element_type> last(size_type count) const
  {
    assert(count <= size() && "count is out of range");
    return subspan(size() - count, count);
  }

  /// Obtains a span that is a view over the count elements of this span starting at offset offset.
  span<element_type> subspan(size_type offset, size_type count) const
  {
    assert(count <= size() - offset && "size out of bounds!");
    return {data() + offset, count};
  }

  /// Returns true if the input span has the same elements as this.
  bool equals(span rhs) const { return (len == rhs.len) ? std::equal(begin(), end(), rhs.begin()) : false; }

private:
  pointer   ptr = nullptr;
  size_type len = 0;
};

template <typename T>
inline bool operator==(span<T> lhs, span<T> rhs)
{
  return lhs.equals(rhs);
}

template <typename T>
inline bool operator!=(span<T> lhs, span<T> rhs)
{
  return not lhs.equals(rhs);
}

template <typename T>
using const_span = span<const T>;

} // namespace srsran

#endif // SRSRAN_SPAN_H
