/*!
 * \file    utl/dev/out_dev.h
 * \brief   Abstract base class interface for output 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_out_dev_h__
#define __utl_dev_out_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 classes for output devices
 */
//!@{

   /*!
    * \brief
    *    Template base class for output devices. using CRTP
    *
    * This class force a common interface for output devices.
    * By using this common interface the class implements
    * - Stream-like inserting operator
    * - Output iterator
    * - Const output 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 out_dev {
      _CRTP_IMPL(impl_t);
      using out_dev_t = out_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 = out_dev_t;    //!< Export type as identity meta-function

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

   //! \name Common output device interface requirements
   //!@{
   private:
      size_t put_ (const data_t& data) { return impl().put_ (data); }
      size_t put_ (const data_t* data, size_t n) {
         return impl().put_ (data, n);
      }
   //!@}
   /*!
    * \name Common output device interface
    */
   //!@{
   public:
      /*!
       * \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) {
         return put_ (data);
      }

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

   /*!
    * \name  Stream operator << interface
    */
   //!@{
   public:
      /*!
       * \brief
       *    Template operator<< implementation for for all by value/ref parameters
       * \note
       *    In the case _Src_t size is not an exact multiple of data_t size
       *    the write data will be  truncated and there may be data loss.
       * \param src     Reference to source data
       * \return        Reference to this device for chaining
       */
       template <typename _Src_t>
       out_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));
          return *this;
       }
       //! Overload to disallow pointer types as source
       template <typename _Src_t>
       out_dev_t& operator<< (_Src_t* src) = delete;

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

   /*!
    * \name STL-like Output iterator interface
    */
   //!@{
      using iterator       = outdev_it <out_dev_t, data_t*, streamsize>;       //!< Iterator
      using const_iterator = outdev_it <const out_dev_t, data_t*, streamsize>; //!< 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   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 out_dev <virtual_tag, data_t, streamsize> {
      using out_dev_t = out_dev <virtual_tag, 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 = out_dev_t;  //!< Export type as identity meta-function

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

   /*!
    * \name Common output device interface requirements
    */
   //!@{
   private:
      /*!
       * \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
       */
      virtual size_t put_ (const data_t& data) = 0;

      /*!
       * \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.
       */
      virtual size_t put_ (const data_t* data, size_t n) = 0;
   //!@}
   /*!
    * \name  Public Put interface
    */
   //!@{
   public:
      size_t put (const data_t& data) { return put_ (data); }
      size_t put (const data_t* data, size_t n) { return put_ (data, n); }
   //!@}
   /*!
    * \name  Stream operator<< interface
    */
   //!@{
   public:
      /*!
       * \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>
      out_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));
         return *this;
      }
      //! Overload to disallow pointer types as source
      template <typename _Src_t>
      out_dev_t& operator<< (_Src_t* src) = delete;

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

   /*!
    * \name STL-like Output iterator interface
    */
   //!@{
      using iterator       = outdev_it <out_dev_t, data_t*, streamsize>;       //!< Iterator
      using const_iterator = outdev_it <const out_dev_t, data_t*, streamsize>; //!< 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); }
      //!@}
   //!@}
   };


   /*!
    * Output device concept
    */
   //! @{
   #if defined _utl_have_concepts
      template <typename _Tp>
      concept bool Out_dev = requires (_Tp t, const _Tp ct, typename _Tp::data_type v) {
         // Object type
         requires !std::is_copy_constructible<_Tp>::value;
         requires !std::is_copy_assignable<_Tp>::value;
         // Methods
         {t.put(v)}              -> size_t;
         {t.put(&v, sizeof(v))}  -> size_t;
         // Operators
         t << v;
         // Iterators
         typename _Tp::const_iterator;
         requires Outdev_it<typename _Tp::iterator>;
         requires Outdev_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 out_dev_details {
      using std::declval;

      template <class _Tp> using try_put1_t = decltype (declval<_Tp>().put (declval<const typename _Tp::data_type&>()));
      template <class _Tp> using try_put2_t = decltype (declval<_Tp>().put (declval<const typename _Tp::data_type*>(),
                                                                            declval<size_t>()));
      // operators
      //template <class _Tp> using try_insert_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 output device types
      template <typename _Tp, typename =void>
      struct is_out_dev_ : false_ { };

      //! template to catch a proper output device type
      template <typename _Tp>
      struct is_out_dev_ <_Tp,
         void_t <
            typename _Tp::data_type,
            typename _Tp::pointer_type,
            typename _Tp::iterator,
            typename _Tp::const_iterator,
            use_if_same_t <try_put1_t <_Tp>, size_t>,
            use_if_same_t <try_put2_t <_Tp>, size_t>,
            //if_same_t <try_insert_t<_Tp>,_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 output device checking
    * \param   _Tp   Type to check
    * \return  True if _Tp is a output device
    */
   template <typename _Tp>
   constexpr bool Out_dev = out_dev_details::is_out_dev_<_Tp>::value;
   #endif
   //!@}

//!@}
} //namespace utl

#endif /* #ifndef __utl_dev_out_dev_h__ */
