package de.renew.engine.searchqueue;

import java.util.ArrayList;
import java.util.List;

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 static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mockStatic;

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

    private RandomSearchQueue _queue;

    private MockedStatic<SimulationThreadPool> _simulationThreadPoolMockedStatic;

    /**
     * Setup method to initialize this test environment.
     */
    @BeforeEach
    public void setUp() {
        _queue = new RandomSearchQueue(0.0);

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

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

    /**
     * Test method for {@link RandomSearchQueue#include} and
     * {@link RandomSearchQueue#extract}.
     */
    @Test
    public void testIncludeExtract() {
        //when
        _queue.include(_searchable1);
        _queue.include(_searchable2);
        _queue.include(_searchable3);

        List<Searchable> extractedQueue = new ArrayList<>();
        extractedQueue.add(_queue.extract());
        extractedQueue.add(_queue.extract());
        extractedQueue.add(_queue.extract());

        //then
        assertTrue(extractedQueue.contains(_searchable1));
        assertTrue(extractedQueue.contains(_searchable2));
        assertTrue(extractedQueue.contains(_searchable3));
        assertNull(_queue.extract()); //queue empty
    }

    /**
     * Test method for {@link RandomSearchQueue#include}. Inserts one of
     * the searchables multiple times which should be ignored.
     */
    @Test
    public void testIncludeSearchableMultipleTimes() {
        //when
        _queue.include(_searchable1);
        _queue.include(_searchable2);
        _queue.include(_searchable1);
        _queue.include(_searchable3);
        _queue.include(_searchable1);

        //when
        List<Searchable> extractedQueue = new ArrayList<>();
        extractedQueue.add(_queue.extract());
        extractedQueue.add(_queue.extract());
        extractedQueue.add(_queue.extract());

        //then
        assertTrue(extractedQueue.contains(_searchable1));
        assertTrue(extractedQueue.contains(_searchable2));
        assertTrue(extractedQueue.contains(_searchable3));
        assertNull(_queue.extract()); //queue empty
    }

    /**
     * Test method for {@link RandomSearchQueue#extract}.
     * Tests that there are no errors and {@code null} is returned
     * when extracting from an empty queue.
     */
    @Test
    public void testExtractFromEmptyQueue() {
        //when/then
        assertNull(_queue.extract());
    }

    /**
     * Test method for {@link RandomSearchQueue#exclude}.
     */
    @Test
    public void testExclude() {
        //given
        _queue.include(_searchable1);
        _queue.include(_searchable2);
        _queue.include(_searchable3);

        //when
        _queue.exclude(_searchable1);

        //then
        List<Searchable> extractedQueue = new ArrayList<>();
        extractedQueue.add(_queue.extract());
        extractedQueue.add(_queue.extract());

        assertFalse(extractedQueue.contains(_searchable1));
        assertTrue(extractedQueue.contains(_searchable2));
        assertTrue(extractedQueue.contains(_searchable3));
        assertNull(_queue.extract()); //queue empty
    }

    /**
     * Test method for {@link RandomSearchQueue#exclude}.
     * Tests that no error is thrown if a {@link Searchable} that
     * is not in the queue is excluded.
     */
    @Test
    public void testExcludeSearchableNotInQueue() {
        //given
        _queue.include(_searchable1);
        _queue.include(_searchable2);

        //when
        _queue.exclude(_searchable3);

        //then
        List<Searchable> extractedQueue = new ArrayList<>();
        extractedQueue.add(_queue.extract());
        extractedQueue.add(_queue.extract());

        assertTrue(extractedQueue.contains(_searchable1));
        assertTrue(extractedQueue.contains(_searchable2));
        assertNull(_queue.extract()); //queue empty
    }

    /**
     * Test method for {@link RandomSearchQueue#exclude}.
     * Tests that no error is thrown when excluding a {@link Searchable}
     * from an empty queue.
     */
    @Test
    public void testExcludeFromEmptyQueue() {
        //then
        _queue.exclude(_searchable1);

        //then
        assertNull(_queue.extract()); //queue empty
    }
}
