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


#ifndef ECXX_CTL_BASIC_OUTPUT_WRITER_H
#define ECXX_CTL_BASIC_OUTPUT_WRITER_H


#include <stdarg.h>

#include <eCxx/CTL/Convert.h>
#include <eCxx/CTL/Function.h>
#include <eCxx/CTL/NumPunct.h>


namespace eCxx {


// NodeMCU-ESP8266 only
#ifdef __XTENSA__
    namespace NodeMCU { class WString; }
#endif


// Function prototype from "CTL/VPrintF.cpp"
extern void vprintf(eCxx::function<void(uint8_t)> write, const char* format, va_list va);


//
// An base class for writing output
//
// WARNING : All functions that accept parameters with data types 'PGMCharPointer' and 'PGMCharPointerFar'
//           expect the pointers to come from program memory exclusively!
//
// NOTE    : Whenever possible, use the versions that accept near program pointers because those that
//           accept the far program pointers may have more overhead and restrictions.
//
template <typename ImplementationT>
class BasicOutputWriter : public Uncopyable {
    public:
        typedef String<0>::ConstIterator StringConstIterator;

    public:
        inline ~BasicOutputWriter() = default;

    private:
        inline void _printfString(const char* format, va_list va);

        template <typename PGMPointerT, typename PGMStrNCpyFunctorT>
        inline void _printfString(PGMPointerT format, va_list va);

    public:
        /*
         * # These are the wrapper functions for the simplified version of C's vprintf() function:
         *       + No floating-point support.
         *       + The maximum width field for any format specifier is 255 characters.
         *       + The maximum string-argument length supported by the "%s" format is 255 characters.
         *       + Some of the specifiers would produce slightly simpler/different results.
         * # The size of the vprintf() function can be quite large, please see "src/eCxx/CTL/VPrintF.cpp"
         *   for more details.
         */
                                                            inline void printfString(const char*       format, ...) __EXCC_ATTR_PRINTF(2, 3);
        template <typename PGMStrNCpyFunctorT = PGMStrNCpy> inline void printfString(PGMCharPointer    format, ...);
        template <typename PGMStrNCpyFunctorT = PGMStrNCpy> inline void printfString(PGMCharPointerFar format, ...);

        template <size_t MaxCapacity> inline void writeString(const String<MaxCapacity>& str);
        template <size_t MaxCapacity> inline void writeStringln(const String<MaxCapacity>& str);

        // NodeMCU-ESP8266 only
    #ifdef __XTENSA__
        inline void writeString(const NodeMCU::WString& str);
        inline void writeStringln(const NodeMCU::WString& str);
    #endif

        inline void writeString(const char* cstr);
        inline void writeStringln(const char* cstr);

        inline void writeString(const char* cstr, size_t maxSize);
        inline void writeStringln(const char* cstr, size_t maxSize);

        inline void writeString(StringConstIterator cbeg, StringConstIterator cend);
        inline void writeStringln(StringConstIterator cbeg, StringConstIterator cend);

        inline void writeBytes(const uint8_t* cbeg, const uint8_t* cend);
        inline void writeBytesln(const uint8_t* cbeg, const uint8_t* cend);

        inline void writeBytes(const uint8_t* cbeg, size_t count);
        inline void writeBytesln(const uint8_t* cbeg, size_t count);

        template <typename PGMPointerT, typename PGMReadByteFunctorT>
        inline void _writeString(PGMPointerT ptr, size_t lenMax);

        template <typename PGMReadByteFunctorT = PGMReadByte> inline void writeString(PGMCharPointer    ptr, size_t lenMax = -1);
        template <typename PGMReadByteFunctorT = PGMReadByte> inline void writeString(PGMCharPointerFar ptr, size_t lenMax = -1);

        template <typename PGMPointerT, typename PGMReadByteFunctorT>
        inline void _writeStringln(PGMPointerT ptr, size_t lenMax);

        template <typename PGMReadByteFunctorT = PGMReadByte> inline void writeStringln(PGMCharPointer    ptr, size_t lenMax = -1);
        template <typename PGMReadByteFunctorT = PGMReadByte> inline void writeStringln(PGMCharPointerFar ptr, size_t lenMax = -1);

        inline void writeString(Bool value);
        inline void writeStringln(Bool value);

        inline void writeChrSeqString(const uint8_t* cbeg, const uint8_t* cend, bool spacing = true);
        inline void writeChrSeqStringln(const uint8_t* cbeg, const uint8_t* cend, bool spacing = true);

        inline void writeChrSeqString(const uint8_t* cbeg, size_t count, bool spacing = true);
        inline void writeChrSeqStringln(const uint8_t* cbeg, size_t count, bool spacing = true);

        template <typename ValueT> inline void writeBinString(
            ValueT             value,
            Convert::NumSign   numSign       = Convert::NumSign::None_Minus,
            uint8_t            minFieldWidth = 0,
            Convert::NumPad    numPad        = Convert::NumPad::Space,
            Convert::NumFormat format        = Convert::NumFormat::LowercaseWithPrefix
        );

        template <typename ValueT> inline void writeBinString(
            ValueT             value,
            uint8_t            minFieldWidth,
            Convert::NumFormat format  = Convert::NumFormat::LowercaseWithPrefix,
            Convert::NumSign   numSign = Convert::NumSign::None_Minus
        );

        inline void writeBinSeqString(
            const uint8_t* cbeg,
            uint8_t        count,
            bool           spacing
        );

        template <typename ValueT> inline void writeBinSeqString(
            const ValueT& value,
            bool          spacing
        );

        template <typename ValueT> inline void writeOctString(
            ValueT             value,
            Convert::NumSign   numSign       = Convert::NumSign::None_Minus,
            uint8_t            minFieldWidth = 0,
            Convert::NumPad    numPad        = Convert::NumPad::Space,
            Convert::NumFormat format        = Convert::NumFormat::LowercaseWithPrefix
        );

        template <typename ValueT> inline void writeOctString(
            ValueT             value,
            uint8_t            minFieldWidth,
            Convert::NumFormat format  = Convert::NumFormat::LowercaseWithPrefix,
            Convert::NumSign   numSign = Convert::NumSign::None_Minus
        );

        inline void writeOctSeqString(
            const uint8_t* cbeg,
            uint8_t        count,
            bool           spacing
        );

        template <typename ValueT> inline void writeOctSeqString(
            const ValueT& value,
            bool          spacing
        );

        template <typename ValueT> inline void writeDecString(
            ValueT           value,
            Convert::NumSign numSign            = Convert::NumSign::None_Minus,
            uint8_t          minFieldWidth      = 0,
            Convert::NumPad  numPad             = Convert::NumPad::Space,
            bool             thousandsSeparator = false
        );

        template <typename ValueT> inline void writeDecString(
            ValueT           value,
            uint8_t          minFieldWidth,
            Convert::NumSign numSign = Convert::NumSign::None_Minus
        );

        inline void writeDecSeqString(
            const uint8_t* cbeg,
            uint8_t        count,
            bool           spacing
        );

        template <typename ValueT> inline void writeDecSeqString(
            const ValueT& value,
            bool          spacing
        );

        template <typename ValueT> inline void writeHexString(
            ValueT             value,
            Convert::NumSign   numSign       = Convert::NumSign::None_Minus,
            uint8_t            minFieldWidth = 0,
            Convert::NumPad    numPad        = Convert::NumPad::Space,
            Convert::NumFormat format        = Convert::NumFormat::LowercaseWithPrefix
        );

        template <typename ValueT> inline void writeHexString(
            ValueT             value,
            uint8_t            minFieldWidth,
            Convert::NumFormat format  = Convert::NumFormat::LowercaseWithPrefix,
            Convert::NumSign   numSign = Convert::NumSign::None_Minus
        );

        inline void writeHexSeqString(
            const uint8_t* cbeg,
            uint8_t        count,
            bool           uppercase,
            bool           spacing
        );

        template <typename ValueT> inline void writeHexSeqString(
            const ValueT& value,
            bool          uppercase,
            bool          spacing
        );

        template <typename ValueT> inline void writeBinStringln(
            ValueT             value,
            Convert::NumSign   numSign       = Convert::NumSign::None_Minus,
            uint8_t            minFieldWidth = 0,
            Convert::NumPad    numPad        = Convert::NumPad::Space,
            Convert::NumFormat format        = Convert::NumFormat::LowercaseWithPrefix
        );

        template <typename ValueT> inline void writeBinStringln(
            ValueT             value,
            uint8_t            minFieldWidth,
            Convert::NumFormat format  = Convert::NumFormat::LowercaseWithPrefix,
            Convert::NumSign   numSign = Convert::NumSign::None_Minus
        );

        inline void writeBinSeqStringln(
            const uint8_t* cbeg,
            uint8_t        count,
            bool           spacing
        );

        template <typename ValueT> inline void writeBinSeqStringln(
            const ValueT& value,
            bool          spacing
        );

        template <typename ValueT> inline void writeOctStringln(
            ValueT             value,
            Convert::NumSign   numSign       = Convert::NumSign::None_Minus,
            uint8_t            minFieldWidth = 0,
            Convert::NumPad    numPad        = Convert::NumPad::Space,
            Convert::NumFormat format        = Convert::NumFormat::LowercaseWithPrefix
        );

        template <typename ValueT> inline void writeOctStringln(
            ValueT             value,
            uint8_t            minFieldWidth,
            Convert::NumFormat format  = Convert::NumFormat::LowercaseWithPrefix,
            Convert::NumSign   numSign = Convert::NumSign::None_Minus
        );

        inline void writeOctSeqStringln(
            const uint8_t* cbeg,
            uint8_t        count,
            bool           spacing
        );

        template <typename ValueT> inline void writeOctSeqStringln(
            const ValueT& value,
            bool          spacing
        );

        template <typename ValueT> inline void writeDecStringln(
            ValueT           value,
            Convert::NumSign numSign            = Convert::NumSign::None_Minus,
            uint8_t          minFieldWidth      = 0,
            Convert::NumPad  numPad             = Convert::NumPad::Space,
            bool             thousandsSeparator = false
        );

        template <typename ValueT> inline void writeDecStringln(
            ValueT           value,
            uint8_t          minFieldWidth,
            Convert::NumSign numSign = Convert::NumSign::None_Minus
        );

        inline void writeDecSeqStringln(
            const uint8_t* cbeg,
            uint8_t        count,
            bool           spacing
        );

        template <typename ValueT> inline void writeDecSeqStringln(
            const ValueT& value,
            bool          spacing
        );

        template <typename ValueT> inline void writeHexStringln(
            ValueT             value,
            Convert::NumSign   numSign       = Convert::NumSign::None_Minus,
            uint8_t            minFieldWidth = 0,
            Convert::NumPad    numPad        = Convert::NumPad::Space,
            Convert::NumFormat format        = Convert::NumFormat::LowercaseWithPrefix
        );

        template <typename ValueT> inline void writeHexStringln(
            ValueT             value,
            uint8_t            minFieldWidth,
            Convert::NumFormat format  = Convert::NumFormat::LowercaseWithPrefix,
            Convert::NumSign   numSign = Convert::NumSign::None_Minus
        );

        inline void writeHexSeqStringln(
            const uint8_t* cbeg,
            uint8_t        count,
            bool           uppercase,
            bool           spacing
        );

        template <typename ValueT> inline void writeHexSeqStringln(
            const ValueT& value,
            bool          uppercase,
            bool          spacing
        );

    protected:
        inline BasicOutputWriter() = default;
};


//
// Include the implementation file
//
#define INCLUDE_ECXX_CTL_BASIC_OUTPUT_WRITER_TPP
#include "BasicOutputWriter.tpp"


} // namespace eCxx


#endif
