package de.renew.net;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import de.renew.engine.thread.SimulationThreadPool;
import de.renew.net.event.TransitionEventListener;
import de.renew.net.inscription.transition.ActionInscription;
import de.renew.net.inscription.transition.ManualInscription;
import de.renew.net.inscription.transition.UplinkInscription;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;

/**
 * Test class for {@link Transition}.
 */
public class TransitionTest {
    private static final String TRANSITION_NAME = "cool transition";
    private static final NetElementID TRANSITION_ID = new NetElementID(123);
    private static final String INSCRIPTION_NAME = "cool inscription";

    private Net _net;

    private Transition _transition;

    private MockedStatic<SimulationThreadPool> _simulationThreadPoolMockedStatic;

    private AutoCloseable _closeable;

    /**
     * Setup method to initialize this test environment.
     */
    @BeforeEach
    public void setUp() {
        _closeable = MockitoAnnotations.openMocks(this);

        _simulationThreadPoolMockedStatic = mockStatic(SimulationThreadPool.class);
        _simulationThreadPoolMockedStatic.when(SimulationThreadPool::isSimulationThread)
            .thenReturn(true);

        _net = Mockito.spy(new Net("cool net"));
        _transition = new Transition(_net, TRANSITION_NAME, TRANSITION_ID);

    }

    /**
     * Teardown method to clean up this test environment.
     *
     * @throws Exception if there is an error while closing the mocks
     */
    @AfterEach
    public void tearDown() throws Exception {
        _simulationThreadPoolMockedStatic.close();
        _closeable.close();
    }

    /**
     * Test method for the constructor of {@link Transition}.
     * Checks that all variables are initialized correctly.
     */
    @Test
    public void testConstructor() {
        //then
        verify(_net).add(_transition);

        assertThat(_transition.getID()).isEqualTo(TRANSITION_ID);
        assertThat(_transition.getName()).isEqualTo(TRANSITION_NAME);
        assertThat(_transition.getTrace()).isTrue();
        assertThat(_transition.getUplink()).isNull();
        assertThat(_transition.inscriptions()).isEmpty();
    }

    /**
     * Test method for {@link Transition#isSpontaneous}.
     */
    @Test
    public void testIsSpontaneous() {
        //then
        assertThat(_transition.isSpontaneous()).isTrue();
    }

    /**
     * Test method for {@link Transition#isSpontaneous} with a {@link ManualInscription}.
     */
    @Test
    public void testIsSpontaneousWithManualInscription() {
        //given
        _transition.add(ManualInscription.getInstance());

        //then
        assertThat(_transition.isSpontaneous()).isFalse();
    }

    /**
     * Test method for {@link Transition#isSpontaneous} with an {@link UplinkInscription}.
     */
    @Test
    public void testIsSpontaneousWithUplinkInscription() {
        //given
        _transition.add(new UplinkInscription(INSCRIPTION_NAME, null));

        //then
        assertThat(_transition.isSpontaneous()).isFalse();
    }

    /**
     * Test method for {@link Transition#listensToChannel}.
     */
    @Test
    public void testListensToChannel() {
        //given
        _transition.add(new UplinkInscription(INSCRIPTION_NAME, null));

        //then
        assertThat(_transition.listensToChannel(INSCRIPTION_NAME)).isTrue();
    }

    /**
     * Test method for {@link Transition#listensToChannel}.
     */
    @Test
    public void testListensToChannelFalse() {
        //given
        _transition.add(new UplinkInscription(INSCRIPTION_NAME, null));

        //then
        assertThat(_transition.listensToChannel("uncool inscription")).isFalse();
    }

    /**
     * Test method for {@link Transition#add(TransitionInscription)} and
     * {@link Transition#remove(TransitionInscription)}.
     */
    @Test
    public void testAddAndRemove() {
        //given
        UplinkInscription inscription = new UplinkInscription(INSCRIPTION_NAME, null);

        //when
        _transition.add(inscription);

        //then
        assertThat(_transition.inscriptions()).hasSize(1);
        assertThat(_transition.getUplink()).isEqualTo(inscription);

        //when
        _transition.remove(inscription);

        //then
        assertThat(_transition.inscriptions()).hasSize(0);
        assertThat(_transition.getUplink()).isNull();
    }

    /**
     * Test method for {@link Transition#isManual}.
     */
    @Test
    public void testIsManual() {
        //given
        _transition.add(ManualInscription.getInstance());

        //then
        assertThat(Transition.isManual(_transition)).isTrue();
    }

    /**
     * Test method for {@link Transition#isManual}.
     */
    @Test
    public void testIsManualFalse() {
        //given
        _transition.add(new ActionInscription(null, _transition));

        //then
        assertThat(Transition.isManual(_transition)).isFalse();
    }

    /**
     * Test method for checking that a place can be serialized and deserialized
     * properly. This is not a test for a specific method in {@link Transition}.
     *
     * @throws IOException if there is an error with the streams
     * @throws ClassNotFoundException if there is an error while reading the place
     */
    @Test
    public void testSerializationDeserialization() throws IOException, ClassNotFoundException {
        //given
        TransitionEventListener mockListener = mock(TransitionEventListener.class);
        _transition.addTransitionEventListener(mockListener);

        String comment = "cool comment";
        _transition.setComment(comment);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectStream = new ObjectOutputStream(outputStream);

        //when
        objectStream.writeObject(_transition);
        objectStream.close();

        ByteArrayInputStream bais = new ByteArrayInputStream(outputStream.toByteArray());
        ObjectInputStream inputStream = new ObjectInputStream(bais);
        Transition deserializedTransition = (Transition) inputStream.readObject();
        inputStream.close();

        //then
        assertThat(deserializedTransition.getID()).isEqualTo(_transition.getID());
        assertThat(deserializedTransition.getName()).isEqualTo(_transition.getName());
        assertThat(deserializedTransition.getTrace()).isEqualTo(_transition.getTrace());
        assertThat(deserializedTransition.getComment()).isEqualTo(_transition.getComment());

        assertThat(deserializedTransition.getListenerSet()).isNotNull();
    }

}