/*!
 * \file    utl/com/spi_bb.h
 * \brief   A bit banking implementation of spi bus inherited from
 *          spi_i base class.
 *
 * 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_com_spi_bb_h__
#define __utl_com_spi_bb_h__

#include <utl/core/impl.h>
#include <utl/core/crtp.h>
#include <utl/com/spi.h>
#include <utl/meta/meta.h>


namespace utl {

/*!
 * \ingroup Communication
 * \brief   A bit banking implementation of spi bus
 *          inherited from \ref spi_i base class.
 * \sa   spi_i
 */
//!@{

   /*!
    * \brief
    *    SPI bit banking interface template class using CRTP.
    *    Using the private interface we provide the interface from
    *    spi_i<impl_t>
    *
    * \param   impl_t   The CRTP type (the derived/implementation class typename).
    * \param   CPOL     Clock polarity
    * \param   CPHA     Clock phase
    * \param   BitOrder Data transfer bit order
    */
   template <typename impl_t,
             spi::cpol CPOL,
             spi::cpha CPHA,
             spi::bitOrder BitOrder =spi::bitOrder::MSB_First>
   class spi_bb_i : public spi_i<spi_bb_i<impl_t, CPOL, CPHA, BitOrder>> {
      _CRTP_IMPL(impl_t);  //! \brief  Syntactic sugar to CRTP casting
      friend spi_i<spi_bb_i<impl_t, CPOL, CPHA, BitOrder>>;
   public:
      using type = spi_bb_i<impl_t, CPOL, CPHA, BitOrder>; //!< Export type as identity meta-function

   /*!
    * \name Object lifetime
    */
   //!@{
   protected:
      ~spi_bb_i () = default; //!< A default destructor, allow destructor from derived only
      //! \brief A default constructor
      spi_bb_i (uint32_t clk) noexcept
         : nsec_ {1000000000/(2*clk)} {
      }
   //!@}

   private:
   //! \name SPI implementation specific functions
   //!@{
      template <spi::bitOrder B =BitOrder> constexpr
      meta::enable_if_t <(B == spi::bitOrder::LSB_First), void> shift (byte_t& b) { b <<=1; }
      template <spi::bitOrder B =BitOrder> constexpr
      meta::enable_if_t <(B == spi::bitOrder::MSB_First), void> shift (byte_t& b) { b >>=1; }
      template <spi::cpol C =CPOL> static constexpr bool clkHigh () {
         return !static_cast<bool>(C);
      }
      template <spi::cpol C =CPOL> static constexpr bool clkLow () {
         return static_cast<bool>(C);
      }
      static constexpr bool clkH_ {clkHigh()};
      static constexpr bool clkL_ {clkLow()};
   //!}

   /*!
    * \name Implementation requirements
    * \note
    *    In order for the implementation to have the following as private members
    *    it also need to declare this class as friend
    */
   //!@{
   private:
      void MOSI (bool st)  { impl().MOSI(st); }       //!< Implementers's MOSI pin function
      bool MISO ()         { return impl().MISO(); }  //!< Implementers's MISO pin function
      void SCLK (bool st)  { impl().SCLK (st); }      //!< Implementers's SCLK pin function
      void delay (uint32_t nsec) { impl().delay (nsec); }   //!< Implementers's half period delay function
   //!@}


   /*!
    * \name Implementation of base requirements
    */
   //!@{
   private:
      uint32_t _clock () const { return 1000000000/(2*nsec_); }
      void _clock (uint32_t c) { nsec_ = 1000000000/(2*c); }
      byte_t _tx_data (byte_t out) { return _tx_data_impl (out); }

      template <spi::cpha C =CPHA>
      meta::enable_if_t <(C == spi::cpha::LOW), byte_t> _tx_data_impl (byte_t out);
      template <spi::cpha C =CPHA>
      meta::enable_if_t <(C == spi::cpha::HIGH), byte_t> _tx_data_impl (byte_t out);
   //!@}

   //! Data members
   //! @{
   private:
      uint32_t nsec_;      //!< half period of SPI bus
   //! @}
   };

   /*!
    * \brief
    *    _tx_data implementation for CPHA == LOW
    *    Out at preceding clock trailing edge, In at leading clock edge
    * \param   out   The byte to send to SPI bus
    * \return        The byte read from the bus
    */
   template <typename impl_t, spi::cpol CPOL, spi::cpha CPHA, spi::bitOrder BitOrder>
   template <spi::cpha C>
      meta::enable_if_t <(C == spi::cpha::LOW), byte_t>
      spi_bb_i<impl_t, CPOL, CPHA, BitOrder>::_tx_data_impl (byte_t out) {
         byte_t in {};
         SCLK (clkL_);
         for (uint8_t bit {static_cast<uint8_t>(BitOrder)} ; bit ; shift (bit)) {
            MOSI (out & bit);    // Out at preceding clock trailing edge
            delay (nsec_);       // Half cycle delay
            SCLK (clkH_);        // Leading edge
            in |= (MISO ()) ? bit : 0; // In at leading clock edge
            delay (nsec_);       // Half cycle delay
            SCLK (clkL_);        // Trailing edge
         }
         return in;
      }

   /*!
    * \brief
    *    _tx_data implementation CPHA == HIGH
    *    Out at leading clock edge, In at trailing clock edge
    * \param   out   The byte to send to SPI bus
    * \return        The byte read from the bus
    */
   template <typename impl_t, spi::cpol CPOL, spi::cpha CPHA, spi::bitOrder BitOrder>
   template <spi::cpha C>
      meta::enable_if_t <(C == spi::cpha::HIGH), byte_t>
      spi_bb_i<impl_t, CPOL, CPHA, BitOrder>::_tx_data_impl (byte_t out) {
         byte_t in {};
         SCLK (clkL_);
         for (uint8_t bit {static_cast<uint8_t>(BitOrder)} ; bit ; shift (bit)) {
            delay (nsec_);       // Half cycle delay
            SCLK (clkH_);        // Leading edge
            MOSI (out & bit);    // Out at leading clock edge
            delay (nsec_);       // Half cycle delay
            SCLK (clkL_);        // Trailing edge
            in |= (MISO ()) ? bit : 0; // In at trailing clock edge
         }
         return in;
      }


   /*!
    * \brief
    *    A virtual base class interface specialization.
    * Using the private virtual interface we provide the interface from
    * spi_i<virtual_tag>
    * \param   impl_t   = virtual_tag
    * \param   CPOL     Clock polarity
    * \param   CPHA     Clock phase
    * \param   BitOrder Data transfer bit order
    */
   template <spi::cpol CPOL,
             spi::cpha CPHA,
             spi::bitOrder BitOrder>
   class spi_bb_i <virtual_tag, CPOL, CPHA, BitOrder> : public spi_i<virtual_tag> {
   public:
      using type = spi_bb_i<virtual_tag, CPOL, CPHA, BitOrder>; //!< Export type as identity meta-function

   /*!
    * \name Object lifetime
    */
   //!@{
   protected:
      ~spi_bb_i () = default; //!< A default destructor, allow destructor from derived only
      //! \brief A default constructor
      spi_bb_i (uint32_t clk) noexcept
         : nsec_ {1000000000/(2*clk)} {
      }
   //!@}

   private:
   //! \name SPI implementation specific functions
   //!@{
      template <spi::bitOrder B =BitOrder> constexpr
      meta::enable_if_t <(B == spi::bitOrder::LSB_First), void> shift (byte_t& b) { b <<=1; }
      template <spi::bitOrder B =BitOrder> constexpr
      meta::enable_if_t <(B == spi::bitOrder::MSB_First), void> shift (byte_t& b) { b >>=1; }
      template <spi::cpol C =CPOL> static constexpr bool clkHigh () {
         return !static_cast<bool>(C);
      }
      template <spi::cpol C =CPOL> static constexpr bool clkLow () {
         return static_cast<bool>(C);
      }
      static constexpr bool clkH_ {clkHigh()};
      static constexpr bool clkL_ {clkLow()};
   //!}


   //! \name Implementation requirements
   //!@{
   private:
      virtual void MOSI (bool) =0;  //!< Implementers's MOSI pin function
      virtual bool MISO ()     =0;  //!< Implementers's MISO pin function
      virtual void SCLK (bool) =0;  //!< Implementers's SCLK pin function
      virtual void delay (uint32_t) =0; //!< Implementers's half period delay function
   //!@}


   /*!
    * \name Implementation of base requirements
    */
   //!@{
   private:
      uint32_t _clock () const final { return 1000000000/(2*nsec_); }
      void _clock (uint32_t c) final { nsec_ = 1000000000/(2*c); }
      byte_t _tx_data (byte_t out) final { return _tx_data_impl (out); }

      template <spi::cpha C =CPHA>
      meta::enable_if_t <(C == spi::cpha::LOW), byte_t> _tx_data_impl (byte_t out);
      template <spi::cpha C =CPHA>
      meta::enable_if_t <(C == spi::cpha::HIGH), byte_t> _tx_data_impl (byte_t out);
   //!@}

   //! Data members
   //! @{
   private:
      uint32_t nsec_;      //!< half period of SPI bus
   //! @}
   };

   /*!
    * \brief
    *    _tx_data implementation CPHA == LOW
    *    Out at preceding clock trailing edge, In at leading clock edge
    * \param   out   The byte to send to SPI bus
    * \return        The byte read from the bus
    */
   template <spi::cpol CPOL, spi::cpha CPHA, spi::bitOrder BitOrder>
   template <spi::cpha C>
      meta::enable_if_t <(C == spi::cpha::LOW), byte_t>
      spi_bb_i<virtual_tag, CPOL, CPHA, BitOrder>::_tx_data_impl (byte_t out) {
         byte_t in {};
         SCLK (clkL_);
         for (uint8_t bit {static_cast<uint8_t>(BitOrder)} ; bit ; shift (bit)) {
            MOSI (out & bit);    // Out at preceding clock trailing edge
            delay (nsec_);       // Half cycle delay
            SCLK (clkH_);        // Leading edge
            in |= (MISO ()) ? bit : 0; // In at leading clock edge
            delay (nsec_);       // Half cycle delay
            SCLK (clkL_);        // Trailing edge
         }
         return in;
      }

   /*!
    * \brief
    *    _tx_data implementation CPHA == HIGH
    *    Out at leading clock edge, In at trailing clock edge
    * \param   out   The byte to send to SPI bus
    * \return        The byte read from the bus
    */
   template <spi::cpol CPOL, spi::cpha CPHA, spi::bitOrder BitOrder>
   template <spi::cpha C>
      meta::enable_if_t <(C == spi::cpha::HIGH), byte_t>
      spi_bb_i<virtual_tag, CPOL, CPHA, BitOrder>::_tx_data_impl (byte_t out) {
         byte_t in {};
         SCLK (clkL_);
         for (uint8_t bit {static_cast<uint8_t>(BitOrder)} ; bit ; shift (bit)) {
            delay (nsec_);       // Half cycle delay
            SCLK (clkH_);        // Leading edge
            MOSI (out & bit);    // Out at leading clock edge
            delay (nsec_);       // Half cycle delay
            SCLK (clkL_);        // Trailing edge
            in |= (MISO ()) ? bit : 0; // In at trailing clock edge
         }
         return in;
      }

}

#endif   //#ifndef __spi_bb_h__
