/*!
 * \file    utl/dev/idx_dev.h
 * \brief   Abstract base class implementations for indexed
 *          devices interface of utl
 *
 * 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_dev_idx_dev_h__
#define __utl_dev_idx_dev_h__

#include <utl/core/impl.h>
#include <utl/core/crtp.h>
#include <utl/dev/dev_iterators.h>
#include <utl/meta/meta.h>

namespace utl {

/*!
 * \ingroup Device Interface
 * \brief   Abstract base class for indexed devices
 */
//!@{

   /*!
    * \brief
    *    Template base class for indexed (I/O) devices. using CRTP
    *
    * This class force a common interface for input, indexed (I/O) devices.
    * By using this common interface the class implements
    * - Stream-like extracting and insertion operator
    * - Helper operators
    * - Input iterator
    * - Const input iterator
    * to inherit to implementation.
    *
    * \param   impl_t         The CRTP type (the derived/implementation class typename).
    * \param   data_t         The devices base type of data
    * \param   idx_t          The type to use for indexing
    * \param   streamsize     The number of elements to indicate eos.
    */
   template <typename impl_t, typename data_t, typename idx_t, size_t N>
   class idx_dev {
      _CRTP_IMPL(impl_t);
      using idx_dev_t = idx_dev <impl_t, data_t, idx_t, N>; //!< class type syntactic sugar

   //!@{ Export types as index device concept demands
   public:
      using data_type   = data_t;
      using pointer_type= data_t*;
      using idx_type    = idx_t;
   //!@}
      using type = idx_dev_t;    //!< Export type as identity meta-function

   /*!
    * \name Constructor / Destructor
    */
   //!@{
   protected:
      ~idx_dev () = default;                    //!< \brief Allow destructor from derived only
      idx_dev () = default;                     //!< \brief A default constructor from derived only
      idx_dev(const idx_dev_t&) = delete;                //!< No copies
      idx_dev_t& operator= (const idx_dev_t&) = delete;  //!< No copy assignments
   //!@}

   //! \name Common index device interface requirements
   //!@{
   private:
      size_t get_ (data_t& data, idx_t idx)           { return impl().get_(data, idx); }
      size_t get_ (data_t* data, size_t n, idx_t idx) { return impl().get_(data, n, idx); }
      size_t put_ (const data_t& data, idx_t idx)           { return impl().put_(data, idx); }
      size_t put_ (const data_t* data, size_t n, idx_t idx) { return impl().put_ (data, n, idx); }
      idx_t  cursor_ () const    { return impl().cursor_(); }
      void   cursor_ (idx_t idx) { impl().cursor_(idx); }
   //!@}

   /*!
    * \name Public index device interface
    */
   //!@{
   public:
      /*!
       * \brief
       *    Get interface. This function should read
       *    a single data_t object from device in blocking mode.
       * \param   data     Reference to data output from device.
       * \return  Number of data read from device
       * \note
       *    A successful call should return 1
       */
      size_t get (data_t& data, idx_t cursor) {
         return get_ (data, cursor);
      }

      /*!
       * \brief
       *    Old stile get functionality using free standing data_t*.
       *    This function should return a stream of data from device
       * \param data    Pointer to buffer to write the data from device.
       * \param n       The number of data of type data_t to read
       * \return        The read data items.
       */
      size_t get (data_t* data, size_t n, idx_t cursor) {
         return get_ (data, n, cursor);
      }

      /*!
       * \brief
       *    Put interface. This function should send a single
       *    data_t object to device.
       * \param   data  The data to send
       * \return  The number of transmitted data items
       * \note
       *    A successful call should return 1
       */
      size_t put (const data_t& data, idx_t cursor) {
         return put_ (data, cursor);
      }

      /*!
       * \brief
       *    Put interface. This function should send a stream of
       *    data_t objects to device.
       * \param data    Pointer to buffer indenting write to device.
       * \param n       The number of data of type data_t to send
       * \return        The number of transmitted items.
       */
      size_t put (const data_t* data, size_t n, idx_t cursor) {
          return put_ (data, n, cursor);
       }

      /*!
       * \brief
       *    Return the current cursor position
       */
      idx_t cursor () const { return cursor_(); }
      /*!
       * \brief
       *    Set the cursor position
       * \param idx  Cursor address to set
       * \return     The current cursor
       */
      idx_t cursor (idx_t idx) { return cursor_(idx); }
   //!@}

   /*!
    * \name  Stream operator >> interface
    */
   //!@{
      /*!
       * \brief
       *    Template operator>> implementation for for all by value/ref parameters
       * \param dst     Reference to destination
       * \return        Reference to this device for chaining
       */
      template <typename _Dst_t>
      idx_dev_t& operator>> (_Dst_t& dst) {
         static_assert ((sizeof (_Dst_t)%sizeof(data_t) == 0),
                        "Target size must be a integer multiple of device's data size");
         get_ (reinterpret_cast<data_t*>(&dst), sizeof(_Dst_t)/sizeof(data_t), cursor_());
         return *this;
      }
      //! Specialization to disallow pointer types as destination
      template <typename _Dst_t>
      idx_dev_t& operator>> (_Dst_t* dst) = delete;

      //! Overload for single data_t object
      idx_dev_t& operator>> (data_t& dst) {
         get_ (dst, cursor_());
         return *this;
      }
   //!@}

   /*!
    * \name  Stream operator<< interface
    */
   //!@{
      /*!
       * \brief
       *    Template operator<< implementation for for all by value/ref parameters
       * \param src     Reference to source data
       * \return        Reference to this device for chaining
       */
      template <typename _Src_t>
      idx_dev_t& operator<< (_Src_t& src) {
         static_assert ((sizeof (_Src_t)%sizeof(data_t) == 0),
                        "Source size must be a integer multiple of device's data size");
         put_ (reinterpret_cast<data_t*>(&src), sizeof (_Src_t)/sizeof(data_t), cursor_());
         return *this;
      }
      //! specialization to disallow pointer types as source
      template <typename _Src_t>
      idx_dev_t& operator<< (_Src_t* src) = delete;

      //! Overload for single data_t object
      idx_dev_t& operator<< (const data_t& src) {
         put_ (src, cursor_());
         return *this;
      }
   //!@}

   /*!
    * \name Helper operators
    */
   //!@{
      data_t& operator[] (const idx_t idx) {
         iterator it(this, idx);
         return *it;
      }
   //!@}

   /*!
    * \name STL-like Input iterator interface
    */
   //!@{
   public:
      using iterator       = idxdev_it <idx_dev_t, data_t*, N>;         //!< Iterator
      using const_iterator = idxdev_it <idx_dev_t, const data_t*, N>;   //!< Const iterator

      //!@{ .begin implementation
      iterator        begin ()       noexcept { return       iterator(this, iterator::beg); }
      const_iterator  begin () const noexcept { return const_iterator(this, iterator::beg); }
      const_iterator cbegin () const noexcept { return const_iterator(this, iterator::beg); }
      //!@}
      //!@{ .end implementation
      iterator        end ()         noexcept { return       iterator(this, iterator::eos); }
      const_iterator  end ()   const noexcept { return const_iterator(this, iterator::eos); }
      const_iterator cend ()   const noexcept { return const_iterator(this, iterator::eos); }
      //!@}
   //!@}
   };


   /*!
    * \brief
    *    A virtual base class specialization
    * \param   impl_t         = virtual_tag
    * \param   data_t         The devices base type of data
    * \param   idx_t          The type to use for indexing
    * \param   streamsize     The number of elements to indicate eos.
    */
   template <typename data_t, typename idx_t, size_t N>
   class idx_dev <virtual_tag, data_t, idx_t, N> {
      using idx_dev_t = idx_dev <virtual_tag, data_t, idx_t, N>; //!< class type syntactic sugar

   //!@{ Export types as index device concept demands
   public:
      using data_type   = data_t;
      using pointer_type= data_t*;
      using idx_type    = idx_t;
   //!@}
      using type = idx_dev_t; //!< Export type as identity meta-function

   /*!
    * \name Constructor / Destructor
    */
   //!@{
   public:
      virtual ~idx_dev () = default;             //!< \brief Virtual destructor
   protected:
      idx_dev () = default;                      //!< \brief A default constructor from derived only
      idx_dev(const idx_dev_t&) = delete;                //!< No copies
      idx_dev_t& operator= (const idx_dev_t&) = delete;  //!< No copy assignments
   //!@}

   //!@{ \name Common index device interface requirements
   private:
      virtual size_t get_ (data_t&, idx_t) =0;
      virtual size_t get_ (data_t*, size_t n, idx_t) =0;
      virtual size_t put_ (const data_t&, idx_t) =0;
      virtual size_t put_ (const data_t*, size_t n, idx_t) =0;
      virtual idx_t  cursor_ () const =0;
      virtual void   cursor_ (idx_t) =0;
   //!@}

   /*!
    * \name Public index device interface
    */
   //!@{
   public:
      /*!
       * \brief
       *    Get interface. This function should read
       *    a single data_t object from device in blocking mode.
       * \param   data     Reference to data output from device.
       * \return  Number of data read from device
       * \note
       *    A successful call should return 1
       */
      size_t get (data_t& data, idx_t cursor) {
         return get_ (data, cursor);
      }

      /*!
       * \brief
       *    Old stile get functionality using free standing data_t*.
       *    This function should return a stream of data from device
       * \param data    Pointer to buffer to write the data from device.
       * \param n       The number of data of type data_t to read
       * \return        The read data items.
       */
      size_t get (data_t* data, size_t n, idx_t cursor) {
         return get_ (data, n, cursor);
      }

      /*!
       * \brief
       *    Put interface. This function should send a single
       *    data_t object to device.
       * \param   data  The data to send
       * \return  The number of transmitted data items
       * \note
       *    A successful call should return 1
       */
      size_t put (const data_t& data, idx_t cursor) {
         return put_ (data, cursor);
      }

      /*!
       * \brief
       *    Put interface. This function should send a stream of
       *    data_t objects to device.
       * \param data    Pointer to buffer indenting write to device.
       * \param n       The number of data of type data_t to send
       * \return        The number of transmitted items.
       */
      size_t put (const data_t* data, size_t n, idx_t cursor) {
          return put_ (data, n, cursor);
       }

      /*!
       * \brief
       *    Return the current cursor position
       */
      idx_t cursor () const { return cursor_(); }
      /*!
       * \brief
       *    Set the cursor position
       * \param idx  Cursor address to set
       * \return     The current cursor
       */
      idx_t cursor (idx_t idx) { return cursor_(idx); }
   //!@}

   /*!
    * \name  Stream operator>> interface
    */
   //!@{
      /*!
       * \brief
       *    Template operator>> implementation for for all by value/ref parameters
       * \param dst     Reference to destination
       * \return        Reference to this device for chaining
       */
      template <typename _Dst_t>
      idx_dev_t& operator>> (_Dst_t& dst) {
         static_assert ((sizeof (_Dst_t)%sizeof(data_t) == 0),
                        "Target size must be an integer multiple of device's data size");
         get_ (reinterpret_cast<data_t*>(&dst), sizeof(_Dst_t)/sizeof(data_t), cursor_());
         return *this;
      }
      //! specialization to disallow pointer types as destination
      template <typename _Dst_t>
      idx_dev_t& operator>> (_Dst_t* dst) = delete;

      //! Overload for single data_t object
      idx_dev_t& operator>> (data_t& dst) {
         get_ (dst, cursor_());
         return *this;
      }
   //!@}

   /*!
    * \name  Stream operator<< interface
    */
   //!@{
      /*!
       * \brief
       *    Template operator<< implementation for for all by value/ref parameters
       * \param src     Reference to source data
       * \return        Reference to this device for chaining
       */
      template <typename _Src_t>
      idx_dev_t& operator<< (_Src_t& src) {
         static_assert ((sizeof (_Src_t)%sizeof(data_t) == 0),
                        "Source size must be an integer multiple of device's data size");
         put_ (reinterpret_cast<data_t*>(&src), sizeof (_Src_t)/sizeof(data_t), cursor_());
         return *this;
      }
      //! specialization to disallow pointer types as source
      template <typename _Src_t>
      idx_dev_t& operator<< (_Src_t* src) = delete;

      //! Overload for single data_t object
      idx_dev_t& operator<< (const data_t& src) {
         put_ (src, cursor_());
         return *this;
      }
   //!@}

   /*!
    * \name Helper operators
    */
   //!@{
      data_t& operator[] (const idx_t idx) {
         iterator it(this, idx);
         return *it;
      }
   //!@}

   /*!
    * \name STL-like Input iterator interface
    */
   //!@{
   public:
      using iterator       = idxdev_it <idx_dev_t, data_t*, N>;         //!< Iterator
      using const_iterator = idxdev_it <idx_dev_t, const data_t*, N>;   //!< Const iterator

      //!@{ .begin implementation
      iterator        begin ()       noexcept { return       iterator(this, iterator::beg); }
      const_iterator  begin () const noexcept { return const_iterator(this, iterator::beg); }
      const_iterator cbegin () const noexcept { return const_iterator(this, iterator::beg); }
      //!@}
      //!@{ .end implementation
      iterator        end ()         noexcept { return       iterator(this, iterator::eos); }
      const_iterator  end ()   const noexcept { return const_iterator(this, iterator::eos); }
      const_iterator cend ()   const noexcept { return const_iterator(this, iterator::eos); }
      //!@}
   //!@}
   };

   /*!
    * indexed device concept
    */
   //! @{
   #if defined _utl_have_concepts
      template <typename _Tp>
      concept bool Idx_dev = requires (_Tp t, const _Tp ct, typename _Tp::data_type v) {
         // Object type
   //      requires std::is_default_constructible<_Tp>::value;
         requires !std::is_copy_constructible<_Tp>::value;
         requires !std::is_copy_assignable<_Tp>::value;
         // Methods
         {t.get(v, 0)} -> size_t;
         {t.get(&v, 1, 0)} -> size_t;
         {t.put(v, 0)} -> size_t;
         {t.put(&v, 1, 0)} -> size_t;
         // Operators
         t >> v;
         t << v;
         {t[typename _Tp::idx_type{}]} -> typename _Tp::data_type&;
         // Iterators
         requires idxdev_iterator_c<typename _Tp::iterator>;
         typename _Tp::const_iterator; //XXX: change to concept: is_idxdev_iterator<_Tp>
         //requires idxdev_iterator_c<typename _Tp::const_iterator>;
         { t.begin()}  -> typename _Tp::iterator;
   //      {ct.begin()}  -> typename _Tp::const_iterator;
   //      { t.cbegin()} -> typename _Tp::const_iterator;
         { t.end()}  -> typename _Tp::iterator;
   //      {ct.end()}  -> typename _Tp::const_iterator;
   //      { t.cend()} -> typename _Tp::const_iterator;
      };
   #else
      namespace idx_dev_details {
         using std::declval;

         // main api members
         template <class _Tp> using try_get1_t   = decltype (declval<_Tp>().get (declval<typename _Tp::data_type&>()));
         template <class _Tp> using try_get2_t   = decltype (declval<_Tp>().get (declval<typename _Tp::data_type*>(), declval<size_t>()));
         // operators
         //template <class _Tp> using try_extract_t= decltype (declval<_Tp>() >> declval<typename _Tp::data_type&>());
         // iterator members
         template <class _Tp> using try_begin_t  = decltype (declval<_Tp>().begin());
         template <class _Tp> using tryc_begin_t = decltype (declval<const _Tp>().begin());
         template <class _Tp> using try_cbegin_t = decltype (declval<const _Tp>().cbegin());
         template <class _Tp> using try_end_t    = decltype (declval<_Tp>().begin());
         template <class _Tp> using tryc_end_t   = decltype (declval<const _Tp>().begin());
         template <class _Tp> using try_cend_t   = decltype (declval<const _Tp>().cend());

         //! Primary template to catch any non input device types
         template <typename _Tp, typename =void>
         struct is_idx_dev_ : false_ {};

         //! template to catch a proper input device type
         template <typename _Tp>
         struct is_idx_dev_ <_Tp,
            void_t <
               typename _Tp::data_type,
               typename _Tp::pointer_type,
               typename _Tp::iterator,
               typename _Tp::const_iterator,
               use_if_same_t <try_get1_t <_Tp>,  size_t>,
               use_if_same_t <try_get2_t <_Tp>,  size_t>,
               //if_same_t <try_extract_t<_Tp>,typename _Tp&>,
               use_if_same_t <try_begin_t<_Tp>,  typename _Tp::iterator>,
               use_if_same_t <tryc_begin_t<_Tp>, typename _Tp::const_iterator>,
               use_if_same_t <try_cbegin_t<_Tp>, typename _Tp::const_iterator>,
               use_if_same_t <try_end_t<_Tp>,    typename _Tp::iterator>,
               use_if_same_t <tryc_end_t<_Tp>,   typename _Tp::const_iterator>,
               use_if_same_t <try_cend_t<_Tp>,   typename _Tp::const_iterator>
            >
         > : true_ {};
      }
      /*!
       * Predicate for input device checking
       * \param   _Tp   Type to check
       * \return  True if _Tp is a input device
       */
      template <typename _Tp>
      constexpr bool Idx_dev = idx_dev_details::is_idx_dev_ <_Tp>::value;
   #endif
   //!@}

}
//!@}

#endif /* #ifndef __utl_dev_idx_dev_h__ */
