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

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

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

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

const ISLIBufferSize = 4096;

%exec-class InputStreamLineIteratorTest

class InputStreamLineIteratorTest inherits QUnit::Test {
    public {
        const InputString = "hello there 1, 2, 3\nline 2\nline 3\n";
    }

    constructor() : QUnit::Test("InputStreamLineIterator test", "1.0") {
        addTestCase("Basic tests", \basicTests());
        addTestCase("Buffer edge test", \bufferEdgeTest());
        addTestCase("Encoding test", \encodingTest());
        addTestCase("string test", \stringTest());
        addTestCase("file test", \fileTest());
        set_return_value(main());
    }

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

        BinaryInputStream bis = new BinaryInputStream(binary());
        InputStreamLineIterator a = new InputStreamLineIterator(bis);
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getValue());
        assertFalse(a.next());
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getValue());

        StreamReader sr = a.getStreamReader(); # Just checking that no double-free occurs.
        assertEq(NOTHING, sr.readString());

        bis = new BinaryInputStream(binary(Data[0]));
        a = new InputStreamLineIterator(bis);
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(1, a.index());
        assertEq(Data[0], a.getLine());
        assertEq(Data[0], a.getValue());
        assertFalse(a.next());
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getValue());

        bis = new BinaryInputStream(binary(WithNLines));
        a = new InputStreamLineIterator(bis);
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(1, a.index());
        assertEq(Data[0], a.getLine());
        assertEq(Data[0], a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(2, a.index());
        assertEq(Data[1], a.getLine());
        assertEq(Data[1], a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(3, a.index());
        assertEq(Data[2], a.getLine());
        assertEq(Data[2], a.getValue());
        assertFalse(a.next());
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getValue());

        bis = new BinaryInputStream(binary(WithRNLines));
        a = new InputStreamLineIterator(bis);
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(1, a.index());
        assertEq(Data[0], a.getLine());
        assertEq(Data[0], a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(2, a.index());
        assertEq(Data[1], a.getLine());
        assertEq(Data[1], a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(3, a.index());
        assertEq(Data[2], a.getLine());
        assertEq(Data[2], a.getValue());
        assertFalse(a.next());
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getValue());

        bis = new BinaryInputStream(binary(WithRLines));
        a = new InputStreamLineIterator(bis);
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(1, a.index());
        assertEq(Data[0], a.getLine());
        assertEq(Data[0], a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(2, a.index());
        assertEq(Data[1], a.getLine());
        assertEq(Data[1], a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(3, a.index());
        assertEq(Data[2], a.getLine());
        assertEq(Data[2], a.getValue());
        assertFalse(a.next());
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getValue());

        bis = new BinaryInputStream(binary(WithWeirdEOL));
        a = new InputStreamLineIterator(bis, NOTHING, weirdEOL);
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(1, a.index());
        assertEq(Data[0], a.getLine());
        assertEq(Data[0], a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(2, a.index());
        assertEq(Data[1], a.getLine());
        assertEq(Data[1], a.getValue());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(3, a.index());
        assertEq(Data[2], a.getLine());
        assertEq(Data[2], a.getValue());
        assertFalse(a.next());
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getValue());
    }

    bufferEdgeTest() {
        string s = "";
        for (int i = 0; i < (ISLIBufferSize-1); i++)
            s += "a";

        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 = "XY";
        string sWithWeirdEOL = s + weirdEOL;
        string sWithWeirdEOLabc = sWithWeirdEOL + "abc";

        BinaryInputStream bis = new BinaryInputStream(binary(sWith123456));
        AbstractLineIterator a = new InputStreamLineIterator(bis);
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(sWith123456, a.getLine());
        assertFalse(a.next());
        assertFalse(a.valid());

        bis = new BinaryInputStream(binary(sWithRN));
        a = new InputStreamLineIterator(bis);
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(s, a.getLine());
        assertFalse(a.next());
        assertFalse(a.valid());

        bis = new BinaryInputStream(binary(sWithRN));
        a = new InputStreamLineIterator(bis, NOTHING, NOTHING, False);
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(sWithRN, a.getLine());
        assertFalse(a.next());
        assertFalse(a.valid());

        bis = new BinaryInputStream(binary(sWithR));
        a = new InputStreamLineIterator(bis);
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(s, a.getLine());
        assertFalse(a.next());
        assertFalse(a.valid());

        bis = new BinaryInputStream(binary(sWithR));
        a = new InputStreamLineIterator(bis, NOTHING, NOTHING, False);
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(sWithR, a.getLine());
        assertFalse(a.next());
        assertFalse(a.valid());

        bis = new BinaryInputStream(binary(sWithRNabcdef));
        a = new InputStreamLineIterator(bis);
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(s, a.getLine());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq("abcdef", a.getLine());
        assertFalse(a.next());
        assertFalse(a.valid());

        bis = new BinaryInputStream(binary(sWithRNabcdef));
        a = new InputStreamLineIterator(bis, NOTHING, NOTHING, False);
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(sWithRN, a.getLine());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq("abcdef", a.getLine());
        assertFalse(a.next());
        assertFalse(a.valid());
        assertEq(0, a.index());
        assertThrows('ITERATOR-ERROR', \a.getLine());

        bis = new BinaryInputStream(binary(sWithWeirdEOL));
        a = new InputStreamLineIterator(bis, NOTHING, weirdEOL);
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(s, a.getLine());
        assertFalse(a.next());
        assertFalse(a.valid());

        bis = new BinaryInputStream(binary(sWithWeirdEOL));
        a = new InputStreamLineIterator(bis, NOTHING, weirdEOL, False);
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(sWithWeirdEOL, a.getLine());
        assertFalse(a.next());
        assertFalse(a.valid());

        bis = new BinaryInputStream(binary(sWithWeirdEOLabc));
        a = new InputStreamLineIterator(bis, NOTHING, weirdEOL);
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(s, a.getLine());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq("abc", a.getLine());
        assertFalse(a.next());
        assertFalse(a.valid());

        bis = new BinaryInputStream(binary(sWithWeirdEOLabc));
        a = new InputStreamLineIterator(bis, NOTHING, weirdEOL, False);
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(sWithWeirdEOL, a.getLine());
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq("abc", a.getLine());
        assertFalse(a.next());
        assertFalse(a.valid());
    }

    encodingTest() {
        string s = "Příliš žluťoučký kůň úpěl ďábelské ódy.";
        string sUTF16 = convert_encoding(s, "UTF16");
        string sUTF16LE = convert_encoding(s, "UTF16LE");
        string sISO88592 = convert_encoding(s, "ISO-8859-2");

        BinaryInputStream bis = new BinaryInputStream(binary(s));
        AbstractLineIterator a = new InputStreamLineIterator(bis);
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(s, a.getLine());
        assertEq("".encoding(), a.getLine().encoding());
        assertFalse(a.next());

        bis = new BinaryInputStream(binary(sUTF16));
        a = new InputStreamLineIterator(bis, "UTF16", convert_encoding("\n", "UTF16"));
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(s, a.getLine());
        assertEq("UTF-16", a.getLine().encoding().substr(0, 6));
        assertFalse(a.next());

        bis = new BinaryInputStream(binary(sUTF16LE));
        a = new InputStreamLineIterator(bis, "UTF16LE", convert_encoding("\n", "UTF16LE"));
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(s, a.getLine());
        assertEq("UTF-16LE", a.getLine().encoding());
        assertFalse(a.next());

        bis = new BinaryInputStream(binary(sISO88592));
        a = new InputStreamLineIterator(bis, "ISO-8859-2");
        assertTrue(a.next());
        assertTrue(a.valid());
        assertEq(s, a.getLine());
        assertEq("ISO-8859-2", a.getLine().encoding());
        assertFalse(a.next());
    }

    stringTest() {
        StringInputStream is(InputString);
        doTests(is, "string");

        assertEq(NOTHING, sub () { InputStreamLineIterator i(is, NOTHING, NOTHING, True, 1);}());
        assertEq(NOTHING, sub () { InputStreamLineIterator i(is, NOTHING, "test", True, 3);}());

        is = new StringInputStream(convert_encoding(InputString, "utf16"));
        doTests(is, "utf-16 string", "utf16");
    }

    fileTest() {
        string fn = tmp_location() + DirSep + get_random_string();
        {
            File f();
            f.open2(fn, O_CREAT|O_WRONLY|O_TRUNC);
            f.write(InputString);
        }
        on_exit unlink(fn);

        {
            FileInputStream is(fn);
            assertEq("hello ", binary_to_string(is.read(6)));
            StreamReader sr(is);
            assertEq("there ", sr.readString(6));
            assertEq("1, 2, 3", binary_to_string(is.read(7)));
        }

        FileInputStream is(fn);
        doTests(is, "file");
    }

    private doTests(InputStream is, string label, *string enc) {
        InputStreamLineIterator i(is, enc, "\n", True, 1);
        assertEq(True, i.next(), label + " next");
        assertEq("hello there 1, 2, 3", i.getValue(), label + " first");
        StreamReader sr(is, enc);
        assertEq("line 2\n", sr.readLine("\n", False));
        #assertEq("line 2\n", binary_to_string(is.read(7 * x), enc), label + " second");
        i.next();
        assertEq("line 3", i.getValue(), label + " third");
    }
}
