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

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is copied (with a few modifications) from:
 *     Print.cpp - base class that provides print() and println()
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2008 David A. Mellis. All right reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * Modified November 2006 by David A. Mellis
 * Modified December 2014 by Ivan Grokhotkov
 * Modified May      2015 by Michael C. Miller - esp8266 progmem support
 * ----------------------------------------------------------------------------------
 */


#include "CoreSys.h"
#include "Print.h"


namespace eCxx {

namespace NodeMCU {


size_t Print::_printFloat(double n, uint8_t digits)
{
    char buf[48];
    return write( dtostrf(n, 0, digits, buf) );
}


template<typename ValueT>
size_t Print::_printUInt(ValueT n, uint8_t base)
{
    char         buf[ 8 * sizeof(long) + 1 ]; // 8-bit characters plus zero byte
    char *str = &buf[     sizeof(buf ) - 1 ];

    *str = '\0';

    // Esnure the base is valied
    if(base < 2) base = 10;

    do {
        const ValueT m = n;
        n /= base;
        const char c = m - base * n;
        *--str = (c < 10) ? (c + '0') : (c + 'A' - 10);
    } while(n);

    return write(str);
}


// This function should be overridden for better efficiency
size_t Print::write(const uint8_t* buffer, size_t size)
{
    size_t n = 0;

    while(size--) {
        const size_t ret = write( pgm_read_byte(buffer++) );
        if(!ret) break; // Write of last byte did not complete, abort additional processing
        n += ret;
    }

    return n;
}


size_t Print::printf(const char* format, ...)
{
    char  temp[64];
    char* buffer = temp;

    va_list arg;
    va_start(arg, format);
    size_t len = vsnprintf(temp, sizeof(temp), format, arg);
    va_end(arg);

    if(len > sizeof(temp) - 1) {
        buffer = new(eCxx::nothrow) char[len + 1];
        if(!buffer) return 0;

        va_start(arg, format);
        vsnprintf(buffer, len + 1, format, arg);
        va_end(arg);
    }

    len = write(reinterpret_cast<const uint8_t*>(buffer), len);
    if(buffer != temp) delete[] buffer;

    return len;
}


size_t Print::printf_P(_PGMCharPointer format, ...)
{
    char  temp[64];
    char* buffer = temp;

    va_list arg;
    va_start(arg, format);
    size_t len = ::vsnprintf_P(temp, sizeof(temp), format, arg);
    va_end(arg);

    if( len > sizeof(temp) - 1 ) {
        buffer = new(eCxx::nothrow) char[len + 1];
        if(!buffer) return 0;

        va_start(arg, format);
        ::vsnprintf_P(buffer, len + 1, format, arg);
        va_end(arg);
    }

    len = write(reinterpret_cast<const uint8_t*>(buffer), len);
    if(buffer != temp) delete[] buffer;

    return len;
}


size_t Print::print_P(_PGMCharPointer p)
{
    char buff[128] __ECXX_ATTR_ALIGNED_4;

    const size_t len = strlen_P(p);
          size_t n   = 0;

    while(n < len) {
        const size_t toWrite = eCxx::min( sizeof(buff), len - n );

        ::memcpy_P(buff, p, toWrite);

        const size_t written = write(buff, toWrite);
        n += written;
        p += written;

        if(!written) {
            // Error - write() should write at least 1 byte before returning
            break;
        }
    }

    return n;
}


size_t Print::println_P(_PGMCharPointer s)
{
    size_t n = print_P(s);
    n += println();
    return n;
}


size_t Print::print(const WString& s)
{ return write( s.c_str(), s.length() ); }


size_t Print::print(const char str[])
{ return write(str); }


size_t Print::print(char c)
{ return write(c); }


size_t Print::println()
{ return print("\r\n"); }


size_t Print::println(const WString& s)
{
    size_t n = print(s);
    n += println();
    return n;
}


size_t Print::println(const char c[])
{
    size_t n = print(c);
    n += println();
    return n;
}


size_t Print::println(char c)
{
    size_t n = print(c);
    n += println();
    return n;
}


size_t Print::print(signed char n, uint8_t base)
{ return print( static_cast<long>(n), base ); }


size_t Print::print(unsigned char n, uint8_t base)
{ return print( static_cast<unsigned long>(n), base ); }


size_t Print::print(short n, uint8_t base)
{ return print( static_cast<long>(n), base ); }


size_t Print::print(unsigned short n, uint8_t base)
{ return print( static_cast<unsigned long>(n), base ); }


size_t Print::print(int n, uint8_t base)
{ return print( static_cast<long>(n), base ); }


size_t Print::print(unsigned int n, uint8_t base)
{ return print( static_cast<unsigned long>(n), base ); }


size_t Print::print(long n, uint8_t base)
{
    if(base == 0) {
        return write(n);
    }
    else if(base == 10) {
        if(n < 0) {
            const int t = print('-');
            n = -n;
            return _printUInt(n, 10) + t;
        }
        return _printUInt(n, 10);
    }
    else {
        return _printUInt(n, base);
    }
}


size_t Print::print(unsigned long n, uint8_t base)
{
    if(base == 0) return write(n);
    else          return _printUInt(n, base);
}


size_t Print::print(long long n, uint8_t base)
{
    if(base == 0) {
        return write(n);
    }
    else if(base == 10) {
        if(n < 0) {
            const int t = print('-');
            n = -n;
            return _printUInt(n, 10) + t;
        }
        return _printUInt(n, 10);
    }
    else {
        return _printUInt(n, base);
    }
}


size_t Print::print(unsigned long long n, uint8_t base)
{
    if(base == 0) return write(n);
    else          return _printUInt(n, base);
}


size_t Print::print(float n, uint8_t digits)
{ return _printFloat(n, digits); }


size_t Print::print(double n, uint8_t digits)
{ return _printFloat(n, digits); }


size_t Print::println(signed char n, uint8_t base)
{
    size_t r = print(n, base);
    r += println();
    return r;
}


size_t Print::println(unsigned char n, uint8_t base)
{
    size_t r = print(n, base);
    r += println();
    return r;
}


size_t Print::println(short n, uint8_t base)
{
    size_t r = print(n, base);
    r += println();
    return r;
}


size_t Print::println(unsigned short n, uint8_t base)
{
    size_t r = print(n, base);
    r += println();
    return r;
}


size_t Print::println(int n, uint8_t base)
{
    size_t r = print(n, base);
    r += println();
    return r;
}


size_t Print::println(unsigned int n, uint8_t base)
{
    size_t r = print(n, base);
    r += println();
    return r;
}


size_t Print::println(long n, uint8_t base)
{
    size_t r = print(n, base);
    r += println();
    return r;
}


size_t Print::println(unsigned long n, uint8_t base)
{
    size_t r = print(n, base);
    r += println();
    return r;
}


size_t Print::println(float n, uint8_t digits)
{
    size_t r = print(n, digits);
    r += println();
    return r;
}


size_t Print::println(double n, uint8_t digits)
{
    size_t r = print(n, digits);
    r += println();
    return r;
}


} // namespace NodeMCU

} // namespace eCxx
