/*!
 * \file    utl/dev/ostream_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_ostream_dev_h__
#define __utl_dev_ostream_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 stream devices
 */
//!@{

   /*!
    * \brief
    *    Template base class for output stream devices using CRTP
    *
    * This class force a common interface for output stream 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
    */
   template <typename impl_t, typename data_t>
   class ostream_dev {
      _CRTP_IMPL(impl_t);
      using ostream_dev_t = ostream_dev <impl_t, data_t>;  //!< class type syntactic sugar

      //! Export types as output device concept demands
      //! @{
      public:
         using data_type      = data_t;
         using pointer_type   = data_t*;
      //!@}
         using type = ostream_dev_t;    //!< Export type as identity meta-function
      /*!
       * \name Constructor / Destructor
       */
      //!@{
      protected:
         ~ostream_dev () = default;                     //!< \brief Allow destructor from derived only
         ostream_dev () = default;                      //!< \brief A default constructor from derived only
         ostream_dev(const ostream_dev_t&) = delete;                 //!< No copies
         ostream_dev_t& operator= (const ostream_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>
          ostream_dev_t& operator<< (const _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<const data_t*>(&src), sizeof(_Src_t)/sizeof(data_t));
             return *this;
          }
          //! Overload to disallow pointer types as source
          template <typename _Src_t>
          ostream_dev_t& operator<< (_Src_t* src) = delete;

          //! Overload for single data_t object
          ostream_dev_t& operator<< (const data_t& src) {
             put_ (src);
             return *this;
          }
          //ToDo: Add support for c-string, utl::string, ...
       //!@}

       /*!
        * \name STL-like Output iterator interface
        */
       //!@{
          using iterator       = ostreamdev_it <ostream_dev_t, data_t>;       //!< Iterator
          using const_iterator = ostreamdev_it <const ostream_dev_t, data_t>; //!< Const iterator

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





   template <typename data_t>
   class ostream_dev<virtual_tag, data_t> {
      using ostream_dev_t = ostream_dev <virtual_tag, data_t>;  //!< class type syntactic sugar

      //! Export types as output device concept demands
      //! @{
      public:
         using data_type      = data_t;
         using pointer_type   = data_t*;
      //!@}
         using type = ostream_dev_t;    //!< Export type as identity meta-function
      /*!
       * \name Constructor / Destructor
       */
      //!@{
      public:
         virtual ~ostream_dev () = default;             //!< \brief Virtual destructor
      protected:
         ostream_dev () = default;                      //!< \brief A default constructor from derived only
         ostream_dev(const ostream_dev_t&) = delete;                 //!< No copies
         ostream_dev_t& operator= (const ostream_dev_t&) = delete;   //!< No copy assignments
      //!@}

      //! \name Common output device interface requirements
      //!@{
      private:
         virtual size_t put_ (const data_t& data) =0;
         virtual size_t put_ (const data_t* data, size_t n) =0;
      //!@}
      /*!
       * \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>
          ostream_dev_t& operator<< (const _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<const data_t*>(&src), sizeof(_Src_t)/sizeof(data_t));
             return *this;
          }
          //! Overload to disallow pointer types as source
          template <typename _Src_t>
          ostream_dev_t& operator<< (_Src_t* src) = delete;

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

       /*!
        * \name STL-like Output iterator interface
        */
       //!@{
          using iterator       = ostreamdev_it <ostream_dev_t, data_t>;       //!< Iterator
          using const_iterator = ostreamdev_it <const ostream_dev_t, data_t>; //!< Const iterator

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


//!@}
} //namespace utl

#endif /* #ifndef __utl_dev_out_dev_h__ */
