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


#ifndef ECXX_ECXX_INTERNAL_H
#define ECXX_ECXX_INTERNAL_H


//
// Include the platform-specific header file
//
#define INCLUDE_ECXX_PLATFORM_PLATFORM_H
#include "Platform/Platform.h"


//
// Include only the absolutely needed CTL header files
//
#include <eCxx/CTL/Function.h>
#include <eCxx/CTL/NumericLimits.h>


//
// Include the definition of platform-specific's program-memory functors
//
#define INCLUDE_ECXX_PLATFORM_SPECIFIC_PROGMEM_FUNCTOR_H

// AVR
#ifdef __AVR_ARCH__
#ifndef ECXX_AVRX_DO_NOT_USE_PROGMEM
    #include "Platform/AVRCommon/ProgMem_Functor.h"
#endif
#endif

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

// Include a generic implementation of program-memory functors for platforms that do not
// have their own implementation (e.g. for testing the eCxx library in the host PC)
#ifdef INCLUDE_ECXX_PLATFORM_SPECIFIC_PROGMEM_FUNCTOR_H
    #include "Platform/Common/ProgMem_Functor.h"
#endif


//
// Include the definition of platform-specific's literal operators to put inline string literals in program memory
//
#define INCLUDE_ECXX_PLATFORM_SPECIFIC_PROG_STR_LITERAL_H

// AVR
#ifdef __AVR_ARCH__
#ifndef ECXX_AVRX_DO_NOT_USE_PROGMEM
    #include "Platform/AVRCommon/ProgStr_Literal.h"
#endif
#endif

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

// Include a generic implementation of the literal operators for platforms that do not
// have their own implementation (e.g. for testing the eCxx library in the host PC)
#ifdef INCLUDE_ECXX_PLATFORM_SPECIFIC_PROG_STR_LITERAL_H
    #include "Platform/Common/ProgStr_Literal.h"
#endif

// Define the feature macro as needed
#ifdef ECXX_PROGRAM_STRING_LITERALS_DEFINED
    #define ECXX_HAVE_PROGRAM_STRING_LITERALS
#endif


namespace eCxx {


//
// stdPGMCharPointer<T>() and stdPGMCharPointerFar<T>()
//
// NOTE : Unlike eCxx::PGM_PTR<T>() and eCxx::PGM_PTR_FAR<T>() that will return BasicPGMPointer<T> and BasicPGMPointerFar<T>,
//        these functions will always return PGMCharPointer and PGMCharPointerFar.
//
namespace internal {

    template <typename ValueT>
    struct MakeStdPGMCharPointer;

    template <>
    struct MakeStdPGMCharPointer< BasicPGMPointer   <const          char*> > { typedef PGMCharPointer    Type; };

    template <>
    struct MakeStdPGMCharPointer< BasicPGMPointer   <const   signed char*> > { typedef PGMCharPointer    Type; };

    template <>
    struct MakeStdPGMCharPointer< BasicPGMPointer   <const unsigned char*> > { typedef PGMCharPointer    Type; };

#ifdef __AVR_HAVE_ELPM__

    template <>
    struct MakeStdPGMCharPointer< BasicPGMPointerFar<_PGMCharPointerFar  > > { typedef PGMCharPointerFar Type; };

#else

    template <>
    struct MakeStdPGMCharPointer< BasicPGMPointerFar<const          char*> > { typedef PGMCharPointerFar Type; };

    template <>
    struct MakeStdPGMCharPointer< BasicPGMPointerFar<const   signed char*> > { typedef PGMCharPointerFar Type; };

    template <>
    struct MakeStdPGMCharPointer< BasicPGMPointerFar<const unsigned char*> > { typedef PGMCharPointerFar Type; };

#endif

    template <typename ValueT>
    constexpr typename MakeStdPGMCharPointer<ValueT>::Type makeStdPGMCharPointer(ValueT ptr)
    { return typename MakeStdPGMCharPointer<ValueT>::Type(ptr); }

} // namespace internal

template <typename ValueT>
constexpr PGMCharPointer stdPGMCharPointer(const ValueT* ptr)
{ return eCxx::internal::makeStdPGMCharPointer( eCxx::PGM_PTR(ptr) ); }

template <typename ValueT, size_t N>
constexpr PGMCharPointer stdPGMCharPointer(const ValueT (&ptr)[N], size_t idx)
{ return eCxx::internal::makeStdPGMCharPointer( eCxx::PGM_PTR(ptr, idx) ); }

template <typename ValueT>
constexpr PGMCharPointerFar stdPGMCharPointerFar(const ValueT* ptr)
{ return eCxx::internal::makeStdPGMCharPointer( eCxx::PGM_PTR_FAR(ptr) ); }

template <typename ValueT, size_t N>
constexpr PGMCharPointerFar stdPGMCharPointerFar(const ValueT (&ptr)[N], size_t idx)
{ return eCxx::internal::makeStdPGMCharPointer( eCxx::PGM_PTR_FAR(ptr, idx) ); }


//
// Program-memory functors for generic integral values
//
namespace internal {

    template <typename ValueT, size_t ByteSize>
    struct PGMReadValueImpl;

    template <typename ValueT>
    struct PGMReadValueImpl<ValueT, 1> {
        inline ValueT operator()(_PGMVoidPointer ptr) const noexcept
        { return PGMReadByte()(ptr); }

#ifdef __AVR_HAVE_ELPM__
        inline ValueT operator()(_PGMVoidPointerFar ptr) const noexcept
        { return PGMReadByte()(ptr); }
#endif
    };

    template <typename ValueT>
    struct PGMReadValueImpl<ValueT, 2> {
        inline ValueT operator()(_PGMVoidPointer ptr) const noexcept
        { return PGMReadWord()(ptr); }

#ifdef __AVR_HAVE_ELPM__
        inline ValueT operator()(_PGMVoidPointerFar ptr) const noexcept
        { return PGMReadWord()(ptr); }
#endif
    };

    template <typename ValueT>
    struct PGMReadValueImpl<ValueT, 4> {
        inline ValueT operator()(_PGMVoidPointer ptr) const noexcept
        { return PGMReadDWord()(ptr); }

#ifdef __AVR_HAVE_ELPM__
        inline ValueT operator()(_PGMVoidPointerFar ptr) const noexcept
        { return PGMReadDWord()(ptr); }
#endif
    };

} // namespace internal

template <typename ValueT>
struct PGMReadValue : internal::PGMReadValueImpl<ValueT, sizeof(ValueT)> {};


//
// Memory and program-memory functors for constant character values
//
template <typename ReturnValueT, typename PointerT>
struct PGMReadCharPtr;

template <typename ReturnValueT>
struct PGMReadCharPtr<ReturnValueT, const char*> {
    static inline ReturnValueT _(const char* ptr) { return *ptr; }
};

template <typename ReturnValueT>
struct PGMReadCharPtr<ReturnValueT, const signed char*> {
    static inline ReturnValueT _(const signed char* ptr) { return *ptr; }
};

template <typename ReturnValueT>
struct PGMReadCharPtr<ReturnValueT, const unsigned char*> {
    static inline ReturnValueT _(const unsigned char* ptr) { return *ptr; }
};

template <typename ReturnValueT>
struct PGMReadCharPtr<ReturnValueT, PGMCharPointer> {
    static inline ReturnValueT _(PGMCharPointer ptr) { return PGMReadValue<ReturnValueT>()(ptr.p); }
};

template <typename ReturnValueT>
struct PGMReadCharPtr<ReturnValueT, PGMCharPointerFar> {
    static inline ReturnValueT _(PGMCharPointerFar ptr) { return PGMReadValue<ReturnValueT>()(ptr.p); }
};


template <typename ReturnValueT, typename PointerT>
struct PGMReadCharPtrInc;

template <typename ReturnValueT>
struct PGMReadCharPtrInc<ReturnValueT, const char*> {
    static inline ReturnValueT _(const char*& ptr) { return *ptr++; }
};

template <typename ReturnValueT>
struct PGMReadCharPtrInc<ReturnValueT, const signed char*> {
    static inline ReturnValueT _(const signed char*& ptr) { return *ptr++; }
};

template <typename ReturnValueT>
struct PGMReadCharPtrInc<ReturnValueT, const unsigned char*> {
    static inline ReturnValueT _(const unsigned char*& ptr) { return *ptr++; }
};

template <typename ReturnValueT>
struct PGMReadCharPtrInc<ReturnValueT, PGMCharPointer> {
    static inline ReturnValueT _(PGMCharPointer& ptr) { return PGMReadValue<ReturnValueT>()(ptr.p++); }
};

template <typename ReturnValueT>
struct PGMReadCharPtrInc<ReturnValueT, PGMCharPointerFar> {
    static inline ReturnValueT _(PGMCharPointerFar& ptr) { return PGMReadValue<ReturnValueT>()(ptr.p++); }
};


//
// Some helper functions
//
static inline void memcpy_P(void* dst, PGMVoidPointer src, size_t len, size_t ofs = 0)
{ PGMMemCpy()( dst, static_cast<const char*>(src.p) + ofs, len ); }

static inline void memcpy_P(void* dst, PGMVoidPointerFar src, size_t len, size_t ofs = 0)
#ifdef __AVR_HAVE_ELPM__
{ PGMMemCpy()( dst, src.p + ofs, len ); }
#else
{ PGMMemCpy()( dst, static_cast<const char*>(src.p) + ofs, len ); }
#endif

static inline void strncpy_P(char* dst, PGMCharPointer src, size_t max)
{ PGMStrNCpy()( dst, src.p, max ); }

static inline void strncpy_P(char* dst, PGMCharPointerFar src, size_t max)
{ PGMStrNCpy()( dst, src.p, max ); }


static inline int strncmp_P(const char* ptr1, PGMCharPointer ptr2, size_t max)
{ return PGMStrNCmp()( ptr1, ptr2.p, max ); }

static inline int strncmp_P(const char* ptr1, PGMCharPointerFar ptr2, size_t max)
{ return PGMStrNCmp()( ptr1, ptr2.p, max ); }


static inline size_t strncmp_P(PGMCharPointer ptr, size_t max)
{ return PGMStrNLen()( ptr.p, max ); }

static inline size_t strncmp_P(PGMCharPointerFar ptr, size_t max)
{ return PGMStrNLen()( ptr.p, max ); }


} // namespace eCxx


#endif
