/*!
 * \file    utl/dev/inbuf_dev.h
 * \brief   Abstract base class interface for input buffered
 *          devices 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_inbuf_dev_h__
#define __utl_dev_inbuf_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 input buffered devices
 */
//!@{

   /*!
    * \brief
    *    Template base class for buffered input buffered devices. using CRTP
    *
    * This class force a common interface for input, buffered devices.
    * By using this common interface the class implements
    * - Stream-like extracting operator
    * - 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   streamsize     The number of elements to indicate eos.
    *    \arg  None or 0      Stream only. No iterator as begin() now equals end().
    */
   template <typename impl_t, typename data_t, size_t streamsize =0>
   class inbuf_dev {
      _CRTP_IMPL(impl_t);
      using inbuf_dev_t = inbuf_dev <impl_t, data_t, streamsize>; //!< class type syntactic sugar

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

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

   //! \name Common input device interface requirements
   //!@{
   private:
      size_t in_avail_ () { return impl().in_avail_(); }
      size_t get_ (data_t& data) { return impl().get_(data); }
      size_t get_ (data_t* data, size_t n) { return impl().get_ (data, n); }
   //!@}

   /*!
    * \name Public Get interface
    */
   //!@{
   public:
      /*!
       * \return
       *    The available @a data_t typed items in input buffer.
       */
      size_t in_avail () {
         return in_avail_ ();
      }

      /*!
       * \brief
       *    The base get interface. This function should read
       *    a single data_t object from device usually in non-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) {
         return get_ (data);
      }

      /*!
       * \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) {
         return get_ (data, n);
      }
   //!@}

   /*!
    * \name  Stream operator >> interface
    */
   //!@{
   public:
      /*!
       * \brief
       *    Template operator >> implementation for for all by value/ref parameters
       * \note
       *    In the case _Dst_t size is not a integer multiple of device's data size
       *    this will fail by static assertion
       * \param dst     Reference to destination
       * \return        Reference to this device for chaining
       */
      template <typename _Dst_t>
      inbuf_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));
         return *this;
      }
      //! Specialization to disallow pointer types as destination
      template <typename _Dst_t>
      inbuf_dev_t& operator>> (_Dst_t* dst) = delete;

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

   /*!
    * \name STL-like Input iterator interface
    */
   //!@{
      using iterator       =  indev_it <inbuf_dev_t, data_t*, streamsize>;         //!< Iterator
      using const_iterator =  indev_it <inbuf_dev_t, const data_t*, streamsize>;   //!< Const iterator

      //!@{ .begin implementation
      iterator        begin ()       noexcept { return       iterator(this, iterator::init); }
      const_iterator  begin () const noexcept { return const_iterator(this, iterator::init); }
      const_iterator cbegin () const noexcept { return const_iterator(this, iterator::init); }
      //!@}
      //!@{ .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   streamsize     The number of elements to indicate eos.
    *    \arg  None or 0      Stream only. No iterator as begin() now equals end().
    */
   template <typename data_t, size_t streamsize>
   class inbuf_dev <virtual_tag, data_t, streamsize> {
      //!< class type syntactic sugar
      using inbuf_dev_t = inbuf_dev <virtual_tag, data_t, streamsize>;

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

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

   /*!
    * \name Common input device interface requirements
    */
   //!@{
   private:
      /*!
       * \return
       *    The available @a data_t typed items in input buffer.
       */
      virtual size_t in_avail_ () = 0;

      /*!
       * \brief
       *    The base get interface. This function should read
       *    a single data_t object from device usually in non-blocking mode.
       * \param   data     Reference to data output from device.
       * \return  Number of data read from device
       * \note
       *    A successful call should return 1
       */
      virtual size_t get_ (data_t& data) = 0;

      /*!
       * \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.
       */
      virtual size_t get_ (data_t* data, size_t n) = 0;
   //!@}

   /*!
    * \name  Public Get interface
    */
   //!@{
   public:
      size_t in_avail() { return in_avail_(); }
      size_t get (data_t& data) { return get_ (data); }
      size_t get (data_t* data, size_t n) { return get_ (data, n); }
   //!@}

   /*!
    * \name  Stream operator >> interface
    */
   //!@{
      /*!
       * \brief
       *    Template operator >> implementation for for all by value/ref parameters
       * \note
       *    In the case _Dst_t size is not a integer multiple of device's data size
       *    this will fail by static assertion
       * \param dst     Reference to destination
       * \return        Reference to this device for chaining
       */
      template <typename _Dst_t>
      inbuf_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));
         return *this;
      }
      //! specialization to disallow pointer types as destination
      template <typename _Dst_t>
      inbuf_dev_t& operator>> (_Dst_t* dst) = delete;

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

   /*!
    * \name STL-like Input iterator interface
    */
   //!@{
      using iterator       =  indev_it <inbuf_dev_t, data_t*, streamsize>;         //!< Iterator
      using const_iterator =  indev_it <inbuf_dev_t, const data_t*, streamsize>;   //!< Const iterator

      //!@{ .begin implementation
      iterator        begin ()       noexcept { return       iterator(this, iterator::init); }
      const_iterator  begin () const noexcept { return const_iterator(this, iterator::init); }
      const_iterator cbegin () const noexcept { return const_iterator(this, iterator::init); }
      //!@}
      //!@{ .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); }
      //!@}
   //!@}
   };

/*!
 * Input buffer device concept
 */
//! @{
#if defined _utl_have_concepts
   template <typename _Tp>
   concept bool Inbuf_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;
      // Operators
      t >> v;
      // Iterators
      typename _Tp::const_iterator; //XXX: change to concept: is_idxdev_iterator<_Tp>
      requires Indev_it<typename _Tp::iterator>;
      //requires Indev_it<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 inbuf_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_inbuf_dev_ : false_ {};

      //! template to catch a proper input device type
      template <typename _Tp>
      struct is_inbuf_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 Inbuf_dev = inbuf_dev_details::is_inbuf_dev_ <_Tp>::value;
#endif
//!@}
}

//!@}
#endif /* #ifndef __utl_dev_inbuf_dev_h__ */
