package de.renew.engine.searchqueue;

import java.io.IOException;

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

import de.renew.engine.searcher.Searchable;
import de.renew.engine.thread.SimulationThreadPool;
import de.renew.util.RenewObjectInputStream;
import de.renew.util.RenewObjectOutputStream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;

/**
 * Test class for the {@link SearchQueue}.
 */
public class SearchQueueTest {
    private final Searchable _searchable1 = new EmptySearchable();
    private final Searchable _searchable2 = new EmptySearchable();
    private final Searchable _searchable3 = new EmptySearchable();

    private MockedStatic<SimulationThreadPool> _simulationThreadPoolMockedStatic;


    /**
     * Setup method to initialize this test environment.
     */
    @BeforeEach
    public void setUp() {
        _simulationThreadPoolMockedStatic = mockStatic(SimulationThreadPool.class);
        _simulationThreadPoolMockedStatic.when(SimulationThreadPool::isSimulationThread)
            .thenReturn(true);
        SearchQueue.reset(0.0);
    }

    /**
     * Teardown method to clean up this test environment.
     */
    @AfterEach
    public void tearDown() {
        _simulationThreadPoolMockedStatic.close();
    }

    /**
     * Test method for {@link SearchQueue#reset}.
     * Checks that queue is emptied and time is set to the new start time.
     */
    @Test
    public void testReset() {
        // given
        double newTime = 3.0;
        SearchQueue.include(_searchable1, 0.0);
        SearchQueue.include(_searchable2, 1.0);
        SearchQueue.include(_searchable3, 2.0);

        // when
        SearchQueue.reset(newTime);

        // then
        assertNull(SearchQueue.extract());
        assertEquals(newTime, SearchQueue.getTime());
    }

    /**
     * Test method for {@link SearchQueue#advanceTime()}.
     * Checks that time is advanced to the timestamp of the earliest queue.
     */
    @Test
    public void testAdvanceTime() {
        // given
        assertEquals(0.0, SearchQueue.getTime());

        double earliestTime = 3.0;
        SearchQueue.include(_searchable2, earliestTime + 1.0);
        SearchQueue.include(_searchable1, earliestTime);

        // when
        SearchQueue.advanceTime();

        // then
        assertEquals(earliestTime, SearchQueue.getTime());
    }

    /**
     * Test method for {@link SearchQueue#include} and {@link SearchQueue#extract}.
     */
    @Test
    public void testIncludeExtract() {
        // when
        SearchQueue.include(_searchable1, 0.0);
        SearchQueue.include(_searchable2, 1.0);
        SearchQueue.include(_searchable3, 2.0);

        // when/then
        assertEquals(_searchable1, SearchQueue.extract());
        assertEquals(_searchable2, SearchQueue.extract());
        assertEquals(_searchable3, SearchQueue.extract());
        assertNull(SearchQueue.extract());
    }

    /**
     * Test method for {@link SearchQueue#saveQueue} and
     * {@link SearchQueue#loadQueue}.
     *
     * @throws IOException if there is an error with the streams
     * @throws ClassNotFoundException if there is an error while loading the net
     */
    @Test
    public void testSaveAndLoadQueue() throws IOException, ClassNotFoundException {
        //given
        SearchQueue.include(_searchable1, 0.0);
        SearchQueue.include(_searchable2, 1.0);
        SearchQueue.include(_searchable3, 2.0);

        RenewObjectOutputStream roos = mock(RenewObjectOutputStream.class);

        //when
        SearchQueue.saveQueue(roos);

        //then
        verify(roos).beginDomain(SearchQueue.class);
        verify(roos).writeDelayedObjects();
        verify(roos).endDomain(SearchQueue.class);

        //given
        RenewObjectInputStream rois = mock(RenewObjectInputStream.class);

        //when
        SearchQueue.loadQueue(rois);

        //then
        verify(rois).readDelayedObjects();
    }
}
