package com.atlassian.bitbucket.mesh.io;

import com.atlassian.bitbucket.mesh.junit.LogListener;
import org.hamcrest.CoreMatchers;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.InOrder;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.util.Random;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

public class ListenableWritableByteChannelTest {

    @Rule
    public final LogListener logListener = new LogListener(ListenableWritableByteChannel.class);

    @Test
    public void testListenersInvokedOnClose() throws IOException {
        WritableByteChannel delegate = mock(WritableByteChannel.class);
        Runnable listener1 = mock(Runnable.class);
        Runnable listener2 = mock(Runnable.class);
        doAnswer(ignored -> {
            throw new IllegalStateException("Testing exception handling");
        }).when(listener2).run();
        doAnswer(ignored -> {
            throw new IOException("Test exception on close");
        }).when(delegate).close();
        Runnable listener3 = mock(Runnable.class);

        byte[] bytes = new byte[1024];
        new Random(978394628).nextBytes(bytes);
        ByteBuffer buffer = ByteBuffer.wrap(bytes);

        ListenableWritableByteChannel byteChannel = new ListenableWritableByteChannel(delegate);
        try {
            byteChannel.addListener(listener1);
            byteChannel.write(buffer);
            byteChannel.addListener(listener2);
            byteChannel.write(buffer);
            byteChannel.addListener(listener3);
        } finally {
            IOException e = assertThrows(IOException.class, byteChannel::close);
            Throwable[] suppressed = e.getSuppressed();
            assertEquals(1, suppressed.length);
            assertThat(suppressed[0], CoreMatchers.instanceOf(IllegalStateException.class));
        }

        //Verify that listeners added after byteChannel has been closed are invoked immediately
        Runnable listener4 = mock(Runnable.class);
        byteChannel.addListener(listener4);

        InOrder inOrder = inOrder(delegate, listener1, listener2, listener3, listener4);
        inOrder.verify(delegate, times(2)).write(buffer);
        inOrder.verify(delegate).close();
        inOrder.verify(listener1).run();
        inOrder.verify(listener2).run();
        inOrder.verify(listener3).run();
        inOrder.verify(listener4).run();

        //Calling close again should call delegate.close() again, but _not_ invoke the listeners again
        IOException e = assertThrows(IOException.class, byteChannel::close);
        assertEquals(0, e.getSuppressed().length);
        inOrder.verify(delegate).close();
        inOrder.verifyNoMoreInteractions();
    }
}