/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef INCLUDE_ECXX_PLATFORM_SPECIFIC_APPLICATION_H
#error "this file cannot be included directly"
#endif
#undef INCLUDE_ECXX_PLATFORM_SPECIFIC_APPLICATION_H


#ifndef ECXX_PLATFORM_AVR_APPLICATION_H
#define ECXX_PLATFORM_AVR_APPLICATION_H


#include <eCxx/Platform/Common/SPI.h>
#include <eCxx/Platform/Common/TWIMaster.h>
#include <eCxx/Platform/Common/TWISlave.h>
#include <eCxx/Platform/Common/UART.h>

#include "PinName.h"


namespace eCxx {


//
// Forward declaration of AnalogComparatorLite class
//
class AnalogComparatorLite;


//
// A standard AVR application
//
// AVR's ADC voltage range is between 0V to VRef.
//
class Application : public Application_Base, public Application_CRTPBase<Application> {
    public:
        // Forward declaration to the application's configuration structure
        struct Config;

        // Explicitly pull these from the Application_Base class
        using Application_Base::PinMode;

        using Application_Base::InvalidPin;

        using Application_Base::UARTBufferSize;
        using Application_Base::TWIBufferSize;
        using Application_Base::BSRBufferSize;

        using Application_Base::CommunicationTimeoutCycles;
        using Application_Base::MaxScheduledUserCallback;

        using Application_Base::VoidFuncPtr;
        using Application_Base::VoidFuncPtrArg;

        // Declare the OWIMaster class
        using OWIMaster = Application_OWIMaster<Application>;

        // Minimum and maximum ADC pin numbers
        static constexpr uint8_t ADCPinMin = AVR::AMin;
        static constexpr uint8_t ADCPinMax = AVR::AMax;

        // Interrupt mode
        enum class InterruptMode : uint8_t {
            Rising  = 0x03,
            Falling = 0x02,
            Change  = 0x01
        };

        // ADC voltage reference selection
        enum class ADCVRef : uint8_t {
            ExternalAREF = 0, // External AREF pin
            ExternalAVCC = 1, // External AVCC pin (default; 3.3 V or 5 V, depending on the power supply)
            Internal1    = 2, // Internal 1.10 V   (only available for 164/324/644/1284 & 1280/2560 series)
            Internal2    = 3  // Internal 2.56 V   (1.1 V for 88/168/328 series)
        };

    public:
        static void halt() noexcept __ECXX_ATTR_NORETURN;
        static void softReset() noexcept __ECXX_ATTR_NORETURN;

        static void getSoftUARTBitDelayUS(uint16_t& txBit, uint16_t& rxBit, uint16_t& rxCtr, uint32_t baudrate) noexcept;

        static void setPinMode(uint8_t pin, PinMode mode);
        static void setActivityIndicatorLED(bool on);

        static void digitalWrite(uint8_t pin, bool bit);
        static bool digitalRead(uint8_t pin);

        static void analogWrite(uint8_t pin, uint8_t val);
        static uint16_t analogRead(uint8_t pin);

        static bool nvStorageFormat(); // WARNING : Does nothing in AVR!
        static bool nvStorageWrite(const void* src, uint16_t size, uint16_t offset = 0);
        static bool nvStorageRead (      void* dst, uint16_t size, uint16_t offset = 0);

        static uint32_t micros() noexcept;
        static uint32_t millis() noexcept;

        static inline void delayUS(uint16_t uS) noexcept __ECXX_ATTR_ALWAYS_INLINE;
        static inline void delayMS(uint16_t mS) noexcept __ECXX_ATTR_ALWAYS_INLINE;

#ifndef ECXX_AVR_HAVE_HARDWARE_USB
        static inline UART& uart();
#else
        static inline VirtualUART& uart();
#endif

        static inline SPI& spi();

        static TWIMaster& twiMaster();
        static TWISlave& twiSlave();

        static OWIMaster& owiMaster();

        static inline void yield(bool /*immediate*/ = false) noexcept __ECXX_ATTR_ALWAYS_INLINE;
        static constexpr uint32_t getCPUFrequency() noexcept;

        static bool attachInterruptArg(uint8_t pin, VoidFuncPtrArg userFunc, void* arg, InterruptMode mode);
        static bool detachInterrupt(uint8_t pin);

        static void initialize(const Application::Config& config);
        static uint32_t processEvent(bool blinkActivityIndicatorLED = true);

    public:
        static inline void setADCReferenceVoltage(ADCVRef adcReferenceVoltage = ADCVRef::ExternalAVCC) noexcept;

        static UART* uart0();
        static UART* uart1();
        static UART* uart2();
        static UART* uart3();

        static SPI* spi0();
        static SPI* spi1();

#ifdef ECXX_AVR_HAVE_HARDWARE_USB

        static VirtualUART* vuart0();
        static VirtualUART* vuart1();

        // NOTE : The callbacks must be as short as possible; for long operation,
        //        set a flag and execute the real operation in eCxx_user_task()
        static void vuart0MagicBaudrateCallback(VoidCallback cb);
        static void vuart0DtrLowCallback(VoidCallback cb);

        // NOTE : The callbacks must be as short as possible; for long operation,
        //        set a flag and execute the real operation in eCxx_user_task()
        static void vuart0ChangeSettingsCallback(VoidCallback cb);
        static void vuart1ChangeSettingsCallback(VoidCallback cb);

        static void vuart0GetSettings(uint32_t& baudrate, uint8_t& charSize, uint8_t& parity, uint8_t& stopBits);
        static void vuart1GetSettings(uint32_t& baudrate, uint8_t& charSize, uint8_t& parity, uint8_t& stopBits);

        static uint32_t vuart0GetBaudrate();
        static uint32_t vuart1GetBaudrate();

        static void vuart0GetRtsDtrState(bool& rts, bool& dtr);
        static void vuart1GetRtsDtrState(bool& rts, bool& dtr);

#endif

        static AnalogComparatorLite& analogComparatorLite();

    public:
        // Fast-digital-read wrapper class
        class _PinFDR {
            private:
                friend class Application;

                volatile uint8_t* _sfr; // PINn
                uint8_t           _bm;  // Bitmask

            public:
                inline _PinFDR() noexcept __ECXX_ATTR_ALWAYS_INLINE
                : _sfr(nullptr), _bm(0)
                {}

                inline bool operator()() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return *_sfr & _bm; }

                inline volatile uint8_t* sfrPin() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return _sfr; }

                inline uint8_t bitmask() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return _bm; }
        };

        // Fast-digital-write wrapper class
        class _PinFDW {
            private:
                friend class Application;

                volatile uint8_t* _sfr; // PORTn
                uint8_t           _bm;  // Bitmask

            public:
                inline _PinFDW() noexcept __ECXX_ATTR_ALWAYS_INLINE
                : _sfr(nullptr), _bm(0)
                {}

                inline void operator()(bool bit) const noexcept __ECXX_ATTR_ALWAYS_INLINE
                {
                    if(bit) *_sfr |=  _bm;
                    else    *_sfr &= ~_bm;
                }

                inline volatile uint8_t* sfrPort() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return _sfr; }

                inline uint8_t bitmask() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return _bm; }
        };

        // Fast-digital-read-and-write wrapper class
        // (by default, when in read mode, the pin is configured with pull-up)
        class _PinFDX {
            private:
                friend class Application;

                volatile uint8_t* _ddr;       // DDRn
                volatile uint8_t* _sfrr;      // PINn
                volatile uint8_t* _sfrw;      // PORTn
                uint8_t           _bm;        // Bitmask

                bool              _usePullUp; // Use pull-up flag

            public:
                inline _PinFDX() noexcept __ECXX_ATTR_ALWAYS_INLINE
                : _ddr(nullptr), _sfrr(nullptr), _sfrw(nullptr), _bm(0), _usePullUp(true)
                {}

                inline void setUsePullUp(bool usePullUp) noexcept __ECXX_ATTR_ALWAYS_INLINE
                { _usePullUp = usePullUp; }

                inline void setReadMode() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                {
                    if(_usePullUp) {
                        *_sfrw |=  _bm;
                        *_ddr  &= ~_bm;
                    }
                    else {
                        *_sfrw &= ~_bm;
                        *_ddr  &= ~_bm;
                    }
                }

                inline void setWriteMode(bool initialBit) const noexcept __ECXX_ATTR_ALWAYS_INLINE
                {
                    if(initialBit) *_sfrw |=  _bm;
                    else           *_sfrw &= ~_bm;

                    *_ddr |= _bm;
                }

                inline bool read() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return *_sfrr & _bm; }

                inline void write(bool bit) const noexcept __ECXX_ATTR_ALWAYS_INLINE
                {
                    if(bit) *_sfrw |=  _bm;
                    else    *_sfrw &= ~_bm;
                }

                inline volatile uint8_t* sfrDir() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return _ddr; }

                inline volatile uint8_t* sfrPin() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return _sfrr; }

                inline volatile uint8_t* sfrPort() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return _sfrw; }

                inline uint8_t bitmask() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return _bm; }
        };

        // Fast-digital-read/write initialization functions
        static void _preparePinFDR(_PinFDR& pinFDR, uint8_t pin) noexcept;
        static void _preparePinFDW(_PinFDW& pinFDW, uint8_t pin) noexcept;
        static void _preparePinFDX(_PinFDX& pinFDX, uint8_t pin) noexcept;

    private:
        // OWIMaster low-level functions
        friend class Application_OWIMaster<Application>;

        class OWIData {
            public:
                inline OWIData()
                : portNumber(0), bitmask(0)
                {}

                inline bool valid() const
                { return bitmask != 0; }

            private:
                friend class Application;

                uint8_t portNumber;
                uint8_t bitmask;
        };

        static bool owiBegin(OWIData& owiData, uint8_t pin);
        static void owiEnd(OWIData& owiData);

        static bool owiReset(const OWIData& owiData);

        static void owiWriteBit0(const OWIData& owiData);
        static void owiWriteBit1(const OWIData& owiData);
        static bool owiReadBit(const OWIData& owiData);

    private:
        static constexpr uint32_t _cpuFrequency = F_CPU;

        static bool     _pwmEnabled;
        static uint8_t  _adcReferenceVoltage;

        static uint16_t _microsMultFactor;

        static inline void __delay_loop4  (uint16_t cnt) noexcept __ECXX_ATTR_ALWAYS_INLINE;
        static inline void __delay_loop5  (uint16_t cnt) noexcept __ECXX_ATTR_ALWAYS_INLINE;
        static inline void __delay_us_impl(uint16_t uS ) noexcept __ECXX_ATTR_ALWAYS_INLINE;
        static inline void __delay_ms_impl(uint16_t mS ) noexcept __ECXX_ATTR_ALWAYS_INLINE;

        static void disablePWMOutput(uint8_t pin);

        friend class OneWireSoftUART;
};


inline void Application::yield(bool) noexcept
{ __asm__ __volatile__ ( "wdr\n\t" ); }


constexpr uint32_t Application::getCPUFrequency() noexcept
{ return _cpuFrequency; }


inline void Application::setADCReferenceVoltage(ADCVRef adcReferenceVoltage) noexcept
{ _adcReferenceVoltage = static_cast<uint8_t>(adcReferenceVoltage) << 6; }


inline void Application::delayUS(uint16_t uS) noexcept
{ Application::__delay_us_impl(uS); }


inline void Application::delayMS(uint16_t mS) noexcept
{ Application::__delay_ms_impl(mS); }


#ifndef ECXX_AVR_HAVE_HARDWARE_USB

inline UART& Application::uart()
{ return *uart0(); }

#else

inline VirtualUART& Application::uart()
{ return *vuart0(); }

#endif


inline SPI& Application::spi()
{ return *spi0(); }


//
// Application configuration structure
//
struct Application::Config {
    enum class WDTO : uint8_t {
        Disabled = 255,
        _15mS    = 0,
        _30mS    = 1,
        _60mS    = 2,
        _120mS   = 3,
        _250mS   = 4,
        _500mS   = 5,
        _1S      = 6,
        _2S      = 7
    };

    WDTO     watchdogTimeout;
    bool     enablePWM;

    uint16_t timer0MillisInc;
    uint8_t  timer0FractFInc;
    uint16_t microsMultiplierFactor;

    inline Config(bool enablePWM = true, WDTO watchdogTimeout = WDTO::Disabled);
};


inline Application::Config::Config(bool enablePWM_, WDTO watchdogTimeout_)
: watchdogTimeout(watchdogTimeout_)
, enablePWM      (enablePWM_)
{
    const uint8_t  clockCyclesPerMicroseconds = (F_CPU) / 1000000UL;
          uint16_t usecPerTimer0Overflow;

    // CPU frequency >= 1 MHz
    if(clockCyclesPerMicroseconds) {
        // The number of microseconds for each Timer0 overflow (Timer0 prescaler is 64)
        usecPerTimer0Overflow = 64 * 256 / clockCyclesPerMicroseconds;
        // Multiplier factor for micros()
        microsMultiplierFactor = 64 / clockCyclesPerMicroseconds;
    }

    // CPU frequency < 1 MHz
    else {
        // The number of microseconds for each Timer0 overflow (Timer0 prescaler is 64)
        usecPerTimer0Overflow = (64 * 256 * (F_CPU) + 500000UL) / 1000000UL;
        // Multiplier factor for micros()
        microsMultiplierFactor = 64 * 1000000UL / (F_CPU);
    }

    // Calculate Timer0 increment factors
    timer0MillisInc =  usecPerTimer0Overflow / 1000;
    timer0FractFInc = (usecPerTimer0Overflow % 1000 + 4) >> 3;
}


//
// Private inline member functions of the Application class
//
inline void Application::__delay_loop4(uint16_t cnt) noexcept
{
    // Maximum delay at 1 MHz CPU is about 65536 * 4 = 262144 uS = 262.144 mS
    __asm__ __volatile__ (
        "1: sbiw %0, 1 \n\t"
        "   brne 1b    \n\t"
        : "=w"(cnt)
        :  "0"(cnt)
    );
}


inline void Application::__delay_loop5(uint16_t cnt) noexcept
{
    // Maximum delay at 1 MHz CPU is about 65536 * 5 = 327680 uS = 327.680 mS
    __asm__ __volatile__ (
        "1: sbiw %0, 1 \n\t"
        "   wdr        \n\t"
        "   brne 1b    \n\t"
        : "=w"(cnt)
        :  "0"(cnt)
    );
}


inline void Application::__delay_us_impl(uint16_t uS_) noexcept
{
    // Minimum delay at any MHz CPU is about     1 uS
    // Maximum delay at any MHz CPU is about 65535 uS
    if(!uS_) return;

    const uint16_t mS = uS_ / 1000;
    const uint16_t uS = uS_ - mS * 1000;

#if (F_CPU) >= 1000000UL
    const uint16_t totalTicks = ( uS * 65536UL * ( (F_CPU) / 1000000UL )                   + 163840UL ) / 327680UL;
#else
    const uint16_t totalTicks = ( uS * 65536UL / ( ( 1000000UL + (F_CPU) / 2 ) / (F_CPU) ) + 163840UL ) / 327680UL;
#endif

    __delay_ms_impl(mS);
    __delay_loop5(totalTicks);
}


inline void Application::__delay_ms_impl(uint16_t mS) noexcept
{
    // Minimum delay at any MHz CPU is about     1 mS
    // Maximum delay at any MHz CPU is about 65535 mS
    if(!mS) return;

#if (F_CPU) >= 1000000UL
    const uint32_t totalTicks     = ( mS * 65536UL / 32768UL * 100UL ) * ( (F_CPU) / 1000000UL );
#else
    const uint32_t totalTicks     = ( mS * 65536UL / 32768UL * 100UL ) / ( ( 1000000UL + (F_CPU) / 2 ) / (F_CPU) );
#endif
          uint16_t numOfFullTicks = totalTicks / 65536UL;
    const uint16_t remainderTicks = totalTicks - numOfFullTicks * 65536UL;

    while(numOfFullTicks--) __delay_loop5(65535UL);
    if(remainderTicks) __delay_loop5(remainderTicks);
}


} // namespace eCxx


//
// Include the header files for some AVR-specific classes
//
#include "AnalogComparatorLite.h"
#include "TimerCounter1.h"


#endif
