// Copyright (c) 2019-2020 Dr. Colin Hirsch and Daniel Frey
// Please see LICENSE for license or visit https://github.com/taocpp/json/

#ifndef TAO_JSON_SPAN_HPP
#define TAO_JSON_SPAN_HPP

#if !defined( TAO_JSON_USE_STD_SPAN ) && ( __cplusplus > 201703L ) && defined( __has_include )
// clang-format off
#if __has_include(<span>)
// clang-format on
#define TAO_JSON_USE_STD_SPAN
#endif
#endif

#if defined( TAO_JSON_USE_STD_SPAN )

#include <span>

namespace tao
{
   using std::dynamic_extent;
   using std::span;

   using std::as_bytes;
   using std::as_writable_bytes;

}  // namespace tao

#else

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

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

   template< typename ElementType, std::size_t Extent = dynamic_extent >
   class span;

   namespace internal
   {
      template< typename >
      inline constexpr bool is_span = false;

      template< typename ElementType, std::size_t Extent >
      inline constexpr bool is_span< span< ElementType, Extent > > = true;

      template< typename >
      inline constexpr bool is_std_array = false;

      template< typename T, std::size_t N >
      inline constexpr bool is_std_array< std::array< T, N > > = true;

      template< typename T, typename ElementType >
      inline constexpr bool is_span_compatible_ptr = std::is_convertible_v< T ( * )[], ElementType ( * )[] >;

      template< typename, typename, typename = void >
      inline constexpr bool is_span_compatible_container = false;

      template< typename Container, typename ElementType >
      inline constexpr bool is_span_compatible_container< Container,
                                                          ElementType,
                                                          std::void_t<
                                                             std::enable_if_t< !is_span< std::remove_cv_t< Container > > >,
                                                             std::enable_if_t< !is_std_array< std::remove_cv_t< Container > > >,
                                                             std::enable_if_t< !std::is_array_v< Container > >,
                                                             decltype( std::data( std::declval< Container >() ) ),
                                                             decltype( std::size( std::declval< Container >() ) ),
                                                             std::enable_if_t< std::is_convertible_v< std::remove_pointer_t< decltype( std::data( std::declval< Container& >() ) ) > ( * )[], ElementType ( * )[] > > > > = true;

   }  // namespace internal

   template< typename ElementType, std::size_t Extent >
   class span  // NOLINT(cppcoreguidelines-special-member-functions)
   {
   public:
      static_assert( !std::is_abstract_v< ElementType > );

      using element_type = ElementType;
      using value_type = std::remove_cv_t< ElementType >;
      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 >;

      static constexpr size_type extent = Extent;

      template< typename T = void, typename = std::enable_if_t< Extent == 0, T > >
      constexpr span() noexcept
         : m_data( nullptr )
      {}

      constexpr span( pointer ptr, size_type count ) noexcept
         : m_data( ptr )
      {
         assert( count == Extent );
      }

      constexpr span( pointer first, pointer last ) noexcept
         : m_data( first )
      {
         assert( std::distance( first, last ) == Extent );
      }

      constexpr span( element_type ( &arr )[ Extent ] ) noexcept
         : m_data( arr )
      {}

      template< typename OtherElementType, std::size_t N, typename = std::enable_if_t< ( N == Extent ) && tao::internal::is_span_compatible_ptr< OtherElementType, ElementType > > >
      constexpr span( std::array< OtherElementType, N >& arr ) noexcept
         : m_data( static_cast< pointer >( arr.data() ) )
      {}

      template< typename OtherElementType, std::size_t N, typename = std::enable_if_t< ( N == Extent ) && tao::internal::is_span_compatible_ptr< const OtherElementType, ElementType > > >
      constexpr span( const std::array< OtherElementType, N >& arr ) noexcept
         : m_data( static_cast< pointer >( arr.data() ) )
      {}

      constexpr span( const span& ) = default;

      template< typename OtherElementType, typename = std::enable_if_t< tao::internal::is_span_compatible_ptr< OtherElementType, ElementType > > >
      constexpr span( const span< OtherElementType, Extent >& s ) noexcept
         : m_data( s.data() )
      {}

      ~span() = default;

      constexpr auto operator=( const span& ) -> span& = default;

      constexpr auto size() const noexcept -> size_type  // NOLINT(modernize-use-nodiscard)
      {
         return Extent;
      }

      constexpr auto size_bytes() const noexcept -> size_type  // NOLINT(modernize-use-nodiscard)
      {
         return Extent * sizeof( element_type );
      }

      [[nodiscard]] constexpr auto empty() const noexcept -> bool
      {
         return Extent == 0;
      }

      constexpr auto operator[]( size_type idx ) const noexcept -> reference
      {
         assert( idx < Extent );
         return *( data() + idx );
      }

      constexpr auto front() const noexcept -> reference
      {
         assert( Extent != 0 );
         return *data();
      }

      constexpr auto back() const noexcept -> reference
      {
         assert( Extent != 0 );
         return *( data() + ( Extent - 1 ) );
      }

      constexpr auto data() const noexcept -> pointer
      {
         return m_data;
      }

      constexpr auto begin() const noexcept -> iterator
      {
         return data();
      }

      constexpr auto end() const noexcept -> iterator
      {
         return data() + Extent;
      }

      constexpr auto rbegin() const noexcept -> reverse_iterator
      {
         return reverse_iterator( end() );
      }

      constexpr auto rend() const noexcept -> reverse_iterator
      {
         return reverse_iterator( begin() );
      }

      template< std::size_t Count >
      constexpr auto first() const noexcept
         -> span< element_type, Count >
      {
         static_assert( Count <= Extent );
         return { data(), Count };
      }

      template< std::size_t Count >
      constexpr auto last() const noexcept
         -> span< element_type, Count >
      {
         static_assert( Count <= Extent );
         return { data() + ( Extent - Count ), Count };
      }

      template< std::size_t Offset, std::size_t Count = dynamic_extent >
      constexpr auto subspan() const
         -> span< element_type, ( ( Count != dynamic_extent ) ? Count : ( Extent - Offset ) ) >
      {
         static_assert( Offset <= Extent );
         static_assert( ( Count == dynamic_extent ) || ( Count <= ( Extent - Offset ) ) );
         return { data() + Offset, ( Count != dynamic_extent ) ? Count : ( Extent - Offset ) };
      }

      constexpr auto first( size_type count ) const
         -> span< element_type, dynamic_extent >
      {
         assert( count <= Extent );
         return { data(), count };
      }

      constexpr auto last( size_type count ) const
         -> span< element_type, dynamic_extent >
      {
         assert( count <= Extent );
         return { data() + Extent - count, count };
      }

      constexpr auto subspan( size_type offset, size_type count = dynamic_extent ) const
         -> span< element_type, dynamic_extent >
      {
         assert( offset <= Extent );
         assert( ( count == dynamic_extent ) || ( count <= ( Extent - offset ) ) );
         return { data() + offset, ( count != dynamic_extent ) ? count : ( Extent - offset ) };
      }

   private:
      pointer m_data;
   };

   template< typename ElementType >
   class span< ElementType, dynamic_extent >  // NOLINT(cppcoreguidelines-special-member-functions)
   {
   public:
      static_assert( !std::is_abstract_v< ElementType > );

      using element_type = ElementType;
      using value_type = std::remove_cv_t< ElementType >;
      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 >;

      static constexpr size_type extent = dynamic_extent;

      constexpr span() noexcept
         : m_data( nullptr ), m_size( 0 )
      {}

      constexpr span( pointer ptr, size_type count ) noexcept
         : m_data( ptr ), m_size( count )
      {}

      constexpr span( pointer first, pointer last ) noexcept
         : m_data( first ), m_size( std::distance( first, last ) )
      {}

      template< std::size_t N >
      constexpr span( element_type ( &arr )[ N ] ) noexcept
         : m_data( arr ), m_size( N )
      {}

      template< typename OtherElementType, std::size_t N, typename = std::enable_if_t< tao::internal::is_span_compatible_ptr< OtherElementType, ElementType > > >
      constexpr span( std::array< OtherElementType, N >& arr ) noexcept
         : m_data( static_cast< pointer >( arr.data() ) ), m_size( N )
      {}

      template< typename OtherElementType, std::size_t N, typename = std::enable_if_t< tao::internal::is_span_compatible_ptr< const OtherElementType, ElementType > > >
      constexpr span( const std::array< OtherElementType, N >& arr ) noexcept
         : m_data( static_cast< pointer >( arr.data() ) ), m_size( N )
      {}

      template< typename Container, typename = std::enable_if_t< tao::internal::is_span_compatible_container< Container, ElementType > > >
      constexpr span( Container& cont )
         : m_data( static_cast< pointer >( std::data( cont ) ) ), m_size( std::size( cont ) )
      {}

      template< typename Container, typename = std::enable_if_t< tao::internal::is_span_compatible_container< const Container, ElementType > > >
      constexpr span( const Container& cont )
         : m_data( static_cast< pointer >( std::data( cont ) ) ), m_size( std::size( cont ) )
      {}

      constexpr span( const span& ) = default;

      template< typename OtherElementType, std::size_t OtherExtent, typename = std::enable_if_t< tao::internal::is_span_compatible_ptr< OtherElementType, ElementType > > >
      constexpr span( const span< OtherElementType, OtherExtent >& s ) noexcept
         : m_data( s.data() ), m_size( s.size() )
      {}

      ~span() = default;

      constexpr auto operator=( const span& ) -> span& = default;

      constexpr auto size() const noexcept -> size_type  // NOLINT(modernize-use-nodiscard)
      {
         return m_size;
      }

      constexpr auto size_bytes() const noexcept -> size_type  // NOLINT(modernize-use-nodiscard)
      {
         return size() * sizeof( element_type );
      }

      [[nodiscard]] constexpr auto empty() const noexcept -> bool
      {
         return size() == 0;
      }

      constexpr auto operator[]( size_type idx ) const noexcept -> reference
      {
         assert( idx < size() );
         return *( data() + idx );
      }

      constexpr auto front() const noexcept -> reference
      {
         assert( !empty() );
         return *data();
      }

      constexpr auto back() const noexcept -> reference
      {
         assert( !empty() );
         return *( data() + ( size() - 1 ) );
      }

      constexpr auto data() const noexcept -> pointer
      {
         return m_data;
      }

      constexpr auto begin() const noexcept -> iterator
      {
         return data();
      }

      constexpr auto end() const noexcept -> iterator
      {
         return data() + size();
      }

      constexpr auto rbegin() const noexcept -> reverse_iterator
      {
         return reverse_iterator( end() );
      }

      constexpr auto rend() const noexcept -> reverse_iterator
      {
         return reverse_iterator( begin() );
      }

      template< std::size_t Count >
      constexpr auto first() const noexcept
         -> span< element_type, Count >
      {
         assert( Count <= size() );
         return { data(), Count };
      }

      template< std::size_t Count >
      constexpr auto last() const noexcept
         -> span< element_type, Count >
      {
         assert( Count <= size() );
         return { data() + ( size() - Count ), Count };
      }

      template< std::size_t Offset, std::size_t Count = dynamic_extent >
      constexpr auto subspan() const
         -> span< element_type, ( ( Count != dynamic_extent ) ? Count : dynamic_extent ) >
      {
         assert( Offset <= size() );
         assert( ( Count == dynamic_extent ) || ( Count <= ( size() - Offset ) ) );
         return { data() + Offset, ( Count != dynamic_extent ) ? Count : ( size() - Offset ) };
      }

      constexpr auto first( size_type count ) const
         -> span< element_type, dynamic_extent >
      {
         assert( count <= size() );
         return { data(), count };
      }

      constexpr auto last( size_type count ) const
         -> span< element_type, dynamic_extent >
      {
         assert( count <= size() );
         return { data() + size() - count, count };
      }

      constexpr auto subspan( size_type offset, size_type count = dynamic_extent ) const
         -> span< element_type, dynamic_extent >
      {
         assert( offset <= size() );
         assert( ( count == dynamic_extent ) || ( count <= ( size() - offset ) ) );
         return { data() + offset, ( count != dynamic_extent ) ? count : ( size() - offset ) };
      }

   private:
      pointer m_data;
      size_type m_size;  // NOLINT(modernize-use-default-member-init)
   };

   template< typename ElementType, std::size_t Extent >
   auto as_bytes( span< ElementType, Extent > s ) noexcept
      -> span< const std::byte, ( ( Extent == dynamic_extent ) ? dynamic_extent : ( sizeof( ElementType ) * Extent ) ) >
   {
      return { reinterpret_cast< const std::byte* >( s.data() ), s.size_bytes() };
   }

   template< typename ElementType, std::size_t Extent, typename = std::enable_if_t< !std::is_const_v< ElementType > > >
   auto as_writable_bytes( span< ElementType, Extent > s ) noexcept
      -> span< std::byte, ( ( Extent == dynamic_extent ) ? dynamic_extent : ( sizeof( ElementType ) * Extent ) ) >
   {
      return { reinterpret_cast< std::byte* >( s.data() ), s.size_bytes() };
   }

   // deduction guides
   template< typename T, std::size_t N >
   span( T ( & )[ N ] ) -> span< T, N >;

   template< typename T, std::size_t N >
   span( std::array< T, N >& ) -> span< T, N >;

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

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

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

}  // namespace tao

namespace std
{
#if defined( __clang__ )
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmismatched-tags"
#endif

   template< typename ElementType, size_t Extent >
   struct tuple_size< tao::span< ElementType, Extent > >
      : integral_constant< size_t, Extent >
   {};

   template< typename ElementType >
   struct tuple_size< tao::span< ElementType, tao::dynamic_extent > >;  // not defined

   template< size_t I, typename ElementType, size_t Extent >
   struct tuple_element< I, tao::span< ElementType, Extent > >
   {
      static_assert( ( Extent != tao::dynamic_extent ) && ( I < Extent ) );
      using type = ElementType;
   };

#if defined( __clang__ )
#pragma clang diagnostic pop
#endif

   // TODO: this is probably illegal. keep it?
   template< size_t I, typename ElementType, size_t Extent >
   constexpr auto get( tao::span< ElementType, Extent > s ) noexcept -> ElementType&
   {
      static_assert( ( Extent != tao::dynamic_extent ) && ( I < Extent ) );
      return s[ I ];
   }

}  // namespace std

#endif

#endif
