/*
 * 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_AVRX_APPLICATION_H
#define ECXX_PLATFORM_AVRX_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 some classes
//
class AnalogComparator;
class Logic;
class TimerCounterB;


//
// 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 = 0; // AVR::AMin;
        static constexpr uint8_t ADCPinMax = 0; // AVR::AMax;

        // size of user row (an extra page of EEPROM memory that can be used for firmware settings)
        static constexpr uint8_t UserRowSize = 64;

        // 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)
            Internal0V55 = 2, // Internal 0.55 V
            Internal1V10 = 3, // Internal 1.10 V
            Internal1V50 = 4, // Internal 1.50 V
            Internal2V50 = 5, // Internal 2.50 V
            Internal4V34 = 6  // Internal 4.34 V
        };

    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 setPinInvertedIOMode(uint8_t pin, bool enabled);
        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 bool writeUserRow(uint8_t index, uint8_t value);
        static bool readUserRow(uint8_t index, uint8_t& value);

        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;

        static inline UART& uart();

        static 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);

        // The MCU will never wake-up if there is no previously configured wake-up interrupt source
        // (only EINT, TWI slave address match, and RTC PIT can be used to wake-up the MCU)
        static void powerDown() noexcept;

        // This function must be called immediately after wake-up to restore some configurations that
        // have been changed by the power-down process
        static void restoreOnWakeUp() noexcept;

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

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

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

        static Logic& logic();
        static AnalogComparator& analogComparator();

        static TimerCounterB* tcb1();
        static TimerCounterB* tcb2();
        static TimerCounterB* tcb3();

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

                volatile PORT_t* _port; // PORTn
                uint8_t          _bm;   // Bitmask

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

                inline bool operator()() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return _port->IN & _bm; }

                inline volatile uint8_t* sfrPortIn() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return &_port->IN; }

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

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

                volatile PORT_t* _port; // PORTn
                uint8_t          _bm;   // Bitmask

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

                inline void operator()(bool bit) const noexcept __ECXX_ATTR_ALWAYS_INLINE
                {
                    if(bit) _port->OUTSET = _bm;
                    else    _port->OUTCLR = _bm;
                }

                inline volatile uint8_t* sfrPortOutSet() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return &_port->OUTSET; }

                inline volatile uint8_t* sfrPortOutClr() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return &_port->OUTCLR; }

                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 PORT_t*  _port;      // PORTn
                volatile uint8_t* _ctrl;      // PINnCTRL
                uint8_t           _bm;        // Bitmask

                bool              _usePullUp; // Use pull-up flag

            public:
                inline _PinFDX() noexcept __ECXX_ATTR_ALWAYS_INLINE
                : _port(nullptr), _ctrl(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) {
                        *_ctrl        |=  PORT_PULLUPEN_bm;
                        _port->DIRCLR  = _bm;
                    }
                    else {
                        *_ctrl        &= ~PORT_PULLUPEN_bm;
                        _port->DIRCLR  = _bm;
                    }
                }

                inline void setWriteMode(bool initialBit) const noexcept __ECXX_ATTR_ALWAYS_INLINE
                {
                    if(initialBit) _port->OUTSET = _bm;
                    else           _port->OUTCLR = _bm;

                    _port->DIRSET = _bm;
                }

                inline bool read() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return _port->IN & _bm; }

                inline void write(bool bit) const noexcept __ECXX_ATTR_ALWAYS_INLINE
                {
                    if(bit) _port->OUTSET = _bm;
                    else    _port->OUTCLR = _bm;
                }

                inline volatile uint8_t* sfrPortDirSet() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return &_port->DIRSET; }

                inline volatile uint8_t* sfrPortDirClr() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return &_port->DIRCLR; }

                inline volatile uint8_t* sfrPortPinCtrl() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return _ctrl; }

                inline volatile uint8_t* sfrPortIn() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return &_port->IN; }

                inline volatile uint8_t* sfrPortOutSet() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return &_port->OUTSET; }

                inline volatile uint8_t* sfrPortOutClr() const noexcept __ECXX_ATTR_ALWAYS_INLINE
                { return &_port->OUTCLR; }

                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()
                : port(nullptr), creg(nullptr), bitmask(0)
                {}

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

            private:
                friend class Application;

                volatile PORT_t*  port;
                volatile uint8_t* creg;
                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 uint16_t _microsMultFactor;

        static uint8_t  _wdtCtrlA;

        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::delayUS(uint16_t uS) noexcept
{ Application::__delay_us_impl(uS); }


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


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


//
// 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,
        _4S      = 8,
        _8S      = 9
    };

    WDTO     watchdogTimeout;
    bool     enablePWM;

    uint16_t timer0MillisInc;
    uint8_t  timer0FractFInc;

    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 usecPerTCB0Interrupt;

    // The number of microseconds for each TCB0 interrupt (TCB0 prescaler is 2 ; TOP is 8191)
    usecPerTCB0Interrupt = 2UL * 8192UL / clockCyclesPerMicroseconds;

    // Calculate TCB0 increment factors
    timer0MillisInc =  usecPerTCB0Interrupt / 1000;
    timer0FractFInc = (usecPerTCB0Interrupt % 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.68 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;

    const uint16_t totalTicks = ( uS * 65536UL * ( (F_CPU) / 1000000UL ) + 163840UL ) / 327680UL;

    __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;

    const uint32_t totalTicks     = ( mS * 65536UL / 32768UL * 100UL ) * ( (F_CPU) / 1000000UL );
          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 AVRX-specific classes
//
#include "AnalogComparator.h"
#include "EventSystem.h"
#include "Logic.h"
#include "RealTimeCounter.h"
#include "TimerCounterB.h"


#endif
