package htsjdk.tribble.readers;


import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Tests for streams and readers
 */
public class ReaderTest {
    @BeforeClass
    public void setup() throws IOException {
    }

    @AfterClass
    public void teardown() throws Exception {

    }

    @Test
    public void testMultipleLines() throws IOException {
        testStream("line 1\nline2\n");
    }

    @Test
    public void testSingleLine() throws IOException {
        testStream("line 1\n");
    }

    @Test
    public void testEmpty() throws IOException {
        testStream("");
    }


    @Test
    public void testLotsOfLines() throws IOException {
        final StringBuilder b = new StringBuilder();
        for ( int i = 0; i < 10000; i++ ) {
            b.append("line " + i + "\n");
        }
        testStream(b.toString());
    }

    @Test
    public void testMassiveLines() throws IOException {
        final StringBuilder b = new StringBuilder();
        for ( int i = 0; i < 10; i++ ) {
            for ( int j = 0; j < 1000000; j++) {
                b.append(i + "." + j);
            }
            b.append("\n");
        }
        testStream(b.toString());
    }

    @Test
    public void testSkip() throws IOException {
        for ( int skipSizeBase : Arrays.asList(0, 10, 100, 1000, 10000, 1000000)) {
            for ( int skipSizeAdd = 0; skipSizeAdd < 10; skipSizeAdd ++ ) {
                final int skipSize = skipSizeBase + skipSizeAdd;
                final byte[] bytes = new byte[skipSize+2];
                Arrays.fill(bytes, 0, skipSize, (byte)0);
                bytes[skipSize] = 1;
                bytes[skipSize+1] = 2;

                final InputStream is = new ByteArrayInputStream(bytes);
                final PositionalBufferedStream pbs = new PositionalBufferedStream(is);
                pbs.skip(skipSize);

                // first value is 1
                Assert.assertTrue(! pbs.isDone());
                Assert.assertEquals(pbs.getPosition(), skipSize);
                Assert.assertEquals(pbs.peek(), 1);
                Assert.assertEquals(pbs.read(), 1);

                Assert.assertTrue(! pbs.isDone());
                Assert.assertEquals(pbs.getPosition(), skipSize + 1);
                Assert.assertEquals(pbs.peek(), 2);
                Assert.assertEquals(pbs.read(), 2);

                Assert.assertTrue(pbs.isDone());
            }
        }
    }

    private void testStream(final String s) throws IOException {
        testStream(s.getBytes());
        testLineReader(s);
    }

    private void testStream(final byte[] bytes) throws IOException {
        final InputStream is = new ByteArrayInputStream(bytes);
        final PositionalBufferedStream pbs = new PositionalBufferedStream(is);

        int bytePos = 0;
        while ( ! pbs.isDone() ) {
            Assert.assertTrue(bytePos < bytes.length);

            // test position
            Assert.assertEquals(pbs.getPosition(), bytePos);

            // test peek
            final byte atPos = bytes[bytePos];
            Assert.assertEquals(toByte(pbs.peek()), atPos);
            // test position
            Assert.assertEquals(pbs.getPosition(), bytePos);

            // test read
            Assert.assertEquals(toByte(pbs.read()), atPos);
            bytePos++;
            // test position
            Assert.assertEquals(pbs.getPosition(), bytePos);

            // test repeek
            if ( bytePos < bytes.length ) {
                Assert.assertEquals(toByte(pbs.peek()), bytes[bytePos]);
                // test position
                Assert.assertEquals(pbs.getPosition(), bytePos);
            }
        }

        Assert.assertEquals(bytePos, bytes.length);
        pbs.close();
    }

    private void testLineReader(final String lines) throws IOException {
        // read all of the lines into the
        final BufferedReader br = new BufferedReader(new StringReader(lines));
        final List<String> eachLine = new ArrayList<String>();
        while (true) {
            final String line = br.readLine();
            if ( line == null ) break;
            eachLine.add(line);
        }

        final byte[] bytes = lines.getBytes();
        final InputStream is = new ByteArrayInputStream(bytes);
        final PositionalBufferedStream pbs = new PositionalBufferedStream(is);
        final LineReader alr = new AsciiLineReader(pbs); // AsciiLineReader must be used here because it does not read ahead.

        int bytePos = 0, linePos = 0;
        /** 
         * TODO: Requires revision: we're calling readLine() here, but making assumptions about how the underlying input stream operates.
         * Specifically, these tests assume the underlying stream only advances exactly the required number of characters to find the
         * newline, which is not true for most buffered readers.
         */
        while ( ! pbs.isDone() ) {
            Assert.assertTrue(bytePos < bytes.length);

            // test position
            Assert.assertEquals(pbs.getPosition(), bytePos);

            // test read
            final String readLine = alr.readLine();
            Assert.assertEquals(readLine, eachLine.get(linePos));
            linePos++;

            bytePos += readLine.length() + 1; // 1 for the terminator
            // test position
            Assert.assertEquals(pbs.getPosition(), bytePos);
        }

        Assert.assertEquals(linePos, eachLine.size());
        Assert.assertEquals(bytePos, bytes.length);
        pbs.close();
    }

    private final byte toByte(int i) {
        return (byte)(i & 0xFF);
    }
}
