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


#ifndef ECXX_PLATFORM_COMMON_APPLICATION_H
#define ECXX_PLATFORM_COMMON_APPLICATION_H


#ifdef ECXX_BUILDING_ECXX_LIBRARY
    #include <eCxx/CTL/HaveMemberCheck.h>
#endif

#include <eCxx/CTL/Utility.h>

#include <eCxx/Platform/Common/Application_CRTPBase.h>
#include <eCxx/Platform/Common/OWIMaster.h>


namespace eCxx {


/*
 * Verification of the communication sub-systems
 *
 *     Sub-System            AVR   AVRX   NodeMCU
 *     ----------            ---   ----   -------
 *     UART                  2     2      2
 *     SPIMaster             2     2      2
 *     SPISlave              1     1      1 (*)
 *     TWIMaster             3     2      2
 *     TWISlave              2     1      1 (!)
 *     OWIMaster             1     1      1
 *     OneWireSoftUART (M)   1     1      1
 *     OneWireSoftUART (S)   1     1      1
 *     StreamSampler         1     1      1
 *
 *     (*) NodeMCU SPI in slave mode is register-based, half-duplex, and working in a bulk of 4 or 32 bytes.
 *     (!) NodeMCU TWI in slave mode can only work with low clock frequency (25 kHz or less is recommended).
 */


//
// An application base class that contains common member types/constants/variables/functions/etc.
//
class Application_Base : public Uninstantiable {
    public:
        // Pin mode
        enum class PinMode : uint8_t {
            Input       = 0,
            InputPullUp = 1,
            Output      = 2
        };

        // Invalid pin number
        static constexpr uint8_t InvalidPin = 0xFF;

        // Specify the size of some communication buffers
        //     # The values specified here must be in the power of two (maximum 128 bytes each).
        //     # The values specified here shall be the minimum size of the corresponding buffer(s) in bytes.
        //     # The actual size of the buffer(s) can be larger because on platforms with more RAM, those
        //       values will be scaled up.
        static constexpr uint8_t UARTBufferSize = 16; // Universal asynchronous receiver/transmitter
        static constexpr uint8_t TWIBufferSize  = 16; // Two wire interface
        static constexpr uint8_t BSRBufferSize  = 16; // Bitstream receiver

        // Specify the number of cycles for communication timeout
        // # It is mainly used by SPI and TWI communication drivers/systems.
        // # It must be long enough for slow operations, such as writing to EEPROM/flash memory.
        static constexpr uint32_t CommunicationTimeoutCycles = 100000UL;

        // Maximum number of scheduled user callbacks that will be called on the next Application::processEvent()
        static constexpr uint8_t MaxScheduledUserCallback = 2;

        // Generic user callback
        using VoidFuncPtr     = eCxx::VoidCallback;
        using VoidFuncPtrBool = eCxx::VoidCallback_Bool;
        using VoidFuncPtrArg  = eCxx::VoidCallback_VoidPtr;

    public:
        Application_Base(const Application_Base& ) = delete;
        Application_Base(      Application_Base&&) = delete;

        Application_Base& operator=(const Application_Base& ) = delete;
        Application_Base& operator=(      Application_Base&&) = delete;

        Application_Base() = delete;
        ~Application_Base() = delete;

    public:
        static const char PROGMEM PGMStrNoError[];
        static const char PROGMEM PGMStrUnknown[];

    public:
        static        uint32_t random32() noexcept;
        static inline uint16_t random16() noexcept;
        static inline uint8_t  random8 () noexcept;

        static inline uint32_t random32(uint32_t min, uint32_t max) noexcept;
        static inline uint16_t random16(uint16_t min, uint16_t max) noexcept;
        static inline uint8_t  random8 (uint8_t  min, uint8_t  max) noexcept;

#ifdef __AVR_ARCH__
        static inline uint16_t random() noexcept;
        static inline uint16_t random(uint16_t min, uint16_t max) noexcept;
#else
        static inline uint32_t random() noexcept;
        static inline uint32_t random(uint32_t min, uint32_t max) noexcept;
#endif

    public:
        // Register a user callback that will be called with interrupt disabled
        // by the next Application::processEvent()
        static bool scheduleAtomicUserCallback(VoidFuncPtrArg userFunc, void* arg);

    private:
        // Scheduled-user-callback support
        static VoidFuncPtrArg _schedUserCB   [MaxScheduledUserCallback];
        static void*          _schedUserCBArg[MaxScheduledUserCallback];

        template <typename ImplementationT>
        friend class Application_CRTPBase;
};


inline uint16_t Application_Base::random16() noexcept
{ return random32(); }


inline uint8_t Application_Base::random8() noexcept
{ return random32(); }


inline uint32_t Application_Base::random32(uint32_t min, uint32_t max) noexcept
{ return random32() % ( (max - min) + 1 ) + min; }


inline uint16_t Application_Base::random16(uint16_t min, uint16_t max) noexcept
{ return random16() % ( (max - min) + 1 ) + min; }


inline uint8_t Application_Base::random8(uint8_t min, uint8_t max) noexcept
{ return random8() % ( (max - min) + 1 ) + min; }


#ifdef __AVR_ARCH__


inline uint16_t Application_Base::random() noexcept
{ return random16(); }


inline uint16_t Application_Base::random(uint16_t min, uint16_t max) noexcept
{ return random16(min, max); }


#else


inline uint32_t Application_Base::random() noexcept
{ return random32(); }


inline uint32_t Application_Base::random(uint32_t min, uint32_t max) noexcept
{ return random32(min, max); }


#endif


} // namespace eCxx


//
// Include the platform-specific's implementation
//
/* All application class implementations are required to:
 *
 * # be singleton;
 *
 * # use the built-in LED is used as an activity indicator:
 *
 *       + ATmega  platform : D13 -> PB5 for standard          variants : Uno, Uno WiFi, Duemilanove, Diecimila, LilyPad, NG
 *                                -> PB5 for eightanaloginputs variants : Nano, Mini, Fio, Pro, Pro Mini, BT
 *                                -> PB7 for mega              variants : Mega, Mega 2560, Mega ADK
 *                                -> PC7 for micro             variants : Micro
 *                                -> PC7 for leonardo          variants : Leonardo, Leonardo ETH, Esplora, LilyPad USB
 *
 *       + ESP8266 platform : D0  -> GPIO16 for NodeMCU 0.9 and NodeMCU 1.0
 *                            D4  -> GPIO2  for NodeMCU 0.9 and NodeMCU 1.0
 *
 * -------------------------------------------------------------------------------------------------
 *
 * # The entry point for initialization in user application is:
 *
 *       void eCxx_user_init();
 *
 *   User is required to define this function. Within this function, the application must be initialized by calling
 *   this function exactly once at the beginning:
 *
 *       eCxx::Application::initialize(const Application::Config&);
 *
 *   Failing to do so would cause undefined behavior or even crash/reboot.
 *
 * # The task function in user application is:
 *
 *       void eCxx_user_task();
 *
 *   User is required to define this function. Within this function, this function must be called at least once at
 *   the beginning:
 *
 *       eCxx::Application::processEvent();
 *
 *   Failing to do so would cause undefined behavior or even crash/reboot. If the task is quite long, then it maybe
 *   necessary to call the above function more than once.
 */

#define INCLUDE_ECXX_PLATFORM_SPECIFIC_APPLICATION_H

// AVR
#if defined(__AVR_ARCH_X__)
    #include "../AVRX/Application.h"
#elif defined(__AVR_ARCH__)
    #include "../AVR/Application.h"
#endif

// NodeMCU-ESP8266
#ifdef __XTENSA__
    #include "../NodeMCU/Application.h"
#endif

// Dummy (for testing the eCxx library in the host PC)
#ifdef INCLUDE_ECXX_PLATFORM_SPECIFIC_APPLICATION_H
    #include "../Dummy/Application.h"
#endif

// For convenience
namespace eCxx {
    typedef Application::OWIMaster OWIMaster;
}


//
// Check if the platform-specific's implementation provides the necessary member types/constants/variables/functions/etc.
//
#ifdef ECXX_BUILDING_ECXX_LIBRARY

namespace eCxx {

    namespace internal_application_check {

        // Member types
        ECXX_CHECK_HAVE_MEMBER_TYPE(Application, Config);

        ECXX_CHECK_HAVE_MEMBER_TYPE(Application, PinMode);

        ECXX_CHECK_HAVE_MEMBER_TYPE(Application, VoidFuncPtr);
        ECXX_CHECK_HAVE_MEMBER_TYPE(Application, VoidFuncPtrArg);

        ECXX_CHECK_HAVE_MEMBER_TYPE(Application, OWIMaster);

        ECXX_CHECK_HAVE_MEMBER_TYPE(Application, InterruptMode);

        ECXX_CHECK_HAVE_MEMBER_TYPE(Application, _PinFDR);
        ECXX_CHECK_HAVE_MEMBER_TYPE(Application, _PinFDW);
        ECXX_CHECK_HAVE_MEMBER_TYPE(Application, _PinFDX);

        // Member constants/variables/functions
        ECXX_CHECK_HAVE_MEMBER(Application, InvalidPin);

        ECXX_CHECK_HAVE_MEMBER(Application, ADCPinMin);
        ECXX_CHECK_HAVE_MEMBER(Application, ADCPinMax);

        ECXX_CHECK_HAVE_MEMBER(Application, UARTBufferSize);
        ECXX_CHECK_HAVE_MEMBER(Application, TWIBufferSize);
        ECXX_CHECK_HAVE_MEMBER(Application, BSRBufferSize);

        ECXX_CHECK_HAVE_MEMBER(Application, CommunicationTimeoutCycles);
        ECXX_CHECK_HAVE_MEMBER(Application, MaxScheduledUserCallback);

        ECXX_CHECK_HAVE_MEMBER(Application, halt);
        ECXX_CHECK_HAVE_MEMBER(Application, softReset);

        ECXX_CHECK_HAVE_MEMBER(Application, getSoftUARTBitDelayUS);

        ECXX_CHECK_HAVE_MEMBER(Application, setPinMode);
        ECXX_CHECK_HAVE_MEMBER(Application, setActivityIndicatorLED);

        ECXX_CHECK_HAVE_MEMBER(Application, digitalWrite);
        ECXX_CHECK_HAVE_MEMBER(Application, digitalRead);

        ECXX_CHECK_HAVE_MEMBER(Application, shiftOut);

        ECXX_CHECK_HAVE_MEMBER(Application, analogWrite);
        ECXX_CHECK_HAVE_MEMBER(Application, analogRead);

        ECXX_CHECK_HAVE_MEMBER(Application, nvStorageFormat);
        ECXX_CHECK_HAVE_MEMBER(Application, nvStorageWrite);
        ECXX_CHECK_HAVE_MEMBER(Application, nvStorageRead);

        ECXX_CHECK_HAVE_MEMBER(Application, micros);
        ECXX_CHECK_HAVE_MEMBER(Application, millis);

        ECXX_CHECK_HAVE_MEMBER(Application, delayUS);
        ECXX_CHECK_HAVE_MEMBER(Application, delayMS);

        ECXX_CHECK_HAVE_MEMBER(Application, uart);

        ECXX_CHECK_HAVE_MEMBER(Application, twiMaster);
        ECXX_CHECK_HAVE_MEMBER(Application, twiSlave);

        ECXX_CHECK_HAVE_MEMBER(Application, owiMaster);

        ECXX_CHECK_HAVE_MEMBER(Application, yield);
        ECXX_CHECK_HAVE_MEMBER(Application, getCPUFrequency);

        ECXX_CHECK_HAVE_MEMBER(Application, attachInterruptArg);
        ECXX_CHECK_HAVE_MEMBER(Application, detachInterrupt);

        ECXX_CHECK_HAVE_MEMBER(Application, initialize);
        ECXX_CHECK_HAVE_MEMBER(Application, processEvent);

        ECXX_CHECK_HAVE_MEMBER(Application, _preparePinFDR);
        ECXX_CHECK_HAVE_MEMBER(Application, _preparePinFDW);
        ECXX_CHECK_HAVE_MEMBER(Application, _preparePinFDX);

    } // namespace internal

} // namespace eCxx

#endif


#endif
