package com.atlassian.bitbucket.mesh.util;

import com.google.common.collect.ImmutableList;
import org.junit.Test;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static com.google.common.primitives.Bytes.concat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

public class ByteSegmentScannerTest extends AbstractByteSegmentTest {

    @Test
    public void testScanSeparateSegments() {
        byte[] b1 = bytes(20);
        byte[] b2 = bytes(20);

        ImmutableList<byte[]> expected = ImmutableList.of(b1, b2);
        ByteBuffer testData = toBuffer(b1, getDelimiter(), b2, getDelimiter());

        runTest(expected, testData, true);
    }

    @Test
    public void testScanMultipleSegments() {
        byte[] b1 = bytes(20);
        byte[] b2 = bytes(20);
        byte[] b3 = bytes(20);
        byte[] b4 = bytes(20);
        byte[] b5 = bytes(20);

        List<byte[]> expected = ImmutableList.of(concat(b1, b2, b3, b4), b5);
        ByteBuffer testData = toBuffer(b1, b2, b3, b4, getDelimiter(), b5, getDelimiter());

        runTest(expected, testData, true);
    }

    @Test
    public void testScanPartialReads() {
        byte[] b1 = bytes(20);
        byte[] b2 = bytes(20);
        byte[] b3 = bytes(20);
        byte[] b4 = bytes(20);
        byte[] b5 = bytes(20);

        List<byte[]> expected = ImmutableList.of(concat(b1, b2, b3, b4, b5));
        ByteBuffer testData = toBuffer(b1, b2, b3, b4, b5, getDelimiter());

        //Simulate the buffer not containing the complete segment by using slices to gradually
        //"reveal" the test data
        ByteSegmentScanner scanner = new ByteSegmentScanner(getDelimiter()[0], MAX_BUFFER_SIZE);
        for (int i = 1; i < 5; ++i) {
            ByteBuffer subset = testData.slice();
            subset.limit(i * 20);

            assertNull("Unexpected result from partial read at " + (i * 20) + " bytes", scanner.scan(subset, false));
            assertEquals("Buffer position should not have advanced", 0, subset.position());
        }

        //Lastly, expose the full buffer and verify the segment is scanned
        runTest(expected, testData, true);
    }

    @Test
    public void testScanWithoutTrailingDelimiter() {
        byte[] b1 = bytes(20);
        byte[] b2 = bytes(20);
        byte[] b3 = bytes(20);
        byte[] b4 = bytes(20);
        byte[] b5 = bytes(20);

        List<byte[]> expected = ImmutableList.of(concat(b1, b2, b3, b4, b5));
        ByteBuffer testData = toBuffer(b1, b2, b3, b4, b5);

        runTest(expected, testData, true);
    }

    @Test
    public void testScanWithEmptySegment() {
        byte[] b1 = bytes(20);
        byte[] b2 = bytes(20);
        byte[] empty = new byte[0];

        List<byte[]> expected = ImmutableList.of(b1, empty, b2);
        ByteBuffer testData = toBuffer(b1, getDelimiter(), getDelimiter(), b2, getDelimiter());

        runTest(expected, testData, true);
    }

    @Test
    public void testScanJustUnderMaxBufferSize() {
        byte[] b1 = bytes(MAX_BUFFER_SIZE);
        byte[] b2 = bytes(MAX_BUFFER_SIZE);

        List<byte[]> expected = ImmutableList.of(b1, b2);
        ByteBuffer testData = toBuffer(b1, getDelimiter(), b2, getDelimiter());

        runTest(expected, testData, false);
    }

    @Test
    public void testScanOverMaxBufferSizeWithDelimiterClosed() {
        byte[] b1 = bytes(MAX_BUFFER_SIZE + 1);
        byte[] b2 = bytes(MAX_BUFFER_SIZE + 1);

        List<byte[]> expected = ImmutableList.of(b1, b2);
        ByteBuffer testData = toBuffer(b1, getDelimiter(), b2, getDelimiter());

        runTest(expected, testData, true);
    }

    @Test
    public void testScanOverMaxBufferSizeWithDelimiterOpen() {
        byte[] b1 = bytes(MAX_BUFFER_SIZE + 1);
        byte[] b2 = bytes(MAX_BUFFER_SIZE + 1);

        List<byte[]> expected = ImmutableList.of(b1, b2);
        ByteBuffer testData = toBuffer(b1, getDelimiter(), b2, getDelimiter());

        runTest(expected, testData, false);
    }

    @Test(expected = IllegalStateException.class)
    public void testScanOverMaxBufferSizeWithoutDelimiterClosed() {
        ByteBuffer testData = toBuffer(bytes(MAX_BUFFER_SIZE + 1));

        runTest(ImmutableList.of(), testData, true);
    }

    @Test(expected = IllegalStateException.class)
    public void testScanOverMaxBufferSizeWithoutDelimiterOpen() {
        ByteBuffer testData = toBuffer(bytes(MAX_BUFFER_SIZE + 1));

        runTest(ImmutableList.of(), testData, false);
    }

    private static void runTest(List<byte[]> expected, ByteBuffer testData, boolean closed) {
        List<byte[]> actual = new ArrayList<>(expected.size());

        ByteSegmentScanner scanner = new ByteSegmentScanner(getDelimiter()[0], MAX_BUFFER_SIZE);
        while (true) {
            ByteBuffer segment = scanner.scan(testData, closed);
            if (segment == null) {
                break;
            }

            byte[] bytes = new byte[segment.remaining()];
            segment.get(bytes);
            actual.add(bytes);
        }

        assertSegments(expected, actual);
    }

    private static ByteBuffer toBuffer(byte[]... arrays) {
        int size = Arrays.stream(arrays).mapToInt(array -> array.length).sum();

        ByteBuffer buffer = ByteBuffer.allocate(size);
        for (byte[] array : arrays) {
            buffer.put(array);
        }
        buffer.flip();

        return buffer;
    }
}