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


#ifndef ECXX_CONFIG_H
#define ECXX_CONFIG_H


//
// Include eCxx's version-information header file
//
#include "Version.h"


//
// Uncomment this to enable system-wide debugging
// (individual debugging macros for each sub-system must be enabled independently)
//
// #define SYSTEM_DEBUG(FMT, ...) ::printf_P( PGM_STR(FMT).p, ## __VA_ARGS__ )

#define SYSTEM_DEBUG_NOP do {} while(0)

#ifndef SYSTEM_DEBUG
    #define NO_SYSTEM_DEBUG
    #define SYSTEM_DEBUG(FMT, ...) SYSTEM_DEBUG_NOP
#endif

#if defined(__x86_64__) || defined(__i386__)
    #define printf_P printf
    extern "C" int printf(const char* format, ...) __attribute__((format(printf,1,2)));
#else
    extern "C" int printf_P(const char* format, ...) __attribute__((format(printf,1,2)));
#endif


//
// For compatibility with the Arduino library part
//
// Uncomment this to enable Arduino library debugging
// (the system-wide debugging macro above must be enabled for it to have any effect)
//
// #define DEBUGV(FMT, ...) SYSTEM_DEBUG(FMT, ## __VA_ARGS__)

#ifndef DEBUGV
    #define NO_ARDUINO_DEBUG
    #define DEBUGV(FMT, ...) SYSTEM_DEBUG_NOP
#endif


//
// Check if the CPU clock is defined
//
#ifndef F_CPU
    #if defined(__x86_64__) || defined(__i386__)
        // Dummy (for testing the eCxx library in the host PC)
        #define F_CPU 1000000000UL
    #else
        // Error if F_CPU is not defined
        #error "F_CPU is not defined"
    #endif
#endif


//
// Turn off this warning as needed
//
#if __GNUC__ >= 6
    #pragma GCC diagnostic ignored "-Wmisleading-indentation"
#endif
#if __GNUC__ >= 8
    #pragma GCC diagnostic ignored "-Wint-in-bool-context"
    #pragma GCC diagnostic ignored "-Wstringop-truncation"
#endif


//
// Stringify
//
#define __ECXX_STRINGIFY_I(T) #T
#define __ECXX_STRINGIFY(T)   __ECXX_STRINGIFY_I(T)

#undef STRINGIFY
#define STRINGIFY_I(...) #__VA_ARGS__
#define STRINGIFY(...)   STRINGIFY_I(__VA_ARGS__)


//
// Check if the given template argument is a char
//
#define ECXX_CHECK_IF_TEMPLATE_ARG_IS_CHAR(ARG)                           \
    static_assert(                                                        \
        eCxx::isSame<ARG, char>::Value,                                   \
        "template argument substituting " STRINGIFY(ARG) " is not a char" \
    )


//
// Check if the given template argument is a PGMCharPointer/PGMCharPointerFar
//
#define ECXX_CHECK_IF_TEMPLATE_ARG_IS_PGM_CHAR_POINTER(ARG)                                           \
    static_assert(                                                                                    \
        eCxx::isSame<ARG, PGMCharPointer>::Value || eCxx::isSame<ARG, PGMCharPointerFar>::Value,      \
        "template argument substituting " STRINGIFY(ARG) " is not a PGMCharPointer/PGMCharPointerFar" \
    )


//
// Attributes
//
#define __ECXX_ATTR_MODE_QI       __attribute__((mode(QI))) /* Should be  8 bits */
#define __ECXX_ATTR_MODE_HI       __attribute__((mode(HI))) /* Should be 16 bits */
#define __ECXX_ATTR_MODE_SI       __attribute__((mode(SI))) /* Should be 32 bits */
#define __ECXX_ATTR_MODE_DI       __attribute__((mode(DI))) /* Should be 64 bits */

#define __ECXX_ATTR_MODE_SF       __attribute__((mode(SF))) /* Usually 32 bits */
#define __ECXX_ATTR_MODE_DF       __attribute__((mode(DF))) /* Usually 64 bits */

#define __ECXX_ATTR_PACKED        __attribute__((packed))
#define __ECXX_ATTR_ALIGNED_4     __attribute__((aligned(4)))
#define __ECXX_ATTR_ALIGNED_8     __attribute__((aligned(8)))
#define __ECXX_ATTR_ALIGNED_16    __attribute__((aligned(16)))
#define __ECXX_ATTR_ALIGNED_T(T)  __attribute__((aligned(sizeof(T))))

#define __EXCC_ATTR_PRINTF(S, F)  __attribute__((format(printf,S,F)))

#define __ECXX_ATTR_PURE          __attribute__((pure))
#define __ECXX_ATTR_CONST         __attribute__((const))
#define __ECXX_ATTR_NORETURN      __attribute__((noreturn))

#define __ECXX_ATTR_NAKED         __attribute__((naked))
#define __ECXX_ATTR_ALWAYS_INLINE __attribute__((always_inline))
#define __ECXX_ATTR_NOINLINE      __attribute__((noinline))

#define __ECXX_ATTR_OPTIMIZE_S    __attribute__((optimize("Os")))
#define __ECXX_ATTR_OPTIMIZE_2    __attribute__((optimize("O2")))

#define __ECXX_ATTR_USED          __attribute__((used))
#define __ECXX_ATTR_WEAK          __attribute__((weak))

#define __ECXX_ATTR_ALIAS(NAME)   __attribute__((alias(NAME)))
#define __ECXX_ATTR_WEAKREF(NAME) __attribute__((weakref(NAME)))

#define __ECXX_ATTR_SEC_NOINIT    __attribute__((section(".noinit")))
#define __ECXX_ATTR_SEC_INIT3     __attribute__((section(".init3")))
#define __ECXX_ATTR_SEC_FINI2     __attribute__((section(".fini2")))

#define __ECXX_ATTR_DEPRECATED(S) __attribute__((deprecated(S)))


//
// For compatibility with the Arduino library part
//

// NodeMCU-ESP8266 only
#ifdef __XTENSA__
class __FlashStringHelper;
#endif


namespace eCxx {


//
// NOTE : Whenever possible, use the near program pointer because the far one has more overhead
//        and restrictions.
//


//
// Wrapper class for program-memory pointers (normal)
//
template <typename PGMPointerT>
struct BasicPGMPointer {
    using _PGMPointerType = PGMPointerT;

    static constexpr nullptr_t null = nullptr;

    PGMPointerT p;
                                    constexpr explicit BasicPGMPointer(                PGMPointerT  p_);
    template <typename PGMPointerU> constexpr          BasicPGMPointer(BasicPGMPointer<PGMPointerU> p_);
                                    constexpr          BasicPGMPointer(           decltype(nullptr)   );

    constexpr const BasicPGMPointer offset(size_t ofs) const;

    // NodeMCU-ESP8266 only
    #ifdef __XTENSA__
                                    constexpr          operator const __FlashStringHelper*() const;
    #endif
};


template <typename PGMPointerT>
constexpr BasicPGMPointer<PGMPointerT>::BasicPGMPointer(PGMPointerT p_)
: p(p_)
{}


template <typename PGMPointerT> template <typename PGMPointerU>
constexpr BasicPGMPointer<PGMPointerT>::BasicPGMPointer(BasicPGMPointer<PGMPointerU> p_)
: p( reinterpret_cast<PGMPointerT>(p_.p) )
{}


template <typename PGMPointerT>
constexpr BasicPGMPointer<PGMPointerT>::BasicPGMPointer( decltype(nullptr) )
: p(nullptr)
{}


template <typename PGMPointerT>
constexpr const BasicPGMPointer<PGMPointerT> BasicPGMPointer<PGMPointerT>::offset(size_t ofs) const
{ return BasicPGMPointer(p + ofs); }


// NodeMCU-ESP8266 only
#ifdef __XTENSA__
template <typename PGMPointerT>
constexpr BasicPGMPointer<PGMPointerT>::operator const __FlashStringHelper*() const
{ return reinterpret_cast<const __FlashStringHelper*>(p); }
#endif


//
// Wrapper class for program-memory pointers (far)
//
template <typename PGMPointerFarT>
struct BasicPGMPointerFar {
    using _PGMPointerType = PGMPointerFarT;

    static constexpr PGMPointerFarT null = 0;

    PGMPointerFarT p;
                                       constexpr explicit BasicPGMPointerFar(                   PGMPointerFarT  p_);
    template <typename PGMPointerFarU> constexpr          BasicPGMPointerFar(BasicPGMPointerFar<PGMPointerFarU> p_);
                                       constexpr          BasicPGMPointerFar(                 decltype(nullptr)   );

    constexpr const BasicPGMPointerFar offset(size_t ofs) const;

    // NodeMCU-ESP8266 only
    #ifdef __XTENSA__
                                       constexpr          operator const __FlashStringHelper*() const;
    #endif
};


template <typename PGMPointerFarT>
constexpr BasicPGMPointerFar<PGMPointerFarT>::BasicPGMPointerFar(PGMPointerFarT p_)
: p(p_)
{}


template <typename PGMPointerFarT> template <typename PGMPointerU>
constexpr BasicPGMPointerFar<PGMPointerFarT>::BasicPGMPointerFar(BasicPGMPointerFar<PGMPointerU> p_)
: p( reinterpret_cast<PGMPointerFarT>(p_.p) )
{}


template <typename PGMPointerT>
constexpr BasicPGMPointerFar<PGMPointerT>::BasicPGMPointerFar( decltype(nullptr) )
: p(nullptr)
{}


template <typename PGMPointerT>
constexpr const BasicPGMPointerFar<PGMPointerT> BasicPGMPointerFar<PGMPointerT>::offset(size_t ofs) const
{ return BasicPGMPointerFar(p + ofs); }


// NodeMCU-ESP8266 only
#ifdef __XTENSA__
template <typename PGMPointerT>
constexpr BasicPGMPointerFar<PGMPointerT>::operator const __FlashStringHelper*() const
{ return reinterpret_cast<const __FlashStringHelper*>(p); }
#endif


} // namespace eCxx


//
// Check for N3599 extension (it seems that all standard GCC versions in C++14 mode also support the extension)
//
#if defined(__GNUC__) && (__cplusplus >= 201300L)
    #define ECXX_CXXCOMP_HAVE_N3599
#endif


//
// Include the platform-specific's header files
//
#define INCLUDE_ECXX_PLATFORM_SPECIFIC_CONFIG_H

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

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

#undef INCLUDE_ECXX_PLATFORM_SPECIFIC_CONFIG_H


//
// If the platform-specific's instruction-caching macros have not been defined, define the generic versions here
//
#ifndef ECXX_ICACHE_FMACROS_DEFINED

    // Inform that the platform-specific's instruction-caching macros have been defined here
    #define ECXX_ICACHE_FMACROS_DEFINED

    #define ICACHE_FLASH_ATTR
    #define ICACHE_RAM_ATTR

#endif


//
// ICACHE_FLASH_ATTR and ICACHE_RAM_ATTR for lambda functions
//
#if __GNUC__ >= 6
    #define ICACHE_FLASH_ATTR_LAMBDA_B ICACHE_FLASH_ATTR
    #define ICACHE_FLASH_ATTR_LAMBDA_E
#else
    #define ICACHE_FLASH_ATTR_LAMBDA_B
    #define ICACHE_FLASH_ATTR_LAMBDA_E ICACHE_FLASH_ATTR
#endif

#if __GNUC__ >= 6
    #define ICACHE_RAM_ATTR_LAMBDA_B ICACHE_RAM_ATTR
    #define ICACHE_RAM_ATTR_LAMBDA_E
#else
    #define ICACHE_RAM_ATTR_LAMBDA_B
    #define ICACHE_RAM_ATTR_LAMBDA_E ICACHE_RAM_ATTR
#endif


//
// If the platform-specific's RAM size have not been defined, define the generic versions here
//
#ifndef RAM_SIZE
    // Available RAM size (dummy; for testing the eCxx library in the host PC)
    #define RAM_SIZE    1073741824UL
    #define RAM_SIZE_KB (RAM_SIZE / 1024)
#endif


//
// If the platform-specific's program-memory macros have not been defined, define the generic versions here
//
#ifndef ECXX_PROGMEM_FMACROS_NEAR_DEFINED

    // Inform that the platform-specific's program-memory macros have been defined here
    #define ECXX_PROGMEM_FMACROS_NEAR_DEFINED

    // Attribute to put constants in program memory (the generic implementation of this macro does nothing)
    #ifndef PROGMEM
        #define PROGMEM
    #endif

    #ifndef PROGMEM_FAR
        #define PROGMEM_FAR PROGMEM
    #endif

    // Macro to put inline string literals in program memory (normal) (the generic implementation of this macro does nothing)
    #ifndef PGM_STR
        #define PGM_STR(STR) eCxx::PGM_PTR(STR)
    #endif

#endif // ECXX_PROGMEM_FMACROS_NEAR_DEFINED

#ifndef ECXX_PROGMEM_FMACROS_FAR_DEFINED

    // Inform that the platform-specific's program-memory macros have been defined here
    #define ECXX_PROGMEM_FMACROS_FAR_DEFINED

    // Macro to put inline string literals in program memory (far) (the generic implementation of this macro does nothing)
    #ifndef PGM_STR_FAR
        #define PGM_STR_FAR(STR) eCxx::PGM_PTR_FAR(STR)
    #endif

#endif // ECXX_PROGMEM_FMACROS_FAR_DEFINED


namespace eCxx {


//
// If the platform-specific's program-memory pointers have not been defined, define the generic versions here
//
#ifndef ECXX_PROGMEM_POINTER_NEAR_TYPE_DEFINED

    // Inform that the platform-specific's program-memory pointers have been defined here
    #define ECXX_PROGMEM_POINTER_NEAR_TYPE_DEFINED

    // Define the program-memory-pointers using their generic types
    typedef const void* _PGMVoidPointer;
    typedef const char* _PGMCharPointer;

#endif // ECXX_PROGMEM_POINTER_NEAR_TYPE_DEFINED

#ifndef ECXX_PROGMEM_POINTER_FAR_TYPE_DEFINED

    // Inform that the platform-specific's program-memory pointers have been defined here
    #define ECXX_PROGMEM_POINTER_FAR_TYPE_DEFINED

    // Define the program-memory-pointers using their generic types
    typedef const void* _PGMVoidPointerFar;
    typedef const char* _PGMCharPointerFar;

#endif // ECXX_PROGMEM_POINTER_FAR_TYPE_DEFINED


//
// If the platform-specific's program-memory translators have not been defined, define the generic versions here
//
#ifndef ECXX_PROGMEM_TRANSLATOR_NEAR_DEFINED

    // Inform that the platform-specific's program-memory translator have been defined here
    #define ECXX_PROGMEM_TRANSLATOR_NEAR_DEFINED

    // Compute the near-address of the given address (the generic implementation of this translator does nothing)
    template <typename ValueT>
    constexpr BasicPGMPointer<const ValueT*> PGM_PTR(const ValueT* ptr)
    { return BasicPGMPointer<const ValueT*>(ptr); }

    // Compute the near-address of the given address (the generic implementation of this translator does nothing)
    template <typename ValueT, size_t N>
    constexpr BasicPGMPointer<const ValueT*> PGM_PTR(const ValueT (&ptr)[N], size_t idx)
    { return BasicPGMPointer<const ValueT*>(&ptr[idx]); }

#endif // ECXX_PROGMEM_TRANSLATOR_NEAR_DEFINED

#ifndef ECXX_PROGMEM_TRANSLATOR_FAR_DEFINED

    // Inform that the platform-specific's program-memory translator have been defined here
    #define ECXX_PROGMEM_TRANSLATOR_FAR_DEFINED

    // Compute the far-address of the given address (the generic implementation of this translator does nothing)
    template <typename ValueT>
    constexpr BasicPGMPointerFar<const ValueT*> PGM_PTR_FAR(const ValueT* ptr)
    { return BasicPGMPointerFar<const ValueT*>(ptr); }

    // Compute the far-address of the given address (the generic implementation of this translator does nothing)
    template <typename ValueT, size_t N>
    constexpr BasicPGMPointerFar<const ValueT*> PGM_PTR_FAR(const ValueT (&ptr)[N], size_t idx)
    { return BasicPGMPointerFar<const ValueT*>(&ptr[idx]); }

#endif // ECXX_PROGMEM_TRANSLATOR_FAR_DEFINED


//
// Define the _BV() macro if it has not been defined (for testing the eCxx library in the host PC)
//
#ifndef _BV
    #define _BV(B) ( 1UL << (B) )
#endif


//
// Define the wrapped program-memory pointers
//
typedef BasicPGMPointer<_PGMVoidPointer> PGMVoidPointer;
typedef BasicPGMPointer<_PGMCharPointer> PGMCharPointer;

typedef BasicPGMPointerFar<_PGMVoidPointerFar> PGMVoidPointerFar;
typedef BasicPGMPointerFar<_PGMCharPointerFar> PGMCharPointerFar;


//
// Forward declaration of platform-specific's program-memory functors
//
struct PGMReadByte;
struct PGMReadWord;
struct PGMReadDWord;

struct PGMMemCpy;
struct PGMStrNCpy;
struct PGMStrNCmp;
struct PGMStrNLen;


//
// __if_constexpr
//
#if __cplusplus >= 201703L
    #define __if_constexpr if constexpr
#else
    #define __if_constexpr if
#endif


//
// launder<>()
//
#if __cplusplus >= 201703L
    #if __GNUC__ >= 7
        #define ECXX_CXXCOMP_HAVE_BUILTIN_LAUNDER
    #elif defined(__is_identifier) && defined(__has_builtin)
        #if __has_builtin(__builtin_launder)
            #define ECXX_CXXCOMP_HAVE_BUILTIN_LAUNDER
        #endif
    #endif
#endif

#if __cpp_noexcept_function_type
    #define _CXXCOMP_NE_P , bool _NE
    #define _CXXCOMP_NE_Q noexcept(_NE)
#else
    #define _CXXCOMP_NE_P
    #define _CXXCOMP_NE_Q
#endif

template <typename ValueT>
constexpr ValueT* launder(ValueT* ptr) noexcept
{
#ifdef ECXX_CXXCOMP_HAVE_BUILTIN_LAUNDER
    return __builtin_launder(ptr);
#else
    return ptr;
#endif
}

template<typename RetT, typename... Args _CXXCOMP_NE_P> void launder( RetT (*)(Args...   ) _CXXCOMP_NE_Q ) = delete;
template<typename RetT, typename... Args _CXXCOMP_NE_P> void launder( RetT (*)(Args......) _CXXCOMP_NE_Q ) = delete;

void launder(               void*) = delete;
void launder(const          void*) = delete;
void launder(      volatile void*) = delete;
void launder(const volatile void*) = delete;


} // namespace eCxx


#endif
