///\file

/******************************************************************************
The MIT License(MIT)

Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com

Copyright(c) 2022 John Wellbelove

Inspired by the techniques used in https://github.com/mrshurik/poly_span
Copyright(c) 2020 Dr. Alexander Bulovyatov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/

#ifndef ETL_POLY_SPAN_INCLUDED
#define ETL_POLY_SPAN_INCLUDED

#include "platform.h"
#include "array.h"
#include "hash.h"
#include "integral_limits.h"
#include "iterator.h"
#include "memory.h"
#include "nullptr.h"
#include "type_traits.h"

#include "private/dynamic_extent.h"

#if ETL_USING_STL && ETL_USING_CPP11
  #include <array>
#endif

///\defgroup poly_span poly_span
///\ingroup containers

namespace etl
{
  template <typename U, size_t Extent>
  class poly_span;

  namespace private_poly_span
  {
    //*************************************************************************
    // Iterator
    //*************************************************************************
    template <typename TBase>
    class iterator
    {
    public:

      template <typename U, size_t Extent>
      friend class etl::poly_span;

      template <typename UBase>
      friend class const_iterator;

      typedef TBase                                  value_type;
      typedef ptrdiff_t                              difference_type;
      typedef TBase*                                 pointer;
      typedef TBase&                                 reference;
      typedef ETL_OR_STD::random_access_iterator_tag iterator_category;

      //*****************************************
      iterator()
        : ptr(ETL_NULLPTR)
        , element_size(0U)
      {
      }

      //*****************************************
      iterator(const iterator& other)
        : ptr(other.ptr)
        , element_size(other.element_size)
      {
      }

      //*****************************************
      iterator& operator=(const iterator& rhs)
      {
        ptr          = rhs.ptr;
        element_size = rhs.element_size;

        return *this;
      }

      //*****************************************
      TBase& operator*() const
      {
        return *ptr;
      }

      //*****************************************
      TBase* operator->() const
      {
        return ptr;
      }

      //*****************************************
      iterator& operator++()
      {
        ptr = reinterpret_cast<pointer>(reinterpret_cast<char*>(ptr) + element_size);
        return *this;
      }

      //*****************************************
      iterator operator++(int)
      {
        iterator temp(*this);
        ptr = reinterpret_cast<pointer>(reinterpret_cast<char*>(ptr) + element_size);
        return temp;
      }

      //*****************************************
      iterator& operator--()
      {
        ptr = reinterpret_cast<pointer>(reinterpret_cast<char*>(ptr) - element_size);
        return *this;
      }

      //*****************************************
      iterator operator--(int)
      {
        iterator temp(*this);
        ptr = reinterpret_cast<pointer>(reinterpret_cast<char*>(ptr) - element_size);
        return temp;
      }

      //***************************************************
      iterator& operator+=(difference_type offset)
      {
        ptr = reinterpret_cast<pointer>(reinterpret_cast<char*>(ptr) + (offset * difference_type(element_size)));
        return *this;
      }

      //***************************************************
      iterator& operator-=(difference_type offset)
      {
        ptr = reinterpret_cast<pointer>(reinterpret_cast<char*>(ptr) - (offset * difference_type(element_size)));
        return *this;
      }

      //***************************************************
      friend bool operator==(const iterator& lhs, const iterator& rhs)
      {
        return (lhs.ptr == rhs.ptr) && (lhs.element_size == rhs.element_size);
      }

      //***************************************************
      friend bool operator!=(const iterator& lhs, const iterator& rhs)
      {
        return !(lhs == rhs);
      }

      //***************************************************
      friend bool operator<(const iterator& lhs, const iterator& rhs)
      {
        return lhs.ptr < rhs.ptr;
      }

      //***************************************************
      friend bool operator<=(const iterator& lhs, const iterator& rhs)
      {
        return !(lhs > rhs);
      }

      //***************************************************
      friend bool operator>(const iterator& lhs, const iterator& rhs)
      {
        return (rhs < lhs);
      }

      //***************************************************
      friend bool operator>=(const iterator& lhs, const iterator& rhs)
      {
        return !(lhs < rhs);
      }

      //***************************************************
      friend iterator operator+(const iterator& lhs, difference_type offset)
      {
        iterator temp(lhs);
        temp += offset;
        return temp;
      }

      //***************************************************
      friend iterator operator+(difference_type offset, const iterator& rhs)
      {
        iterator temp(rhs);
        temp += offset;
        return temp;
      }

      //***************************************************
      friend iterator operator-(const iterator& lhs, difference_type offset)
      {
        iterator temp(lhs);
        temp -= offset;
        return temp;
      }

      //***************************************************
      friend iterator operator-(difference_type offset, const iterator& rhs)
      {
        iterator temp(rhs);
        temp -= offset;
        return temp;
      }

      //***************************************************
      friend difference_type operator-(const iterator& lhs, const iterator& rhs)
      {
        return lhs.ptr - rhs.ptr;
      }

    private:

      //***************************************************
      iterator(TBase* pbegin_, size_t index_, size_t element_size_)
        : element_size(element_size_)
      {
        ptr = reinterpret_cast<pointer>(reinterpret_cast<char*>(pbegin_) + (index_ * element_size));
      }

      TBase* ptr;
      size_t element_size;
    };
  } // namespace private_poly_span

  //***************************************************************************
  /// Poly Span - Fixed Extent
  //***************************************************************************
  template <typename TBase, size_t Extent = etl::dynamic_extent>
  class poly_span
  {
  public:

    typedef TBase                                element_type;
    typedef typename etl::remove_cv<TBase>::type value_type;
    typedef size_t                               size_type;
    typedef TBase&                               reference;
    typedef const TBase&                         const_reference;
    typedef TBase*                               pointer;
    typedef const TBase*                         const_pointer;
    typedef private_poly_span::iterator<TBase>   iterator;

    typedef ETL_OR_STD::reverse_iterator<iterator> reverse_iterator;

    static ETL_CONSTANT size_t extent = Extent;

    template <typename UBase, size_t UExtent>
    friend class poly_span;

    //*************************************************************************
    /// Default constructor.
    //*************************************************************************
    ETL_CONSTEXPR poly_span() ETL_NOEXCEPT
      : pbegin(ETL_NULLPTR)
      , element_size(0U)
    {
    }

    //*************************************************************************
    /// Construct from iterator + size
    //*************************************************************************
    template <typename TIterator, typename TSize>
    ETL_CONSTEXPR poly_span(const TIterator begin_, const TSize /*size_*/) ETL_NOEXCEPT
      : pbegin(etl::addressof(*begin_))
      , element_size(sizeof(typename etl::iterator_traits<TIterator>::value_type))
    {
      typedef typename etl::iterator_traits<TIterator>::value_type data_type;

      ETL_STATIC_ASSERT((etl::is_same< ETL_OR_STD::random_access_iterator_tag, typename etl::iterator_traits<TIterator>::iterator_category>::value),
                        "Not a random access iterator");
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, data_type>::value || etl::is_same<TBase, data_type>::value), "TBase not a base of the data type");
    }

    //*************************************************************************
    /// Construct from iterators
    //*************************************************************************
    template <typename TIterator>
    ETL_CONSTEXPR poly_span(const TIterator begin_, const TIterator /*end_*/)
      : pbegin(etl::addressof(*begin_))
      , element_size(sizeof(typename etl::iterator_traits<TIterator>::value_type))
    {
      typedef typename etl::iterator_traits<TIterator>::value_type        data_type;
      typedef typename etl::iterator_traits<TIterator>::iterator_category iterator_category;

      ETL_STATIC_ASSERT((etl::is_same<ETL_OR_STD::random_access_iterator_tag, iterator_category>::value), "Not a random access iterator");
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, data_type>::value || etl::is_same<TBase, data_type>::value), "TBase not a base of the data type");
    }

    //*************************************************************************
    /// Construct from C array
    //*************************************************************************
    template <typename U, size_t Size>
    ETL_CONSTEXPR poly_span(U (&begin_)[Size]) ETL_NOEXCEPT
      : pbegin(begin_)
      , element_size(sizeof(U))
    {
      ETL_STATIC_ASSERT(Size <= Extent, "Array data overflow");
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, U>::value || etl::is_same<TBase, U>::value), "TBase not a base of the data type");
    }

    //*************************************************************************
    /// Construct from etl::array.
    //*************************************************************************
    template <typename U, size_t Size>
    ETL_CONSTEXPR poly_span(etl::array<U, Size>& a) ETL_NOEXCEPT
      : pbegin(a.data())
      , element_size(sizeof(U))
    {
      ETL_STATIC_ASSERT(Size <= Extent, "Array data overflow");
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, U>::value || etl::is_same<TBase, U>::value), "TBase not a base of the data type");
    }

    //*************************************************************************
    /// Construct from etl::array.
    //*************************************************************************
    template <typename U, size_t Size>
    ETL_CONSTEXPR poly_span(const etl::array<U, Size>& a) ETL_NOEXCEPT
      : pbegin(a.data())
      , element_size(sizeof(U))
    {
      ETL_STATIC_ASSERT(Size <= Extent, "Array data overflow");
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, U>::value || etl::is_same<TBase, U>::value), "TBase not a base of the data type");
    }

#if ETL_USING_STL && ETL_USING_CPP11
    //*************************************************************************
    /// Construct from std::array.
    //*************************************************************************
    template <typename U, size_t Size>
    ETL_CONSTEXPR poly_span(std::array<U, Size>& a) ETL_NOEXCEPT
      : pbegin(a.data())
      , element_size(sizeof(U))
    {
      ETL_STATIC_ASSERT(Size <= Extent, "Array data overflow");
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, U>::value || etl::is_same<TBase, U>::value), "TBase not a base of U");
    }

    //*************************************************************************
    /// Construct from std::array.
    //*************************************************************************
    template <typename U, size_t Size>
    ETL_CONSTEXPR poly_span(const std::array<U, Size>& a) ETL_NOEXCEPT
      : pbegin(a.data())
      , element_size(sizeof(U))
    {
      ETL_STATIC_ASSERT(Size <= Extent, "Array data overflow");
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, U>::value || etl::is_same<TBase, U>::value), "TBase not a base of U");
    }
#endif

    //*************************************************************************
    /// Copy constructor
    //*************************************************************************
    ETL_CONSTEXPR poly_span(const poly_span<TBase, Extent>& other) ETL_NOEXCEPT
      : pbegin(other.pbegin)
      , element_size(other.element_size)
    {
    }

    //*************************************************************************
    /// Copy constructor
    //*************************************************************************
    template <typename UBase>
    ETL_CONSTEXPR poly_span(const poly_span<UBase, Extent>& other) ETL_NOEXCEPT
      : pbegin(other.pbegin)
      , element_size(other.element_size)
    {
    }

    //*************************************************************************
    /// Returns a reference to the first element.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR reference front() const ETL_NOEXCEPT
    {
      return *pbegin;
    }

    //*************************************************************************
    /// Returns a reference to the last element.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR reference back() const ETL_NOEXCEPT
    {
      return *element_at(Extent - 1U);
    }

    //*************************************************************************
    /// Returns a pointer to the first element of the internal storage.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR pointer data() const ETL_NOEXCEPT
    {
      return pbegin;
    }

    //*************************************************************************
    /// Returns an iterator to the beginning of the poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR iterator begin() const ETL_NOEXCEPT
    {
      return iterator(pbegin, 0U, element_size);
    }

    //*************************************************************************
    /// Returns an iterator to the end of the poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR iterator end() const ETL_NOEXCEPT
    {
      return iterator(pbegin, Extent, element_size);
    }

    //*************************************************************************
    // Returns an reverse iterator to the reverse beginning of the poly_span.
    //*************************************************************************
    ETL_CONSTEXPR reverse_iterator rbegin() const ETL_NOEXCEPT
    {
      return reverse_iterator(end());
    }

    //*************************************************************************
    /// Returns a reverse iterator to the end of the poly_span.
    //*************************************************************************
    ETL_CONSTEXPR reverse_iterator rend() const ETL_NOEXCEPT
    {
      return reverse_iterator(begin());
    }

    //*************************************************************************
    /// Returns <b>true</b> if the poly_span size is zero.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR bool empty() const ETL_NOEXCEPT
    {
      return (Extent == 0U);
    }

    //*************************************************************************
    /// Returns the size of the poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR size_t size() const ETL_NOEXCEPT
    {
      return Extent;
    }

    //*************************************************************************
    /// Returns the size of the type stored in the poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR size_t size_of_element() const ETL_NOEXCEPT
    {
      return element_size;
    }

    //*************************************************************************
    /// Returns the size of the poly_span in bytes.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR size_t size_bytes() const ETL_NOEXCEPT
    {
      return Extent * element_size;
    }

    //*************************************************************************
    /// Assign from a poly_span.
    //*************************************************************************
    ETL_CONSTEXPR14 poly_span& operator=(const poly_span& other) ETL_NOEXCEPT
    {
      pbegin       = other.pbegin;
      element_size = other.element_size;
      return *this;
    }

    //*************************************************************************
    /// Assign from a poly_span.
    //*************************************************************************
    template <typename UBase>
    ETL_CONSTEXPR14 poly_span& operator=(const poly_span<UBase, Extent>& other) ETL_NOEXCEPT
    {
      pbegin       = other.pbegin;
      element_size = other.element_size;
      return *this;
    }

    //*************************************************************************
    /// Returns a reference to the indexed value.
    //*************************************************************************
    ETL_CONSTEXPR reference operator[](size_t i) const
    {
      return *element_at(i);
    }

    //*************************************************************************
    /// Obtains a poly_span that is a view over the first COUNT elements of this
    /// poly_span.
    //*************************************************************************
    template <size_t COUNT>
    ETL_NODISCARD ETL_CONSTEXPR etl::poly_span<element_type, COUNT> first() const ETL_NOEXCEPT
    {
      return etl::poly_span<element_type, COUNT>(pbegin, 0U, COUNT, element_size);
    }

    //*************************************************************************
    /// Obtains a poly_span that is a view over the first count elements of this
    /// poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR etl::poly_span<element_type, etl::dynamic_extent> first(size_t count) const
    {
      return etl::poly_span<element_type, etl::dynamic_extent>(pbegin, 0U, count, element_size);
    }

    //*************************************************************************
    /// Obtains a poly_span that is a view over the last COUNT elements of this
    /// poly_span.
    //*************************************************************************
    template <size_t COUNT>
    ETL_NODISCARD ETL_CONSTEXPR etl::poly_span<element_type, COUNT> last() const ETL_NOEXCEPT
    {
      return etl::poly_span<element_type, COUNT>(pbegin, Extent - COUNT, COUNT, element_size);
    }

    //*************************************************************************
    /// Obtains a poly_span that is a view over the last count elements of this
    /// poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR etl::poly_span<element_type, etl::dynamic_extent> last(size_t count) const ETL_NOEXCEPT
    {
      return etl::poly_span<element_type, etl::dynamic_extent>(pbegin, Extent - count, count, element_size);
    }

#if ETL_USING_CPP11
    //*************************************************************************
    /// Obtains a poly_span that is a view from OFFSET over the next COUNT
    /// elements of this poly_span.
    //*************************************************************************
    template <size_t OFFSET, size_t COUNT = etl::dynamic_extent>
    ETL_NODISCARD ETL_CONSTEXPR etl::poly_span< element_type, COUNT != etl::dynamic_extent ? COUNT : Extent - OFFSET> subspan() const ETL_NOEXCEPT
    {
      return (COUNT == etl::dynamic_extent)                                                                  ? etl::poly_span < element_type,
             COUNT != etl::dynamic_extent ? COUNT : Extent - OFFSET > (pbegin, OFFSET, Extent, element_size) : etl::poly_span < element_type,
             COUNT != etl::dynamic_extent ? COUNT : Extent - OFFSET > (pbegin, OFFSET, COUNT, element_size);
    }
#else
    //*************************************************************************
    /// Obtains a poly_span that is a view from OFFSET over the next COUNT
    /// elements of this poly_span.
    //*************************************************************************
    template <size_t OFFSET, size_t COUNT>
    etl::poly_span<element_type, COUNT != etl::dynamic_extent ? COUNT : Extent - OFFSET> subspan() const
    {
      if (COUNT == etl::dynamic_extent)
      {
        return etl::poly_span < element_type, COUNT != etl::dynamic_extent ? COUNT : Extent - OFFSET > (pbegin, OFFSET, Extent, element_size);
      }
      else
      {
        return etl::poly_span < element_type, COUNT != etl::dynamic_extent ? COUNT : Extent - OFFSET > (pbegin, OFFSET, element_size);
      }
    }
#endif

    //*************************************************************************
    /// Obtains a poly_span that is a view from 'offset' over the next 'count'
    /// elements of this poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR etl::poly_span<element_type, etl::dynamic_extent> subspan(size_t offset, size_t count = etl::dynamic_extent) const
      ETL_NOEXCEPT
    {
      return (count == etl::dynamic_extent) ? etl::poly_span<element_type, etl::dynamic_extent>(pbegin, offset, Extent, element_size)
                                            : etl::poly_span<element_type, etl::dynamic_extent>(pbegin, offset, count, element_size);
    }

  protected:

    //***************************************************************************
    template <typename TFrom>
    struct char_ptr_type
    {
      typedef typename etl::conditional<etl::is_const<TFrom>::value, const char*, char*>::type type;
    };

    typedef typename char_ptr_type<TBase>::type char_ptr_t;

    //***************************************************************************
    pointer element_at(size_t index) const ETL_NOEXCEPT
    {
      char_ptr_t base = reinterpret_cast<char_ptr_t>(pbegin);
      return reinterpret_cast<pointer>(base + (index * element_size));
    }

    //*************************************************************************
    /// Construct from iterator + offset + element size
    /// extent_ is ignored.
    //*************************************************************************
    poly_span(TBase* pbegin_, size_t offset_, size_t /*extent_*/, size_t element_size_) ETL_NOEXCEPT
      : pbegin(reinterpret_cast<pointer>(reinterpret_cast<char_ptr_t>(pbegin_) + (offset_ * element_size_)))
      , element_size(element_size_)
    {
    }

  private:

    pointer pbegin;
    size_t  element_size;
  };

  template <typename TBase, size_t Extent>
  ETL_CONSTANT size_t poly_span<TBase, Extent>::extent;

  //***************************************************************************
  /// Poly Span - Dynamic Extent
  //***************************************************************************
  template <typename TBase>
  class poly_span<TBase, etl::dynamic_extent>
  {
  public:

    typedef TBase                                   element_type;
    typedef typename etl::remove_cv<TBase>::type    value_type;
    typedef size_t                                  size_type;
    typedef TBase&                                  reference;
    typedef const TBase&                            const_reference;
    typedef TBase*                                  pointer;
    typedef const TBase*                            const_pointer;
    typedef etl::private_poly_span::iterator<TBase> iterator;
    typedef ETL_OR_STD::reverse_iterator<iterator>  reverse_iterator;

    static ETL_CONSTANT size_t extent = etl::dynamic_extent;

    template <typename UBase, size_t UExtent>
    friend class poly_span;

    //*************************************************************************
    /// Default constructor.
    //*************************************************************************
    ETL_CONSTEXPR poly_span() ETL_NOEXCEPT
      : pbegin(ETL_NULLPTR)
      , element_size(0U)
      , span_extent(0U)
    {
    }

    //*************************************************************************
    /// Construct from iterator + size
    //*************************************************************************
    template <typename TIterator, typename TSize>
    ETL_CONSTEXPR poly_span(const TIterator begin_, const TSize size_) ETL_NOEXCEPT
      : pbegin(etl::addressof(*begin_))
      , element_size(sizeof(typename etl::iterator_traits<TIterator>::value_type))
      , span_extent(size_)
    {
      typedef typename etl::iterator_traits<TIterator>::value_type        data_type;
      typedef typename etl::iterator_traits<TIterator>::iterator_category iterator_category;

      ETL_STATIC_ASSERT((etl::is_same<ETL_OR_STD::random_access_iterator_tag, iterator_category>::value), "Not a random access iterator");
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, data_type>::value || etl::is_same<TBase, data_type>::value), "TBase not a base of the data type");
    }

    //*************************************************************************
    /// Construct from iterators
    //*************************************************************************
    template <typename TIterator>
    ETL_CONSTEXPR poly_span(const TIterator begin_, const TIterator end_)
      : pbegin(etl::addressof(*begin_))
      , element_size(sizeof(typename etl::iterator_traits<TIterator>::value_type))
      , span_extent(size_t(etl::distance(begin_, end_)))
    {
      typedef typename etl::iterator_traits<TIterator>::value_type        data_type;
      typedef typename etl::iterator_traits<TIterator>::iterator_category iterator_category;

      ETL_STATIC_ASSERT((etl::is_same<ETL_OR_STD::random_access_iterator_tag, iterator_category>::value), "Not a random access iterator");
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, data_type>::value || etl::is_same<TBase, data_type>::value), "TBase not a base of the data type");
    }

    //*************************************************************************
    /// Construct from C array
    //*************************************************************************
    template <typename U, size_t Size>
    ETL_CONSTEXPR poly_span(U (&begin_)[Size]) ETL_NOEXCEPT
      : pbegin(begin_)
      , element_size(sizeof(U))
      , span_extent(Size)
    {
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, U>::value || etl::is_same<TBase, U>::value), "TBase not a base of the data type");
    }

    //*************************************************************************
    /// Construct from etl::array.
    //*************************************************************************
    template <typename U, size_t Size>
    ETL_CONSTEXPR poly_span(etl::array<U, Size>& a) ETL_NOEXCEPT
      : pbegin(a.data())
      , element_size(sizeof(U))
      , span_extent(Size)
    {
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, U>::value || etl::is_same<TBase, U>::value), "TBase not a base of the data type");
    }

    //*************************************************************************
    /// Construct from etl::array.
    //*************************************************************************
    template <typename U, size_t Size>
    ETL_CONSTEXPR poly_span(const etl::array<U, Size>& a) ETL_NOEXCEPT
      : pbegin(a.data())
      , element_size(sizeof(U))
      , span_extent(Size)
    {
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, U>::value || etl::is_same<TBase, U>::value), "TBase not a base of the data type");
    }

#if ETL_USING_STL && ETL_USING_CPP11
    //*************************************************************************
    /// Construct from std::array.
    //*************************************************************************
    template <typename U, size_t Size>
    ETL_CONSTEXPR poly_span(std::array<U, Size>& a) ETL_NOEXCEPT
      : pbegin(a.data())
      , element_size(sizeof(U))
      , span_extent(Size)
    {
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, U>::value || etl::is_same<TBase, U>::value), "TBase not a base of U");
    }

    //*************************************************************************
    /// Construct from std::array.
    //*************************************************************************
    template <typename U, size_t Size>
    ETL_CONSTEXPR poly_span(const std::array<U, Size>& a) ETL_NOEXCEPT
      : pbegin(a.data())
      , element_size(sizeof(U))
      , span_extent(Size)
    {
      ETL_STATIC_ASSERT((etl::is_base_of<TBase, U>::value || etl::is_same<TBase, U>::value), "TBase not a base of U");
    }
#endif

    //*************************************************************************
    /// Copy constructor
    //*************************************************************************
    ETL_CONSTEXPR poly_span(const poly_span<TBase, etl::dynamic_extent>& other) ETL_NOEXCEPT
      : pbegin(other.pbegin)
      , element_size(other.element_size)
      , span_extent(other.span_extent)
    {
    }

    //*************************************************************************
    /// Copy constructor
    //*************************************************************************
    template <typename UBase>
    ETL_CONSTEXPR poly_span(const poly_span<UBase, etl::dynamic_extent>& other) ETL_NOEXCEPT
      : pbegin(other.pbegin)
      , element_size(other.element_size)
      , span_extent(other.span_extent)
    {
    }

    //*************************************************************************
    /// Returns a reference to the first element.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR reference front() const ETL_NOEXCEPT
    {
      return *pbegin;
    }

    //*************************************************************************
    /// Returns a reference to the last element.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR reference back() const ETL_NOEXCEPT
    {
      return *element_at(span_extent - 1U);
    }

    //*************************************************************************
    /// Returns a pointer to the first element of the internal storage.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR pointer data() const ETL_NOEXCEPT
    {
      return pbegin;
    }

    //*************************************************************************
    /// Returns an iterator to the beginning of the poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR iterator begin() const ETL_NOEXCEPT
    {
      return iterator(pbegin, 0U, element_size);
    }

    //*************************************************************************
    /// Returns an iterator to the end of the poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR iterator end() const ETL_NOEXCEPT
    {
      return iterator(pbegin, span_extent, element_size);
    }

    //*************************************************************************
    // Returns an reverse iterator to the reverse beginning of the poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR reverse_iterator rbegin() const ETL_NOEXCEPT
    {
      return reverse_iterator(end());
    }

    //*************************************************************************
    /// Returns a reverse iterator to the end of the poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR reverse_iterator rend() const ETL_NOEXCEPT
    {
      return reverse_iterator(begin());
    }

    //*************************************************************************
    /// Returns <b>true</b> if the poly_span size is zero.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR bool empty() const ETL_NOEXCEPT
    {
      return (span_extent == 0);
    }

    //*************************************************************************
    /// Returns the size of the poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR size_t size() const ETL_NOEXCEPT
    {
      return span_extent;
    }

    //*************************************************************************
    /// Returns the size of the type stored in the poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR size_t size_of_element() const ETL_NOEXCEPT
    {
      return element_size;
    }

    //*************************************************************************
    /// Returns the size of the poly_span in bytes.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR size_t size_bytes() const ETL_NOEXCEPT
    {
      return element_size * span_extent;
    }

    //*************************************************************************
    /// Assign from a poly_span.
    //*************************************************************************
    ETL_CONSTEXPR14 poly_span& operator=(const poly_span& other) ETL_NOEXCEPT
    {
      pbegin       = other.pbegin;
      element_size = other.element_size;
      span_extent  = other.span_extent;
      return *this;
    }

    //*************************************************************************
    /// Assign from a poly_span.
    //*************************************************************************
    template <typename UBase>
    ETL_CONSTEXPR14 poly_span& operator=(const poly_span<UBase, etl::dynamic_extent>& other) ETL_NOEXCEPT
    {
      pbegin       = other.pbegin;
      element_size = other.element_size;
      span_extent  = other.span_extent;
      return *this;
    }

    //*************************************************************************
    /// Returns a reference to the indexed value.
    //*************************************************************************
    ETL_CONSTEXPR reference operator[](size_t i) const
    {
      return *element_at(i);
    }

    //*************************************************************************
    /// Obtains a poly_span that is a view over the first COUNT elements of this
    /// poly_span.
    //*************************************************************************
    template <size_t COUNT>
    ETL_NODISCARD ETL_CONSTEXPR etl::poly_span<element_type, COUNT> first() const ETL_NOEXCEPT
    {
      return etl::poly_span<element_type, COUNT>(pbegin, 0U, COUNT, element_size);
    }

    //*************************************************************************
    /// Obtains a poly_span that is a view over the first count elements of this
    /// poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR etl::poly_span<element_type, etl::dynamic_extent> first(size_t count) const ETL_NOEXCEPT
    {
      return etl::poly_span<element_type, etl::dynamic_extent>(pbegin, 0U, count, element_size);
    }

    //*************************************************************************
    /// Obtains a poly_span that is a view over the last COUNT elements of this
    /// poly_span.
    //*************************************************************************
    template <size_t COUNT>
    ETL_NODISCARD ETL_CONSTEXPR etl::poly_span<element_type, COUNT> last() const ETL_NOEXCEPT
    {
      return etl::poly_span<element_type, COUNT>(pbegin, span_extent - COUNT, COUNT, element_size);
    }

    //*************************************************************************
    /// Obtains a poly_span that is a view over the last count elements of this
    /// poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR etl::poly_span<element_type, etl::dynamic_extent> last(size_t count) const ETL_NOEXCEPT
    {
      return etl::poly_span<element_type, etl::dynamic_extent>(pbegin, span_extent - count, count, element_size);
    }

#if ETL_USING_CPP11
    //*************************************************************************
    /// Obtains a poly_span that is a view from OFFSET over the next COUNT
    /// elements of this poly_span.
    //*************************************************************************
    template <size_t OFFSET, size_t COUNT = etl::dynamic_extent>
    ETL_NODISCARD ETL_CONSTEXPR etl::poly_span< element_type, COUNT != etl::dynamic_extent ? COUNT : etl::dynamic_extent> subspan() const ETL_NOEXCEPT
    {
      return (COUNT == etl::dynamic_extent)                                                                           ? etl::poly_span < element_type,
             COUNT != etl::dynamic_extent ? COUNT : etl::dynamic_extent > (pbegin, OFFSET, span_extent, element_size) : etl::poly_span < element_type,
             COUNT != etl::dynamic_extent ? COUNT : etl::dynamic_extent > (pbegin, OFFSET, COUNT, element_size);
    }
#else
    //*************************************************************************
    /// Obtains a poly_span that is a view from OFFSET over the next COUNT
    /// elements of this poly_span.
    //*************************************************************************
    template <size_t OFFSET, size_t COUNT>
    etl::poly_span<element_type, COUNT != etl::dynamic_extent ? COUNT : etl::dynamic_extent> subspan() const
    {
      if (COUNT == etl::dynamic_extent)
      {
        return etl::poly_span < element_type,
               COUNT != etl::dynamic_extent ? COUNT : etl::dynamic_extent > (pbegin, OFFSET, span_extent, element_size);
      }
      else
      {
        return etl::poly_span < element_type,
               COUNT != etl::dynamic_extent ? COUNT : etl::dynamic_extent > (pbegin, OFFSET, span_extent, element_size);
      }
    }
#endif

    //*************************************************************************
    /// Obtains a poly_span that is a view from 'offset' over the next 'count'
    /// elements of this poly_span.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR etl::poly_span<element_type, etl::dynamic_extent> subspan(size_t offset, size_t count = etl::dynamic_extent) const
      ETL_NOEXCEPT
    {
      return (count == etl::dynamic_extent) ? etl::poly_span<element_type, etl::dynamic_extent>(pbegin, offset, span_extent - offset, element_size)
                                            : etl::poly_span<element_type, etl::dynamic_extent>(pbegin, offset, count, element_size);
    }

  protected:

    //*************************************************************************
    /// Construct from iterator + offset + size + element size
    //*************************************************************************
    poly_span(TBase* pbegin_, size_t offset_, size_t extent_, size_t element_size_) ETL_NOEXCEPT
      : pbegin(reinterpret_cast<pointer>(reinterpret_cast<char_ptr_t>(pbegin_) + (offset_ * element_size_)))
      , element_size(element_size_)
      , span_extent(extent_)
    {
    }

  private:

    //***************************************************************************
    template <typename TFrom>
    struct char_ptr_type
    {
      typedef typename etl::conditional<etl::is_const<TFrom>::value, const char*, char*>::type type;
    };

    typedef typename char_ptr_type<TBase>::type char_ptr_t;

    //***************************************************************************
    pointer element_at(size_t index) const ETL_NOEXCEPT
    {
      char_ptr_t base = reinterpret_cast<char_ptr_t>(pbegin);
      return reinterpret_cast<pointer>(base + (index * element_size));
    }

    pointer pbegin;
    size_t  element_size;
    size_t  span_extent;
  };

  template <typename TBase>
  ETL_CONSTANT size_t poly_span<TBase, etl::dynamic_extent>::extent;

  //*************************************************************************
  /// Template deduction guides.
  //*************************************************************************
#if ETL_USING_CPP17
  template <typename TIterator>
  poly_span(const TIterator begin_, const TIterator end_) -> poly_span<etl::remove_pointer_t<TIterator>, etl::dynamic_extent>;

  template <typename TIterator, typename TSize>
  poly_span(const TIterator begin_, const TSize size_) -> poly_span<etl::remove_pointer_t<TIterator>, etl::dynamic_extent>;

  template <typename T, size_t Size>
  poly_span(T (&)[Size]) -> poly_span<T, Size>;

  template <typename T, size_t Size>
  poly_span(etl::array<T, Size>&) -> poly_span<T, Size>;

  template <typename T, size_t Size>
  poly_span(const etl::array<T, Size>&) -> poly_span<const T, Size>;

  #if ETL_USING_STL
  template <typename T, size_t Size>
  poly_span(std::array<T, Size>&) -> poly_span<T, Size>;

  template <typename T, size_t Size>
  poly_span(const std::array<T, Size>&) -> poly_span<const T, Size>;
  #endif
#endif

  //*************************************************************************
  /// Hash function.
  //*************************************************************************
#if ETL_USING_8BIT_TYPES
  template <typename TBase, size_t Extent>
  struct hash<etl::poly_span<TBase, Extent> >
  {
    size_t operator()(const etl::poly_span<TBase, Extent>& view) const
    {
      return etl::private_hash::generic_hash<size_t>(reinterpret_cast<const uint8_t*>(view.data()),
                                                     reinterpret_cast<const uint8_t*>(view.data() + view.size()));
    }
  };
#endif
} // namespace etl

#endif
