#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%new-style
%enable-all-warnings
%require-types
%strict-args

%requires ../../../../qlib/QUnit.qm

const Data = (
    "abcdef",
    "xyz42",
    "123456"
);

const STREAMREADER_BUFFER_SIZE = 1024;

%exec-class BufferedStreamReaderTest

class BufferedStreamReaderTest inherits QUnit::Test {
    constructor() : QUnit::Test("BufferedStreamReader test", "1.0") {
        addTestCase("Read line tests", \readLineTests());
        addTestCase("Buffer edge tests", \bufferEdgeTests());
        addTestCase("Encoding tests", \encodingTests());
        addTestCase("Read binary tests", \readBinaryTests());
        addTestCase("Read int tests", \readIntTests());
        addTestCase("Exception tests", \exceptionTests());
        addTestCase("Read string tests", \readStringTests());

        set_return_value(main());
    }

    readLineTests() {
        string WithNLines = foldl $1 + "\n" + $2, Data;
        string WithRNLines = foldl $1 + "\r\n" + $2, Data;
        string WithRLines = foldl $1 + "\r" + $2, Data;
        string weirdEOL = "XYZ123";
        string WithWeirdEOL = foldl $1 + weirdEOL + $2, Data;

        InputStream is = new BinaryInputStream(binary());
        BufferedStreamReader sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(NOTHING, sr.readLine());
        assertEq(NOTHING, sr.readLine("\n"));
        assertEq(NOTHING, sr.readLine(NOTHING, False));
        assertEq(NOTHING, sr.readLine("\n", False));

        is = new StringInputStream(Data[0]);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(Data[0], sr.readLine());
        assertEq(NOTHING, sr.readLine());

        is = new StringInputStream(WithNLines);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(Data[0] + "\n", sr.readLine(NOTHING, False));
        assertEq(Data[1] + "\n", sr.readLine(NOTHING, False));
        assertEq(Data[2], sr.readLine(NOTHING, False));
        assertEq(NOTHING, sr.readLine(NOTHING, False));

        is = new StringInputStream(WithNLines);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(Data[0], sr.readLine());
        assertEq(Data[1], sr.readLine());
        assertEq(Data[2], sr.readLine());
        assertEq(NOTHING, sr.readLine());

        is = new StringInputStream(WithNLines);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(Data[0], sr.readLine("\n"));
        assertEq(Data[1], sr.readLine("\n"));
        assertEq(Data[2], sr.readLine("\n"));
        assertEq(NOTHING, sr.readLine("\n"));

        is = new StringInputStream(WithNLines);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(WithNLines, sr.readLine("\r"));
        assertEq(NOTHING, sr.readLine("\r"));

        is = new StringInputStream(WithRNLines);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(Data[0], sr.readLine());
        assertEq(Data[1], sr.readLine());
        assertEq(Data[2], sr.readLine());
        assertEq(NOTHING, sr.readLine());

        is = new StringInputStream(WithRNLines);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(Data[0], sr.readLine("\r\n"));
        assertEq(Data[1], sr.readLine("\r\n"));
        assertEq(Data[2], sr.readLine("\r\n"));
        assertEq(NOTHING, sr.readLine("\r\n"));

        is = new StringInputStream(WithRNLines);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(Data[0] + "\r", sr.readLine("\n"));
        assertEq(Data[1] + "\r", sr.readLine("\n"));
        assertEq(Data[2], sr.readLine("\n"));
        assertEq(NOTHING, sr.readLine("\n"));

        is = new StringInputStream(WithRNLines);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(Data[0], sr.readLine("\r"));
        assertEq("\n" + Data[1], sr.readLine("\r"));
        assertEq("\n" + Data[2], sr.readLine("\r"));
        assertEq(NOTHING, sr.readLine("\r"));

        is = new StringInputStream(WithRLines);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(Data[0], sr.readLine());
        assertEq(Data[1], sr.readLine());
        assertEq(Data[2], sr.readLine());
        assertEq(NOTHING, sr.readLine());

        is = new StringInputStream(WithRLines);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(Data[0], sr.readLine("\r"));
        assertEq(Data[1], sr.readLine("\r"));
        assertEq(Data[2], sr.readLine("\r"));
        assertEq(NOTHING, sr.readLine("\r"));

        is = new StringInputStream(WithRLines);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(WithRLines, sr.readLine("\n"));
        assertEq(NOTHING, sr.readLine("\n"));

        is = new StringInputStream(WithWeirdEOL);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(Data[0], sr.readLine(weirdEOL));
        assertEq(Data[1], sr.readLine(weirdEOL));
        assertEq(Data[2], sr.readLine(weirdEOL));
        assertEq(NOTHING, sr.readLine(weirdEOL));

        is = new StringInputStream(WithWeirdEOL);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(WithWeirdEOL, sr.readLine("\n"));
        assertEq(NOTHING, sr.readLine("\n"));

        is = new StringInputStream(WithWeirdEOL);
        sr = new BufferedStreamReader(is, NOTHING, 4);
        assertEq("abcdef", sr.readLine(weirdEOL));

        is = new StringInputStream(WithNLines);
        sr = new BufferedStreamReader(is, NOTHING, 1);
        assertEq(Data[0] + "\n", sr.readLine("\n", False));
        assertEq(Data[1] + "\n", sr.readLine("\n", False));
        assertEq(Data[2], sr.readLine("\n", False));
        assertEq(NOTHING, sr.readLine("\n", False));

        is = new StringInputStream(WithRLines);
        sr = new BufferedStreamReader(is, NOTHING, 2);
        assertEq(Data[0], sr.readLine());
        assertEq(Data[1], sr.readLine());
        assertEq(Data[2], sr.readLine());
        assertEq(NOTHING, sr.readLine());

        is = new StringInputStream(WithRLines);
        sr = new BufferedStreamReader(is, NOTHING, 1);
        assertEq(Data[0], sr.readLine("\r"));
        assertEq(Data[1], sr.readLine("\r"));
        assertEq(Data[2], sr.readLine("\r"));
        assertEq(NOTHING, sr.readLine("\r"));

        is = new StringInputStream(WithRLines);
        sr = new BufferedStreamReader(is, NOTHING, 1);
        assertEq(WithRLines, sr.readLine("\n"));
        assertEq(NOTHING, sr.readLine("\n"));

        is = new StringInputStream(WithNLines);
        sr = new BufferedStreamReader(is, NOTHING, 2);
        assertEq(Data[0] + "\n", sr.readLine(NOTHING, False));
        assertEq(Data[1] + "\n", sr.readLine(NOTHING, False));
        assertEq(Data[2], sr.readLine(NOTHING, False));
        assertEq(NOTHING, sr.readLine(NOTHING, False));

        is = new StringInputStream(WithRNLines);
        sr = new BufferedStreamReader(is, NOTHING, 1);
        assertEq(Data[0], sr.readLine());
        assertEq(Data[1], sr.readLine());
        assertEq(Data[2], sr.readLine());
        assertEq(NOTHING, sr.readLine());

        is = new StringInputStream(WithRNLines);
        sr = new BufferedStreamReader(is, NOTHING, 2);
        assertEq(Data[0], sr.readLine("\r\n"));
        assertEq(Data[1], sr.readLine("\r\n"));
        assertEq(Data[2], sr.readLine("\r\n"));
        assertEq(NOTHING, sr.readLine("\r\n"));

        is = new StringInputStream(WithRNLines);
        sr = new BufferedStreamReader(is, NOTHING, 2);
        assertEq(Data[0] + "\r", sr.readLine("\n"));
        assertEq(Data[1] + "\r", sr.readLine("\n"));
        assertEq(Data[2], sr.readLine("\n"));
        assertEq(NOTHING, sr.readLine("\n"));

        is = new StringInputStream(WithRNLines);
        sr = new BufferedStreamReader(is, NOTHING, 2);
        assertEq(Data[0], sr.readLine("\r"));
        assertEq("\n" + Data[1], sr.readLine("\r"));
        assertEq("\n" + Data[2], sr.readLine("\r"));
        assertEq(NOTHING, sr.readLine("\r"));

        assertThrows("STREAM-BUFFER-ERROR", sub () { new BufferedStreamReader(is, NOTHING, 0); });
        assertThrows("STREAM-BUFFER-ERROR", sub () { new BufferedStreamReader(is, NOTHING, -1); });
    }

    bufferEdgeTests() {
        string s = strmul("a", STREAMREADER_BUFFER_SIZE - 1);

        string sWith123456 = s + "a" + "123456"; # "a" is there to fill the buffer
        string sWithRN = s + "\r\n";
        string sWithR = s + "\r";
        string sWithRNabcdef = sWithRN + "abcdef";
        string weirdEOL = "XYZ123";
        string sWithWeirdEOL = s + weirdEOL;
        string sWithWeirdEOLabc = sWithWeirdEOL + "abc";

        InputStream is = new StringInputStream(sWith123456);
        BufferedStreamReader sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(sWith123456, sr.readLine());
        assertEq(NOTHING, sr.readLine());

        is = new StringInputStream(sWithRN);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(s, sr.readLine());
        assertEq(NOTHING, sr.readLine());

        is = new StringInputStream(sWithRN);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(sWithRN, sr.readLine(NOTHING, False));
        assertEq(NOTHING, sr.readLine(NOTHING, False));

        is = new StringInputStream(sWithR);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(s, sr.readLine());
        assertEq(NOTHING, sr.readLine());

        is = new StringInputStream(sWithR);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(sWithR, sr.readLine(NOTHING, False));
        assertEq(NOTHING, sr.readLine(NOTHING, False));

        is = new StringInputStream(sWithRNabcdef);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(s, sr.readLine());
        assertEq("abcdef", sr.readLine());
        assertEq(NOTHING, sr.readLine());

        is = new StringInputStream(sWithRNabcdef);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(sWithRN, sr.readLine(NOTHING, False));
        assertEq("abcdef", sr.readLine(NOTHING, False));
        assertEq(NOTHING, sr.readLine(NOTHING, False));

        is = new StringInputStream(sWithWeirdEOL);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(s, sr.readLine(weirdEOL));
        assertEq(NOTHING, sr.readLine(weirdEOL));

        is = new StringInputStream(sWithWeirdEOL);
        sr = new BufferedStreamReader(is, NOTHING, 4);
        assertEq(s, sr.readLine(weirdEOL));

        is = new StringInputStream(sWithWeirdEOL);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(sWithWeirdEOL, sr.readLine(weirdEOL, False));
        assertEq(NOTHING, sr.readLine(weirdEOL, False));

        is = new StringInputStream(sWithWeirdEOLabc);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(s, sr.readLine(weirdEOL));
        assertEq("abc", sr.readLine(weirdEOL));
        assertEq(NOTHING, sr.readLine(weirdEOL));

        is = new StringInputStream(sWithWeirdEOLabc);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(sWithWeirdEOL, sr.readLine(weirdEOL, False));
        assertEq("abc", sr.readLine(weirdEOL, False));
        assertEq(NOTHING, sr.readLine(weirdEOL, False));

        s = strmul("a", STREAMREADER_BUFFER_SIZE - 3);
        sWith123456 = s + "123456xyz";
        is = new StringInputStream(sWith123456);
        sr = new BufferedStreamReader(is);
        assertEq(s, sr.readLine("123456"));
        assertEq("xyz", sr.readLine("123456"));
        assertEq(NOTHING, sr.readLine());
    }

    encodingTests() {
        string s1 = "Příliš žluťoučký kůň úpěl ďábelské ódy.";
        string s2 = "abcdef";
        string s = s1 + "\n" + s2;
        string sUTF8 = convert_encoding(s, "UTF-8");
        string sISO88592 = convert_encoding(s, "ISO-8859-2");
        string i88592eol = convert_encoding("\n", "ISO-8859-2");

        InputStream is = new StringInputStream(s);
        BufferedStreamReader sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq("".encoding(), sr.getEncoding());
        assertEq("".encoding(), sr.readLine().encoding());

        is = new StringInputStream(sUTF8);
        sr = new BufferedStreamReader(is, "UTF-8", STREAMREADER_BUFFER_SIZE);
        assertEq("UTF-8", sr.getEncoding());
        assertEq("UTF-8", sr.readLine().encoding());

        is = new StringInputStream(sISO88592);
        sr = new BufferedStreamReader(is, "ISO-8859-2", STREAMREADER_BUFFER_SIZE);
        assertEq("ISO-8859-2", sr.getEncoding());
        assertEq("ISO-8859-2", sr.readLine().encoding());

        is = new StringInputStream(sUTF8);
        sr = new BufferedStreamReader(is, "UTF-8", STREAMREADER_BUFFER_SIZE);
        assertEq(s1, sr.readLine(i88592eol));
        assertEq(s2, sr.readLine(i88592eol));
        assertEq(NOTHING, sr.readLine(i88592eol));
    }

    readBinaryTests() {
        binary b1 = <0123456789abcdef>;
        binary b2 = <00102030405060708090a0b0c0d0e0f0>;
        binary b = b1 + b2;

        BinaryInputStream is = new BinaryInputStream(binary());
        BufferedStreamReader sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(NOTHING, sr.readBinary());
        assertEq(NOTHING, sr.readBinary(-1));
        assertEq(NOTHING, sr.readBinary(0));
        assertEq(NOTHING, sr.readBinary(100));

        is = new BinaryInputStream(b);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(b, sr.readBinary());
        assertEq(NOTHING, sr.readBinary());

        is = new BinaryInputStream(b);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(b, sr.readBinary(-1));
        assertEq(NOTHING, sr.readBinary(-1));

        is = new BinaryInputStream(b);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(NOTHING, sr.readBinary(0));
        assertEq(b, sr.readBinary());

        is = new BinaryInputStream(b);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(b, sr.readBinary(100));
        assertEq(NOTHING, sr.readBinary(100));

        is = new BinaryInputStream(b);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(b1, sr.readBinary(8));
        assertEq(b2, sr.readBinary(100));
    }

    readIntTests() {
        binary b = <0a1b2c3d001122334455667700102030405060708090>;
        BinaryInputStream is = new BinaryInputStream(b);
        BufferedStreamReader sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(0x0a, sr.readi1());
        assertEq(0x1b, sr.readi1());
        assertEq(0x2c3d, sr.readi2());
        assertEq(0x0011, sr.readi2());
        assertEq(0x22334455, sr.readi4());
        assertEq(0x66770010, sr.readi4());
        assertEq(0x2030405060708090, sr.readi8());

        is = new BinaryInputStream(b);
        sr = new BufferedStreamReader(is, NOTHING, 1);
        assertEq(0x1b0a, sr.readi2LSB());

        is = new BinaryInputStream(b);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(0x1b0a, sr.readi2LSB());
        assertEq(0x3d2c, sr.readi2LSB());
        assertEq(0x33221100, sr.readi4LSB());
        assertEq(0x77665544, sr.readi4LSB());
        assertEq(0x7060504030201000, sr.readi8LSB());

        is = new BinaryInputStream(<abcd0123aabbccdd0011223344556677>);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(0xab, sr.readu1());
        assertEq(0xcd, sr.readu1());
        assertEq(0x0123, sr.readu2());
        assertEq(0xaabb, sr.readu2());
        assertEq(0xccdd0011, sr.readu4());
        assertEq(0x22334455, sr.readu4());
        assertEq(0x66, sr.readu1());
        assertEq(0x77, sr.readu1());

        is = new BinaryInputStream(<abcd0123aabbccdd001122334455>);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(0xcdab, sr.readu2LSB());
        assertEq(0x2301, sr.readu2LSB());
        assertEq(0xbbaa, sr.readu2LSB());
        assertEq(0x1100ddcc, sr.readu4LSB());
        assertEq(0x55443322, sr.readu4LSB());

        is = new BinaryInputStream(<FEFFFD008080000000FCFFFFFFFFFFFFFFFFFFFFFBFAFFFFFFFFFFFFFF>);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(-2, sr.readi1());
        assertEq(-3, sr.readi2());
        assertEq(-0x8000, sr.readi2LSB());
        assertEq(-0x80000000, sr.readi4());
        assertEq(-4, sr.readi4LSB());
        assertEq(-5, sr.readi8());
        assertEq(-6, sr.readi8LSB());

        is = new BinaryInputStream(<FEFFFD008080000000FCFFFFFF>);
        sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertEq(0xFE, sr.readu1());
        assertEq(0xFFFD, sr.readu2());
        assertEq(0x8000, sr.readu2LSB());
        assertEq(0x80000000, sr.readu4());
        assertEq(0xFFFFFFFC, sr.readu4LSB());
    }

    exceptionTests() {
        InputStream is = new BinaryInputStream(binary());
        BufferedStreamReader sr = new BufferedStreamReader(is, NOTHING, STREAMREADER_BUFFER_SIZE);
        assertThrows("END-OF-STREAM-ERROR", sub () { sr.readi1(); });
        assertThrows("END-OF-STREAM-ERROR", sub () { sr.readi2(); });
        assertThrows("END-OF-STREAM-ERROR", sub () { sr.readi4(); });
        assertThrows("END-OF-STREAM-ERROR", sub () { sr.readi8(); });
        assertThrows("END-OF-STREAM-ERROR", sub () { sr.readi2LSB(); });
        assertThrows("END-OF-STREAM-ERROR", sub () { sr.readi4LSB(); });
        assertThrows("END-OF-STREAM-ERROR", sub () { sr.readi8LSB(); });

        assertThrows("END-OF-STREAM-ERROR", sub () { sr.readu1(); });
        assertThrows("END-OF-STREAM-ERROR", sub () { sr.readu2(); });
        assertThrows("END-OF-STREAM-ERROR", sub () { sr.readu4(); });
        assertThrows("END-OF-STREAM-ERROR", sub () { sr.readu2LSB(); });
        assertThrows("END-OF-STREAM-ERROR", sub () { sr.readu4LSB(); });

        assertThrows("ENCODING-CONVERSION-ERROR", sub () { sr = new BufferedStreamReader(is, "abcd"); sr.readLine("\n"); });
    }

    readStringTests() {
        string s1 = "Příliš žluťoučký kůň úpěl ďábelské ódy.";

        StringInputStream is(s1);
        BufferedStreamReader sr(is, NOTHING, 3);

        assertEq("Příliš", sr.readString(6));
        assertEq(" žluťoučký", sr.readString(10));
        assertEq(" kůň úpěl ďábelské ódy.", sr.readString(-1));
        assertThrows("END-OF-STREAM-ERROR", \sr.readu1());

        s1 = convert_encoding(s1, "utf-16le");
        is = new StringInputStream(s1);
        sr = new BufferedStreamReader(is, "utf-16le", 3);

        assertEq(78, s1.size());
        assertEq(39, s1.length());

        assertEq("Příliš", sr.readString(6));
        assertEq(" žluťoučký", sr.readString(10));
        assertEq(" kůň úpěl ďábelské ódy.", sr.readString(-1));
        assertThrows("END-OF-STREAM-ERROR", \sr.readu1());

        is = new StringInputStream(s1);
        sr = new BufferedStreamReader(is, "utf-16le", 3);
        assertEq(s1, sr.readLine());
        assertEq(NOTHING, sr.readLine());
    }
}
