#include <Streaming.h>

// Unit test helper class
class _Assert : public Print
{
public:
    _Assert() : m_idx(0) {}
    size_t      write(uint8_t b) { m_buf[m_idx++] = b; return 1; }
    int8_t      compare(const char * expected) const
    {
        int8_t len_comparison = strlen(expected) - m_idx;
        if ( len_comparison != 0 ) return len_comparison;
        return memcmp(m_buf, expected, m_idx );
    }
    int8_t      compare(const __FlashStringHelper* expected) const
    {
        int8_t len_comparison = strlen_P(reinterpret_cast<const char *>(expected)) - m_idx;
        if ( len_comparison != 0) return len_comparison;
        return memcmp_P(m_buf, expected, m_idx);
    }
    void        reset(void) { m_idx = 0; }
    const char *buffer(void) { m_buf[m_idx] = 0; return m_buf; }

private:
    char        m_buf[40];
    uint8_t     m_idx;
};

_Assert Assert;
uint16_t ran=0;
uint16_t passed=0;

template<typename T>
inline Print& operator==(Print& stm, T expected)
{
    // Hackery since we know stm is of type _Assert
    _Assert* assert = reinterpret_cast<_Assert*>(&stm);
    int8_t cmp = assert->compare(expected);
    // Would love to use the streaming functions here, but as we're testing them we can't
    Serial.print("Test ");
    Serial.print(++ran);
    if ( cmp == 0 )
    {
        Serial.println(" passed.");
        passed++;
    }
    else
    {
        Serial.print(" failed. Expected [");
        Serial.print(expected);
        Serial.print("] but was [");
        Serial.print(assert->buffer());
        Serial.println("].");
    }
    assert->reset();
    return stm;
}

class PrintableTest : public Printable
{
    size_t printTo(Print& p) const { return p.print("printed"); }
};

class NoCopyConstructorType : public Printable
{
public:
    NoCopyConstructorType() {}
    size_t printTo(Print& p) const { return p.print("NoCopyCtr"); }
private:
    // Remove the copy constructor which will ensure that it can't be passed to the streaming template by value
    NoCopyConstructorType(NoCopyConstructorType&) {}
};

void setup()
{
    Serial.begin(115200);
}

void loop()
{
    ran = passed = 0;
    //
    //  Check the assertion comparators
    //
    Assert.print("one");
    if ( Assert.compare("one") != 0 )
    {
        Serial.println(F("const char* comparator failed"));
        while(true);
    }
    if ( Assert.compare(F("one")) != 0)
    {
        Serial.println(F("const __FlashStringHelper* comparator failed"));
        while(true);
    }
    Assert.reset();

    const char abc[] = "abc";
    //
    //  Start by testing that simple types all function correctly
    //  using the generic template
    //
    Assert << 1 == F("1");
    Assert << 10 == F("10");
    Assert << -5 == F("-5");
    Assert << 'a' == F("a");
    Assert << B1000 == F("8");
    Assert << 1.0 == F("1.00");
    Assert << 1.005 == F("1.00");
    Assert << -10.23 == F("-10.23");
    Assert << "abc" == F("abc");
    Assert << abc == F("abc");
    Assert << abc == abc;
    Assert << F("abc") == F("abc");
    Assert << String("string") == F("string");
    Assert << PrintableTest() == F("printed");

    //
    //  Check that chaining works
    //
    Assert << 1 << '2' << 0x3 << "4" << F("5") == F("12345");

    //
    //  Now check that the _BASED ones work too
    //
    Assert << _DEC(1) == F("1");
    Assert << _DEC(-1) == F("-1");

    Assert << _DEC((int8_t)1) == F("1");
    Assert << _DEC((int8_t)-1) == F("-1");
    Assert << _DEC((uint8_t)1) == F("1");
    Assert << _DEC((uint8_t)-1) == F("255");

    Assert << _DEC((int16_t)1) == F("1");
    Assert << _DEC((int16_t)-1) == F("-1");
    Assert << _DEC((uint16_t)1) == F("1");
    Assert << _DEC((uint16_t)-1) == F("65535");

    Assert << _DEC((int32_t)1) == F("1");
    Assert << _DEC((int32_t)-1) == F("-1");
    Assert << _DEC((uint32_t)1) == F("1");
    Assert << _DEC((uint32_t)-1) == F("4294967295");
    Assert << (uint32_t)4294967295 == F("4294967295");

    Assert << _HEX(15) == F("F");
    Assert << _OCT(15) == F("17");
    Assert << _BIN(15) == F("1111");
    Assert << _BYTE(64) == F("@");

    //
    //  _FLOAT
    //
    double v = 1.23456789012345;
    Assert << _FLOAT(v, 1) == F("1.2");
    Assert << _FLOAT(v, 2) == F("1.23");
    Assert << _FLOAT(v, 3) == F("1.235");
    Assert << _FLOAT(v, 4) == F("1.2346");
    Assert << _FLOAT(v, 5) == F("1.23457");
    Assert << _FLOAT(v, 6) == F("1.234568");

    Assert << _FLOAT(1.7649E22, 5) == F("ovf");    // This really needs work!

    //
    //  endl
    //
    Assert << 1 << endl == F("1\r\n");

    //
    //  Padding helper
    //

    Assert << _PAD(10,' ') == F("          ");
    Assert << _PAD(4, '0') == F("0000");
    Assert << _PAD(20, '=') == F("====================");


    //
    //  Width streaming
    //

    Assert << _WIDTH(1, 5) == F("    1");
    Assert << _WIDTH(-1, 5) == F("   -1");
    Assert << _WIDTHZ(1, 5) == F("00001");
    Assert << _WIDTHZ(128,5) == F("00128");
    Assert << _WIDTHZ(_HEX(128), 5) == F("00080");
    Assert << _WIDTH(abc, 5) == F("  abc");
    Assert << _WIDTH("one", 5) == F("  one");
    Assert << _WIDTH(F("one"), 5) == F("  one");
    // Check lightweight std::is_signed on AVR platforms
    Assert << _WIDTH((int8_t)-1, 11) ==     F("         -1");
    Assert << _WIDTH((uint8_t)-1, 11) ==    F("        255");
    Assert << _WIDTH((int16_t)-1, 11) ==    F("         -1");
    Assert << _WIDTH((uint16_t)-1, 11) ==   F("      65535");
    Assert << _WIDTH((int32_t)-1, 11) ==    F("         -1");
    Assert << _WIDTH((uint32_t)-1, 11) ==   F(" 4294967295");
    Assert << _WIDTH((int8_t)-20, 11) ==    F("        -20");
    Assert << _WIDTH((int16_t)-32767, 11) ==F("     -32767");
    Assert << _WIDTH((int32_t)-65536, 11) ==F("     -65536");

    //
    //  Width streaming for floats
    //

    Assert << _WIDTH(_FLOAT(-9.99, 1), 11)==F("      -10.0");
    Assert << _WIDTH(-3.14159, 11)       == F("      -3.14"); // default nb of digits after decimal point is 2 for print(double)
    Assert << _WIDTH((float)12.566, 11)  == F("      12.57");

    Assert << _FLOATW(-1,         2, 11) == F("      -1.00");
    Assert << _FLOATW(1.23456e4,  1, 11) == F("    12345.6");
    #ifdef ARDUINO_ARCH_AVR
    Assert << _FLOATW(-3.1415926, 6, 11) == F("  -3.141592");
    #else
    Assert << _FLOATW(-3.1415926, 6, 11) == F("  -3.141593");
    #endif
    Assert << _FLOATW(-9.999999,  4, 11) == F("   -10.0000");
    Assert << _FLOATW(-0.0,       1, 11) == F("        0.0");
    Assert << _FLOATW(-0.01,      1, 11) == F("       -0.0");
    Assert << _FLOATW(-0.01,      0, 11) == F("         -0");
    Assert << _FLOATW(1.4,        0, 11) == F("          1");
    Assert << _FLOATW(0. / 0.,    2, 11) == F("        nan");
    Assert << _FLOATW(-1. / 0.,   2, 11) == F("        inf");
    #ifndef ESP8266 // no ovf on esp8266
    Assert << _FLOATW(1.e10,      2, 11) == F("        ovf");
    #endif

    //
    //  Stream formatter
    //

    Assert << _FMT("Hello % the time is %:%:%", "gazoodle", _WIDTHZ(1,2), _WIDTHZ(4,2), _WIDTHZ(8,2)) == F("Hello gazoodle the time is 01:04:08");
    Assert << _FMT("Too many % % % for the parms", 1) == F("Too many 1 % % for the parms");
    Assert << _FMT("Too few % for the parms", 1, 2) == F("Too few 1 for the parms");
    Assert << _FMT("Output a \\% sign") == F("Output a % sign");
    Assert << _FMT("Handle trailing \\") == F("Handle trailing ");
    Assert << _FMT(F("Replace % with %"), 1, 2 ) == F("Replace 1 with 2");
    Assert << _FMT("Your score is %\\%", 65.3) == F("Your score is 65.30%");
    Assert << _FMT("Once % a %%%", F("upon"), 't', 1, "me" ) == F("Once upon a t1me");
    Assert << _FMT("This [%][%][%] grid", _WIDTH(10, 4), _WIDTH(_HEX(619),4), _WIDTH(3,4)) == F("This [  10][ 26B][   3] grid");

    //
    //  Make sure that all the stream operators return the stream
    //  by chaining them together.
    //

    Assert
        << (int8_t)1            // Generic template
        << _BYTE(50)            // _BYTE_CODE
        << _HEX(3)              // _BASED
        << _FLOAT(4,0)          // _FLOAT
        << _PAD(1,'5')          // Padding
        << _WIDTH(6,1)          // Width
        << _FMT("%",7)          // Formatter
        << endl

        == F("1234567\r\n");

    //
    // Ensure streaming template overide doesn't pass by value
    //
    Assert
        << NoCopyConstructorType()
        == F("NoCopyCtr");

    //
    // Final report
    //
    Serial.println();
    Serial.print(F("Tests complete. "));
    Serial.print(ran);
    Serial.print(F(" tests ran. "));
    Serial.print(passed);
    Serial.print(" passed, ");
    Serial.print(ran-passed);
    Serial.println(" failed.");

    delay(1000);
}