package com.atlassian.stash.internal.scm.git.io;

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

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.*;

import static com.google.common.primitives.Bytes.concat;
import static org.apache.commons.lang3.RandomStringUtils.randomAscii;
import static org.junit.Assert.*;

public class ByteSegmentScannerTest {

    private static final int MAX_BUFFER_SIZE = 4096;
    private static final byte[] DELIMITER_NULL = new byte[]{(byte) 0x0};
    private static final Random rand = new Random(4957666);

    @Test
    public void testOneReadPerSegment() throws IOException {
        byte[] B1 = bytes(20);
        byte[] B2 = bytes(20);

        List<byte[]> expected = ImmutableList.of(B1, B2);
        List<byte[]> testData = ImmutableList.of(B1, getDelimiter(), B2, getDelimiter());

        runTest(expected, testData);
    }

    @Test
    public void testOneReadMultipleSegments() throws IOException {
        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);
        List<byte[]> testData = ImmutableList.of(B1, B2, B3, B4, getDelimiter(), B5, getDelimiter());

        runTest(expected, testData);
    }

    @Test
    public void testMultipleReadsSingleSegment() throws IOException {
        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));
        List<byte[]> testData = ImmutableList.of(B1, B2, B3, B4, B5, getDelimiter());

        runTest(expected, testData);
    }

    @Test
    public void testWithoutTrailingDelimiter() throws IOException {
        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));
        List<byte[]> testData = ImmutableList.of(B1, B2, B3, B4, B5);

        runTest(expected, testData);
    }

    @Test
    public void testWithEmptySegment() throws IOException {
        byte[] B1 = bytes(20);
        byte[] B2 = bytes(20);
        byte[] EMPTY = new byte[]{};

        List<byte[]> expected = ImmutableList.of(B1, EMPTY, B2);
        List<byte[]> testData = ImmutableList.of(B1, getDelimiter(), getDelimiter(), B2, getDelimiter());

        runTest(expected, testData);
    }

    @Test
    public void testJustUnderMaxBufferSize() throws IOException {
        byte[] B1 = bytes(MAX_BUFFER_SIZE);
        byte[] B2 = bytes(MAX_BUFFER_SIZE);

        List<byte[]> expected = ImmutableList.of(B1, B2);
        List<byte[]> testData = ImmutableList.of(B1, getDelimiter(), B2, getDelimiter());

        runTest(expected, testData);
    }

    @Test(expected = IllegalStateException.class)
    public void testOverMaxBufferSize() throws IOException {
        byte[] B1 = bytes(MAX_BUFFER_SIZE + 1);

        List<byte[]> testData = ImmutableList.of(B1, getDelimiter());

        readSegments(testData.iterator());
    }

    protected byte[] bytes(int count) {
        return randomAscii(count).getBytes();
    }

    protected byte[] getDelimiter() {
        return DELIMITER_NULL;
    }

    private List<byte[]> readSegments(final Iterator<byte[]> inputChunks) throws IOException {
        try (PipedOutputStream sink = new PipedOutputStream();
             PipedInputStream segStream = new PipedInputStream(sink)) {

            Thread t = new Thread(() -> {
                try {
                    while (inputChunks.hasNext()) {
                        sink.write(inputChunks.next());
                    }
                    sink.close();
                } catch (IOException e) {
                    fail("IOException caught while writing segments");
                }
            });
            t.start();

            List<byte[]> segments = new ArrayList<>();
            ByteSegmentScanner scanner = new ByteSegmentScanner(segStream, getDelimiter()[0], MAX_BUFFER_SIZE);
            byte[] segment;
            while ((segment = scanner.readSegment()) != null) {
                segments.add(segment);
            }

            try {
                t.join();
            } catch (InterruptedException e) {
                throw new RuntimeException("Thread did not join", e);
            }

            return segments;
        }
    }

    protected void runTest(List<byte[]> expected, List<byte[]> testData) throws IOException {
        // Allocate a container for the results
        List<byte[]> actual = readSegments(testData.iterator());

        assertEquals("Results must match testSegments", expected.size(), actual.size());

        // Compare each segment to verify content matches
        for (int index = 0; index < expected.size(); index++) {

            byte[] expectedValue = expected.get(index);
            byte[] computedValue = actual.get(index);

            assertTrue(String.format(
                            "Segment value [%s] [%d] did not match expected value [%s] [%d] from segment %d.",
                            new String(computedValue), computedValue.length,
                            new String(expectedValue), expectedValue.length,
                            index),
                    Arrays.equals(computedValue, expectedValue));
        }
    }
}
