package de.renew.application;

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.Mock;
import org.mockito.MockitoAnnotations;

import de.renew.shadowcompiler.DefaultShadowNetLoader;
import de.renew.simulator.api.IFinderRegistration;
import de.renew.simulatorontology.loading.Finder;
import de.renew.simulatorontology.loading.PathlessFinder;
import de.renew.simulatorontology.shadow.ShadowNetLoader;

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

/**
 * This class tests the {@link FinderRegistrationImpl}.
 */
public class FinderRegistrationImplTest {
    private List<Finder> _finders;
    private List<PathlessFinder> _pathlessFinders;
    private IFinderRegistration _finderRegistration;
    @Mock
    private DefaultShadowNetLoader _netLoader;
    @Mock
    private Finder _finder;
    @Mock
    private PathlessFinder _pathlessFinder;
    private AutoCloseable _closeable;

    @BeforeEach
    public void setup() {
        _closeable = MockitoAnnotations.openMocks(this);
        _finderRegistration = new FinderRegistrationImpl();
        _finders = List.of(_finder, mock(Finder.class));
        _pathlessFinders = List.of(_pathlessFinder, mock(PathlessFinder.class));
    }

    @AfterEach
    public void tearDown() throws Exception {
        _closeable.close();
    }

    /**
     * Tests the method {@link FinderRegistrationImpl#setNetLoader(ShadowNetLoader)}.
     * It asserts that the registered {@link Finder} and {@link PathlessFinder} instances are registered
     * on the given {@link DefaultShadowNetLoader}.
     */
    @Test
    public void testSetNetLoaderWithRegisteredFinders() {
        // Given
        _finderRegistration.registerDefaultNetFinder(_finder);
        _finderRegistration.registerDefaultPathlessFinder(_pathlessFinder);
        // when
        _finderRegistration.setNetLoader(_netLoader);
        // Then
        assertThat(_finderRegistration.getNetLoader()).isEqualTo(_netLoader);
        verify(_netLoader).registerFinder(_finder);
        verify(_netLoader).registerPathlessFinder(_pathlessFinder);
    }

    /**
     * Tests, that the  method {@link FinderRegistrationImpl#setNetLoader(ShadowNetLoader)}. throws
     * an exception when the given {@link ShadowNetLoader} is not a {@link DefaultShadowNetLoader}.
     */
    @Test
    public void testSetNetLoaderWithoutDefaultShadowNetLoader() {
        // when/then
        assertThatThrownBy(() -> _finderRegistration.setNetLoader(mock(ShadowNetLoader.class)))
            .isInstanceOf(IllegalArgumentException.class)
            .hasMessage("A DefaultShadowNetLoader was expected!");
    }

    /**
     * Tests, that the method {@link FinderRegistrationImpl#registerDefaultNetFinder(Finder)} registers the
     * given {@link Finder} on the underlying {@link DefaultShadowNetLoader}.
     */
    @Test
    public void testRegisterDefaultNetFinderOnLoader() {
        // when
        _finderRegistration.setNetLoader(_netLoader);
        _finders.forEach(_finderRegistration::registerDefaultNetFinder);
        // Then
        _finders.forEach(finder -> verify(_netLoader).registerFinder(finder));
    }

    /**
     * Tests, that the method {@link FinderRegistrationImpl#registerDefaultNetFinder(Finder)} registers the
     * given {@link Finder} in its internal list.
     */
    @Test
    public void testRegisterDefaultNetFinderInsertion() {
        // when
        _finders.forEach(_finderRegistration::registerDefaultNetFinder);
        _finderRegistration.setNetLoader(_netLoader);
        // Then
        _finders.forEach(finder -> verify(_netLoader).registerFinder(finder));
    }

    /**
     * Tests, that the method {@link FinderRegistrationImpl#removeDefaultNetFinder(Finder)} removes the
     * given {@link Finder} from the underlying {@link DefaultShadowNetLoader}.
     */
    @Test
    public void testRemoveDefaultNetFinderFromLoader() {
        // when
        _finders.forEach(_finderRegistration::registerDefaultNetFinder);
        _finderRegistration.setNetLoader(_netLoader);
        // Then
        _finders.forEach(_finderRegistration::removeDefaultNetFinder);
        _finders.forEach(finder -> verify(_netLoader).removeFinder(finder));
    }

    /**
     * Tests, that the method {@link FinderRegistrationImpl#removeDefaultNetFinder(Finder)} removes the
     * given {@link Finder} from the internal list.
     */
    @Test
    public void testRemoveDefaultNetFinder() {
        // when
        _finders.forEach(_finderRegistration::registerDefaultNetFinder);
        _finderRegistration.removeDefaultNetFinder(_finder);
        _finderRegistration.setNetLoader(_netLoader);
        // Then
        verify(_netLoader).registerFinder(_finders.get(1));
    }

    /**
     * Tests, that the method {@link FinderRegistrationImpl#registerDefaultPathlessFinder(PathlessFinder)} registers the
     * given {@link PathlessFinder} on the underlying {@link DefaultShadowNetLoader}.
     */
    @Test
    public void testRegisterPathlessFinderOnLoader() {
        // when
        _finderRegistration.setNetLoader(_netLoader);
        _pathlessFinders.forEach(_finderRegistration::registerDefaultPathlessFinder);
        // Then
        _pathlessFinders.forEach(finder -> verify(_netLoader).registerPathlessFinder(finder));
    }

    /**
     * Tests, that the method {@link FinderRegistrationImpl#registerDefaultPathlessFinder(PathlessFinder)} registers the
     * given {@link PathlessFinder} in its internal list.
     */
    @Test
    public void testRegisterPathlessFinderInsertion() {
        // when
        _pathlessFinders.forEach(_finderRegistration::registerDefaultPathlessFinder);
        _finderRegistration.setNetLoader(_netLoader);
        // Then
        _pathlessFinders.forEach(finder -> verify(_netLoader).registerPathlessFinder(finder));
    }

    /**
     * Tests, that the method {@link FinderRegistrationImpl#removeDefaultPathlessFinder(PathlessFinder)} removes the
     * given {@link PathlessFinder} from the underlying {@link DefaultShadowNetLoader}.
     */
    @Test
    public void testRemovePathlessFinderFromLoader() {
        // when
        _pathlessFinders.forEach(_finderRegistration::removeDefaultPathlessFinder);
        _finderRegistration.setNetLoader(_netLoader);
        // Then
        _pathlessFinders.forEach(_finderRegistration::removeDefaultPathlessFinder);
        _pathlessFinders.forEach(finder -> verify(_netLoader).removePathlessFinder(finder));
    }

    /**
     * Tests, that the method {@link FinderRegistrationImpl#removeDefaultPathlessFinder(PathlessFinder)} removes the
     * given {@link PathlessFinder} from the internal list.
     */
    @Test
    public void testRemovePathlessFinder() {
        // when
        _pathlessFinders.forEach(_finderRegistration::registerDefaultPathlessFinder);
        _finderRegistration.removeDefaultPathlessFinder(_pathlessFinder);
        _finderRegistration.setNetLoader(_netLoader);
        // Then
        verify(_netLoader).registerPathlessFinder(_pathlessFinders.get(1));
    }
}