/*!
 * \file    utl/containers/array.h
 * \brief   An std::array equivalent implementation
 *
 * Copyright (C) 2018 Christos Choutouridis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#ifndef __utl_container_array_h__
#define __utl_container_array_h__

#include <utl/core/impl.h>
#include <algorithm>

namespace utl {

   /*!
    * \ingroup containers
    * \brief
    *    array container traits helper
    */
   //! @{
   template<typename _Tp, size_t _Nm>
   struct array_traits {
      typedef _Tp type[_Nm];

      static constexpr _Tp& Ref (const type& t, size_t n) noexcept {
         return const_cast<_Tp&>(t[n]);
      }

      static constexpr _Tp* Ptr(const type& t) noexcept {
         return const_cast<_Tp*>(t);
      }
   };

   template<typename _Tp>
   struct array_traits<_Tp, 0> {
      struct type { };

      static constexpr _Tp& Ref(const type&, size_t) noexcept {
         return *static_cast<_Tp*>(nullptr);
      }

      static constexpr _Tp* Ptr(const type&) noexcept {
         return nullptr;
      }
   };
   //! @}

   /*!
    * \ingroup containers
    * \brief
    *    A standard container for storing a fixed size sequence of elements.
    *
    * Meets the requirements of:
    *    <a href="tables.html#65">container</a>,
    *    <a href="tables.html#66">reversible container</a>,
    *    <a href="tables.html#67">sequence</a>.
    * Sets support random access iterators.
    *
    * \tparam  _Tp  type of element. Required to be a complete type.
    * \tparam  _Nm  Number of elements.
    */
   template <typename _Tp, size_t _Nm>
   struct array {
      using value_type        = _Tp;
      using pointer           = value_type*;
      using const_pointer     = const value_type*;
      using reference         = value_type&;
      using const_reference   = const value_type&;
      using iterator          = value_type*;
      using const_iterator    = const value_type*;
      using size_type         = size_t;
      using difference_type   = ptrdiff_t;
      using reverse_iterator  = std::reverse_iterator <iterator>;
      using const_reverse_iterator
                              = std::reverse_iterator <const_iterator>;

      // type and data
      //using empty_t              = array_traits<_Tp, 0>;
      using array_t              = array_traits<_Tp, _Nm>;
      typename array_t::type    _data;

      // No explicit construct/copy/destroy for aggregate type.

      // DR 776.
      void fill (const value_type& v) { std::fill_n (begin(), size(), v); }

      void swap (array& other) noexcept {
         std::swap_ranges (begin(), end(), other.begin());
      }

   //! \name Iterators.
   //!@{
            iterator begin()        noexcept { return iterator (data()); }
      const_iterator begin()  const noexcept { return const_iterator (data()); }
            iterator end()          noexcept { return iterator (data() + _Nm); }
      const_iterator end()    const noexcept { return const_iterator (data() + _Nm); }
      const_iterator cbegin() const noexcept { return const_iterator (data()); }
      const_iterator cend()   const noexcept { return const_iterator (data() + _Nm); }

            reverse_iterator rbegin()        noexcept { return reverse_iterator (end()); }
            reverse_iterator rend()          noexcept { return reverse_iterator (begin()); }
      const_reverse_iterator rbegin()  const noexcept { return const_reverse_iterator (end()); }
      const_reverse_iterator rend()    const noexcept { return const_reverse_iterator (begin()); }
      const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator (end()); }
      const_reverse_iterator crend()   const noexcept { return const_reverse_iterator (begin()); }
   //!@{
   //! \name Capacity.
   //!@{
      constexpr size_type size()     const noexcept { return _Nm; }
      constexpr size_type max_size() const noexcept { return _Nm; }
      constexpr      bool empty()    const noexcept { return size() == 0; }
   //!@{

   //! \name Element access.
   //!@{

      //! Operator []
      reference operator[] (size_type n) noexcept {
         return array_t::Ref(_data, n);
      }
      //! Operator [] for const
      constexpr const_reference  operator[](size_type n) const noexcept {
         return array_t::Ref(_data, n);
      }

      /*!
       * Boundary check dereference operator.
       * If out of bounds, abort (for now)
       */
      reference at (size_type n) noexcept {
         if (n < _Nm)
            return array_t::Ref(_data, n);
         else
            abort ();   //XXX: Throw here
      }
      //! Compile time boundary check dereference operator.
      constexpr const_reference at (size_type n) const noexcept {
         static_assert ((n < _Nm), "array::at: out of range");
         return array_t::Ref(_data, n);
      }
      // first item
      reference front () noexcept {
         return *begin ();
      }
      constexpr const_reference front() const noexcept {
         return array_t::Ref(_data, 0);
      }

      // Last item
      reference back () noexcept {
         return _Nm ? *(end() - 1) : *end();
      }
      constexpr const_reference back () const noexcept {
         return _Nm ? array_t::Ref(_data, _Nm - 1)
                    : array_t::Ref(_data, 0);
      }

      // Pointer to data
            pointer data ()       noexcept { return array_t::Ptr(_data); }
      const_pointer data () const noexcept { return array_t::Ptr(_data); }
   //!<@}
   };

   //! \name Array comparisons.
   //!@{
   template<typename _Tp, size_t _Nm>
   inline bool operator== (const array<_Tp, _Nm>& lhs, const array<_Tp, _Nm>& rhs) {
      return std::equal (lhs.begin(), lhs.end(), rhs.begin());
   }

   template<typename _Tp, size_t _Nm>
   inline bool operator!= (const array<_Tp, _Nm>& lhs, const array<_Tp, _Nm>& rhs) {
      return !(lhs == rhs);
   }

   template<typename _Tp, size_t _Nm>
   inline bool operator< (const array<_Tp, _Nm>& lhs, const array<_Tp, _Nm>& rhs) {
      return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
   }

   template<typename _Tp, size_t _Nm>
   inline bool operator> (const array<_Tp, _Nm>& lhs, const array<_Tp, _Nm>& rhs) {
      return rhs < lhs;
   }

   template<typename _Tp, size_t _Nm>
   inline bool operator<= (const array<_Tp, _Nm>& lhs, const array<_Tp, _Nm>& rhs) {
      return !(lhs > rhs);
   }

   template<typename _Tp, size_t _Nm>
   inline bool
   operator>= (const array<_Tp, _Nm>& lhs, const array<_Tp, _Nm>& rhs) {
      return !(lhs < rhs);
   }
   //!@}

   // Specialized algorithms.
   template<typename _Tp, size_t _Nm>
   inline void swap (array<_Tp, _Nm>& lhs, array<_Tp, _Nm>& rhs)
      noexcept (noexcept (lhs.swap(rhs))) {
      lhs.swap (rhs);
   }

   template<size_t _Int, typename _Tp, size_t _Nm>
   constexpr _Tp& get (array<_Tp, _Nm>& arr) noexcept {
      static_assert(_Int < _Nm, "Index is out of bounds");
      return array_traits<_Tp, _Nm>::Ref(arr._data, _Int);
    }

   template<size_t _Int, typename _Tp, size_t _Nm>
   constexpr _Tp&& get (array<_Tp, _Nm>&& arr) noexcept {
      static_assert(_Int < _Nm, "Index is out of bounds");
      return std::move(std::get<_Int>(arr));
   }

   template<size_t _Int, typename _Tp, size_t _Nm>
   constexpr const _Tp& get (const array<_Tp, _Nm>& arr) noexcept {
      static_assert(_Int < _Nm, "Index is out of bounds");
      return array_traits<_Tp, _Nm>::Ref(arr._data, _Int);
   }

} // namespace utl


#endif /* __utl_continer_array_h__ */
