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


#ifndef INCLUDE_ECXX_CTL_BASIC_OUTPUT_WRITER_TPP
#error "this file cannot be included directly"
#endif
#undef INCLUDE_ECXX_CTL_BASIC_OUTPUT_WRITER_TPP


#ifndef ECXX_CTL_BASIC_OUTPUT_WRITER_TPP
#define ECXX_CTL_BASIC_OUTPUT_WRITER_TPP


//
// Helper functions
//
template <typename ValueT>
constexpr ValueT castBoolToUInt8(ValueT value)
{ return value; }

constexpr uint8_t castBoolToUInt8(Bool value)
{ return static_cast<uint8_t>( static_cast<bool>(value) ); }


//
// An base class for writing output
//
template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::_printfString(const char* format, va_list va)
{
    eCxx::vprintf(
        [this](uint8_t c) {
            static_cast<ImplementationT*>(this)->write(c);
        },
        format,
        va
    );
}


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::printfString(const char* format, ...)
{
    va_list va;
    va_start(va, format);
    _printfString(format, va);
    va_end(va);
}


template <typename ImplementationT> template <typename PGMPointerT, typename PGMStrNCpyFunctorT>
inline void BasicOutputWriter<ImplementationT>::_printfString(PGMPointerT format, va_list va)
{
    char fbuff[ eCxx::min<uint32_t>(256, RAM_SIZE_KB * 16) ];

    PGMStrNCpyFunctorT()( fbuff, format.p, sizeof(fbuff) );

    _printfString(fbuff, va);
}


template <typename ImplementationT> template <typename PGMStrNCpyFunctorT>
inline void BasicOutputWriter<ImplementationT>::printfString(PGMCharPointer format, ...)
{
    va_list va;
    va_start(va, format);
    _printfString<PGMCharPointer, PGMStrNCpyFunctorT>(format, va);
    va_end(va);
}


template <typename ImplementationT> template <typename PGMStrNCpyFunctorT>
inline void BasicOutputWriter<ImplementationT>::printfString(PGMCharPointerFar format, ...)
{
    va_list va;
    va_start(va, format);
    _printfString<PGMCharPointerFar, PGMStrNCpyFunctorT>(format, va);
    va_end(va);
}


template <typename ImplementationT> template <size_t MaxCapacity>
inline void BasicOutputWriter<ImplementationT>::writeString(const String<MaxCapacity>& str)
{ writeString( str.cbegin(), str.cend() ); }


template <typename ImplementationT> template <size_t MaxCapacity>
inline void BasicOutputWriter<ImplementationT>::writeStringln(const String<MaxCapacity>& str)
{ writeStringln( str.cbegin(), str.cend() ); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeString(const char* cstr)
{ while(*cstr) static_cast<ImplementationT*>(this)->write(*cstr++); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeStringln(const char* cstr)
{
    writeString(cstr);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeString(const char* cstr, size_t maxSize)
{
    for(size_t i = 0; i < maxSize; ++i) {
        if(!*cstr) break;
        static_cast<ImplementationT*>(this)->write(*cstr++);
    }
}


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeStringln(const char* cstr, size_t maxSize)
{
    writeString(cstr, maxSize);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeString(StringConstIterator cbeg, StringConstIterator cend)
{ while(cbeg != cend) static_cast<ImplementationT*>(this)->write(*cbeg++); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeStringln(StringConstIterator cbeg, StringConstIterator cend)
{
    writeString(cbeg, cend);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeBytes(const uint8_t* cbeg, const uint8_t* cend)
{ while(cbeg != cend) static_cast<ImplementationT*>(this)->write(*cbeg++); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeBytesln(const uint8_t* cbeg, const uint8_t* cend)
{
    writeBytes(cbeg, cend);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeBytes(const uint8_t* cbeg, size_t count)
{ writeBytes(cbeg, cbeg + count); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeBytesln(const uint8_t* cbeg, size_t count)
{ writeBytesln(cbeg, cbeg + count); }


template <typename ImplementationT> template <typename PGMPointerT, typename PGMReadByteFunctorT>
inline void BasicOutputWriter<ImplementationT>::_writeString(PGMPointerT ptr, size_t lenMax)
{
    const PGMReadByteFunctorT functor;

    for(size_t i = 0; i < lenMax; ++i) {
        const char ch = functor(ptr.p++);
        if(!ch) return;
        static_cast<ImplementationT*>(this)->write(ch);
    }
}


template <typename ImplementationT> template <typename PGMReadByteFunctorT>
inline void BasicOutputWriter<ImplementationT>::writeString(PGMCharPointer ptr, size_t lenMax)
{ _writeString<PGMCharPointer, PGMReadByteFunctorT>(ptr, lenMax); }


template <typename ImplementationT> template <typename PGMReadByteFunctorT>
inline void BasicOutputWriter<ImplementationT>::writeString(PGMCharPointerFar ptr, size_t lenMax)
{ _writeString<PGMCharPointerFar, PGMReadByteFunctorT>(ptr, lenMax); }


template <typename ImplementationT> template <typename PGMPointerT, typename PGMReadByteFunctorT>
inline void BasicOutputWriter<ImplementationT>::_writeStringln(PGMPointerT ptr, size_t lenMax)
{
    _writeString<PGMPointerT, PGMReadByteFunctorT>(ptr, lenMax);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT> template <typename PGMReadByteFunctorT>
inline void BasicOutputWriter<ImplementationT>::writeStringln(PGMCharPointer ptr, size_t lenMax)
{ _writeStringln<PGMCharPointer, PGMReadByteFunctorT>(ptr, lenMax); }


template <typename ImplementationT> template <typename PGMReadByteFunctorT>
inline void BasicOutputWriter<ImplementationT>::writeStringln(PGMCharPointerFar ptr, size_t lenMax)
{ _writeStringln<PGMCharPointerFar, PGMReadByteFunctorT>(ptr, lenMax); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeString(Bool value)
{ writeString( static_cast<bool>(value) ? NumPunct::trueName() : NumPunct::falseName() ); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeStringln(Bool value)
{
    writeString(value);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeChrSeqString(const uint8_t* cbeg, const uint8_t* cend, bool spacing)
{
    while(cbeg != cend) {
        static_cast<ImplementationT*>(this)->write(*cbeg++);
        if(spacing && cbeg < cend) static_cast<ImplementationT*>(this)->write(' ');
    }
}


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeChrSeqStringln(const uint8_t* cbeg, const uint8_t* cend, bool spacing)
{
    writeChrSeqString(cbeg, cend, spacing);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeChrSeqString(const uint8_t* cbeg, size_t count, bool spacing)
{ writeChrSeqString(cbeg, cbeg + count, spacing); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeChrSeqStringln(const uint8_t* cbeg, size_t count, bool spacing)
{ writeChrSeqStringln(cbeg, cbeg + count, spacing); }


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeBinString(
    ValueT           value,
    Convert::NumSign numSign, uint8_t            minFieldWidth,
    Convert::NumPad  numPad,  Convert::NumFormat format
)
{
    String<68> buff;

    Convert::toBinString(buff, castBoolToUInt8(value), numSign, minFieldWidth, numPad, format);
    writeString(buff);
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeBinString(
    ValueT             value,  uint8_t          minFieldWidth,
    Convert::NumFormat format, Convert::NumSign numSign
)
{ writeBinString(value, numSign, minFieldWidth, Convert::NumPad::Space, format); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeBinSeqString(const uint8_t* cbeg, uint8_t count, bool spacing)
{
    String<8> buff;

    for(uint8_t i = 0; i < count; ++i) {
        Convert::toBinString(
            buff,
            *cbeg++,
            Convert::NumSign::None_Minus,
            8,
            Convert::NumPad::Zero
        );
        static_cast<ImplementationT*>(this)->write(buff[0]);
        static_cast<ImplementationT*>(this)->write(buff[1]);
        static_cast<ImplementationT*>(this)->write(buff[2]);
        static_cast<ImplementationT*>(this)->write(buff[3]);
        static_cast<ImplementationT*>(this)->write(buff[4]);
        static_cast<ImplementationT*>(this)->write(buff[5]);
        static_cast<ImplementationT*>(this)->write(buff[6]);
        static_cast<ImplementationT*>(this)->write(buff[7]);
        if( spacing && i < (count - 1) ) static_cast<ImplementationT*>(this)->write(' ');
    }
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeBinSeqString(const ValueT& value, bool spacing)
{ writeBinSeqString( reinterpret_cast<const uint8_t*>(&value), sizeof(ValueT), spacing ); }


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeOctString(
    ValueT           value,
    Convert::NumSign numSign, uint8_t            minFieldWidth,
    Convert::NumPad  numPad,  Convert::NumFormat format
)
{
    String<68> buff;

    Convert::toOctString(buff, castBoolToUInt8(value), numSign, minFieldWidth, numPad, format);
    writeString(buff);
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeOctString(
    ValueT             value,  uint8_t          minFieldWidth,
    Convert::NumFormat format, Convert::NumSign numSign
)
{ writeOctString(value, numSign, minFieldWidth, Convert::NumPad::Space, format); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeOctSeqString(const uint8_t* cbeg, uint8_t count, bool spacing)
{
    String<3> buff;

    for(uint8_t i = 0; i < count; ++i) {
        Convert::toOctString(
            buff,
            *cbeg++,
            Convert::NumSign::None_Minus,
            3,
            Convert::NumPad::Zero
        );
        static_cast<ImplementationT*>(this)->write(buff[0]);
        static_cast<ImplementationT*>(this)->write(buff[1]);
        static_cast<ImplementationT*>(this)->write(buff[2]);
        if( spacing && i < (count - 1) ) static_cast<ImplementationT*>(this)->write(' ');
    }
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeOctSeqString(const ValueT& value, bool spacing)
{ writeOctSeqString( reinterpret_cast<const uint8_t*>(&value), sizeof(ValueT), spacing ); }


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeDecString(
    ValueT           value,
    Convert::NumSign numSign, uint8_t minFieldWidth,
    Convert::NumPad  numPad,  bool    thousandsSeparator
)
{
    String<68> buff;

    Convert::toDecString(buff, castBoolToUInt8(value), numSign, minFieldWidth, numPad, thousandsSeparator);
    writeString(buff);
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeDecString(
    ValueT  value,
    uint8_t minFieldWidth, Convert::NumSign numSign
)
{ writeDecString(value, numSign, minFieldWidth); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeDecSeqString(const uint8_t* cbeg, uint8_t count, bool spacing)
{
    String<3> buff;

    for(uint8_t i = 0; i < count; ++i) {
        Convert::toDecString(
            buff,
            *cbeg++,
            Convert::NumSign::None_Minus,
            3,
            Convert::NumPad::Zero
        );
        static_cast<ImplementationT*>(this)->write(buff[0]);
        static_cast<ImplementationT*>(this)->write(buff[1]);
        static_cast<ImplementationT*>(this)->write(buff[2]);
        if( spacing && i < (count - 1) ) static_cast<ImplementationT*>(this)->write(' ');
    }
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeDecSeqString(const ValueT& value, bool spacing)
{ writeDecSeqString( reinterpret_cast<const uint8_t*>(&value), sizeof(ValueT), spacing ); }


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeHexString(
    ValueT           value,
    Convert::NumSign numSign, uint8_t            minFieldWidth,
    Convert::NumPad  numPad,  Convert::NumFormat format
)
{
    String<68> buff;

    Convert::toHexString(buff, castBoolToUInt8(value), numSign, minFieldWidth, numPad, format);
    writeString(buff);
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeHexString(
    ValueT             value,  uint8_t          minFieldWidth,
    Convert::NumFormat format, Convert::NumSign numSign
)
{ writeHexString(value, numSign, minFieldWidth, Convert::NumPad::Space, format); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeHexSeqString(
    const uint8_t* cbeg,      uint8_t count,
    bool           uppercase, bool    spacing
)
{
    String<2> buff;

    for(uint8_t i = 0; i < count; ++i) {
        Convert::toHexString(
            buff,
            *cbeg++,
            Convert::NumSign::None_Minus,
            2,
            Convert::NumPad::Zero,
            uppercase ? Convert::NumFormat::Uppercase: Convert::NumFormat::Lowercase
        );
        static_cast<ImplementationT*>(this)->write(buff[0]);
        static_cast<ImplementationT*>(this)->write(buff[1]);
        if( spacing && i < (count - 1) ) static_cast<ImplementationT*>(this)->write(' ');
    }
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeHexSeqString(const ValueT& value, bool uppercase, bool spacing)
{ writeHexSeqString( reinterpret_cast<const uint8_t*>(&value), sizeof(ValueT), uppercase, spacing ); }


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeBinStringln(
    ValueT           value,
    Convert::NumSign numSign, uint8_t            minFieldWidth,
    Convert::NumPad  numPad,  Convert::NumFormat format
)
{
    writeBinString(value, numSign, minFieldWidth, numPad, format);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeBinStringln(
    ValueT             value,  uint8_t          minFieldWidth,
    Convert::NumFormat format, Convert::NumSign numSign
)
{ writeBinStringln(value, numSign, minFieldWidth, Convert::NumPad::Space, format); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeBinSeqStringln(const uint8_t* cbeg, uint8_t count, bool spacing)
{
    writeBinSeqString(cbeg, count, spacing);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeBinSeqStringln(const ValueT& value, bool spacing)
{ writeBinSeqStringln( reinterpret_cast<const uint8_t*>(&value), sizeof(ValueT), spacing ); }


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeOctStringln(
    ValueT           value,
    Convert::NumSign numSign, uint8_t            minFieldWidth,
    Convert::NumPad  numPad,  Convert::NumFormat format
)
{
    writeOctString(value, numSign, minFieldWidth, numPad, format);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeOctStringln(
    ValueT             value,  uint8_t          minFieldWidth,
    Convert::NumFormat format, Convert::NumSign numSign
)
{ writeOctStringln(value, numSign, minFieldWidth, Convert::NumPad::Space, format); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeOctSeqStringln(const uint8_t* cbeg, uint8_t count, bool spacing)
{
    writeOctSeqString(cbeg, count, spacing);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeOctSeqStringln(const ValueT& value, bool spacing)
{ writeOctSeqStringln( reinterpret_cast<const uint8_t*>(&value), sizeof(ValueT), spacing ); }


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeDecStringln(
    ValueT           value,
    Convert::NumSign numSign, uint8_t minFieldWidth,
    Convert::NumPad  numPad,  bool    thousandsSeparator
)
{
    writeDecString(value, numSign, minFieldWidth, numPad, thousandsSeparator);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeDecStringln(
    ValueT  value,
    uint8_t minFieldWidth, Convert::NumSign numSign
)
{ writeDecStringln(value, numSign, minFieldWidth); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeDecSeqStringln(const uint8_t* cbeg, uint8_t count, bool spacing)
{
    writeDecSeqString(cbeg, count, spacing);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeDecSeqStringln(const ValueT& value, bool spacing)
{ writeDecSeqStringln( reinterpret_cast<const uint8_t*>(&value), sizeof(ValueT), spacing ); }


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeHexStringln(
    ValueT           value,
    Convert::NumSign numSign, uint8_t            minFieldWidth,
    Convert::NumPad  numPad,  Convert::NumFormat format
)
{
    writeHexString(value, numSign, minFieldWidth, numPad, format);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeHexStringln(
    ValueT             value,  uint8_t          minFieldWidth,
    Convert::NumFormat format, Convert::NumSign numSign
)
{ writeHexStringln(value, numSign, minFieldWidth, Convert::NumPad::Space, format); }


template <typename ImplementationT>
inline void BasicOutputWriter<ImplementationT>::writeHexSeqStringln(
    const uint8_t* cbeg,      uint8_t count,
    bool           uppercase, bool    spacing
)
{
    writeHexSeqString(cbeg, count, uppercase, spacing);
    static_cast<ImplementationT*>(this)->write('\n');
}


template <typename ImplementationT> template <typename ValueT>
inline void BasicOutputWriter<ImplementationT>::writeHexSeqStringln(const ValueT& value, bool uppercase, bool spacing)
{ writeHexSeqStringln( reinterpret_cast<const uint8_t*>(&value), sizeof(ValueT), uppercase, spacing ); }


#endif
