#ifndef BIWAK_BARREL_ORGAN_H
#define BIWAK_BARREL_ORGAN_H
//----------------------------------------------------------------------------
///
/// \brief  Class for handling processes like playing sounds or special
///         backlight fading.
///
///         It can be used to manage the changing frequency or duty cycle of
///         an PWM.
///
/// \date   20220827
/// \author Maximilian Seesslen <mes.seesslen.net>
///
//----------------------------------------------------------------------------


//---Includes-----------------------------------------------------------------


///---General-------------------------


///---Own-----------------------------

#include <biwak/output.hpp>
#include <biwak/timer.hpp>
//#include <biwak/pwm.hpp>


//---Defines------------------------------------------------------------------


//---Declaration--------------------------------------------------------------


//#define FTYPE  int
#define MULTI        5000     // 2000 is minimal off

// Need to be int16_t for miniminutnik. Saves 68 bytes.
typedef int16_t   barrel_t;

class CBarrelOrgan
{
   public:
      struct SSlope
      {
         barrel_t startValue;
         barrel_t endValue;
         barrel_t duration;
      };

      static constexpr barrel_t unchanged=-1;
      static constexpr barrel_t finished=-2;
      static constexpr barrel_t stopped=-4;
      static constexpr barrel_t restart=-3;

   private:
      const SSlope *m_slopes=nullptr;
      int m_arrayPos = 0;
      lrtimer_t m_timer;
      lrtimer_t m_rampTimer;
      barrel_t m_currentFrequency;

   public:
      barrel_t eventLoop()
      {
         if(m_slopes==nullptr)
            return(finished);

         if( lrElapsedMSeconds( &m_timer, m_slopes[m_arrayPos].duration ) )
         {
            barrel_t finishedTarget=m_slopes[m_arrayPos].endValue;
            m_arrayPos++;
            if ( m_slopes[ m_arrayPos ].startValue < 0 )
            {
               if( m_slopes[ m_arrayPos ].startValue == finished )
               {
                  stop();
                  // Give a chance to set the value precisely.
                  return(finishedTarget);
               }
               if( m_slopes[ m_arrayPos ].startValue == stopped )
               {
                  stop();
                  // Give a chance to set the value precisely.
                  return(0);
               }
               // Restart
               m_arrayPos=0;
               m_rampTimer=lrNow();
            }
         }
         // 32 changes per second still has some rattle; 64 was recognizable on slow
         // sound changes
         if( lrElapsedMSeconds( &m_rampTimer, MSECS_PER_SEC/128))
         {
            int currentFrequency = calculateCurrentFrequency();
            if( currentFrequency != m_currentFrequency )
            {
               m_currentFrequency=currentFrequency;
               return(m_currentFrequency);
            }
         }
         return(unchanged);
      }

      int calculateCurrentFrequency()
      {
        const SSlope &slope = m_slopes[ m_arrayPos ];
        return (
            slope.startValue +
               ( ( lrElapsedMSeconds(m_timer) * getDiffPerMs(slope) ) / MULTI ) );
      }
      static int getDiffPerMs(const SSlope &slope)
      {
          return ( ( ( slope.endValue -
                       slope.startValue ) * MULTI )
                                    / slope.duration );
      }
      const SSlope *getSlopes() const
      {
         return(m_slopes);
      }
      barrel_t startSlopes(const SSlope *slopes, int forwardFrequency = 0)
      {
         m_slopes=slopes;
         m_arrayPos=0;
         m_timer=m_rampTimer=lrNow();
         if( forwardFrequency )
         {
            m_timer += msecToLrt( calculateTimeShift (m_slopes[ m_arrayPos ], forwardFrequency) );
            return( m_currentFrequency = calculateCurrentFrequency() );
         }
         return( m_slopes[ m_arrayPos ].startValue );
      }
      static int calculateTimeShift( const SSlope &slope, int frequency)
      {
          return( ( ( slope.startValue - frequency )
                                              * MULTI) / getDiffPerMs(slope) );
      }
      void stop()
      {
         m_slopes=nullptr;
         m_arrayPos=0;
      }
      bool isActive()
      {
         return( m_slopes != nullptr );
      }
};

class CPwmLine;

class CBarrelOrganBeep : public CBarrelOrgan
{
   CPwmLine *m_pwmLine;
       
   public:
   CBarrelOrganBeep(CPwmLine* pwmLine)
           :m_pwmLine( pwmLine )
   {}
   CPwmLine *getPwm()
   {
      return( m_pwmLine );
   }
   void eventLoop();
   void setBeepMode( const CBarrelOrgan::SSlope *slopes, bool adapt = false );
   void stop();
};


class CBarrelOrganFade : public CBarrelOrgan
{
   CPwmLine *m_pwmLine;
   
   public:
   CBarrelOrganFade(CPwmLine* pwmLine)
       :m_pwmLine( pwmLine )
   {}
   CPwmLine *getPwm()
   {
      return( m_pwmLine );
   }
   void eventLoop();
   void setFadeMode( const CBarrelOrgan::SSlope *slopes );
};


namespace Barrel::Beep
{
   extern const CBarrelOrgan::SSlope singleShort[];
   extern const CBarrelOrgan::SSlope singleSuperShort[];
   extern const CBarrelOrgan::SSlope trippleShort[];
   extern const CBarrelOrgan::SSlope sireneFast[];
   extern const CBarrelOrgan::SSlope hexLong[];
   extern const CBarrelOrgan::SSlope singleLong[];
   extern const CBarrelOrgan::SSlope error[];
   extern const CBarrelOrgan::SSlope down[];
   extern const CBarrelOrgan::SSlope doubleUp[];
   extern const CBarrelOrgan::SSlope klack[];
   extern const CBarrelOrgan::SSlope fall[];
   extern const CBarrelOrgan::SSlope singleDownUp[];
}


namespace Barrel::Fade
{
   extern const CBarrelOrgan::SSlope standby[];
}


//---fin-----------------------------------------------------------------------
#endif
