package de.renew.formalism;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;

import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.junit.jupiter.MockitoExtension;

import de.renew.plugin.PluginException;
import de.renew.shadowcompiler.ShadowCompilerFactory;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.mockito.Mockito.mock;

@ExtendWith(MockitoExtension.class)
public class FormalismPluginTest {

    @Test
    public void testInit() {
        // given
        FormalismPlugin testee = null;
        try {
            testee = new FormalismPlugin(new URL("https://uni-hamburg.de"));
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        } catch (PluginException e) {
            throw new RuntimeException(e);
        }

        //when
        testee.init();

        //then
        assertNotEquals(null, testee.getCompilerFactoryByName("Bool Net Compiler"));
        assertNotEquals(null, testee.getCompilerFactoryByName("Java Net Compiler"));
        assertNotEquals(null, testee.getCompilerFactoryByName("Timed Java Compiler"));
        assertNotEquals(null, testee.getCompilerFactoryByName("P/T Net Compiler"));
    }

    @ParameterizedTest
    @ValueSource(
        strings = {
            "Bool Net Compiler", "Java Net Compiler", "Timed Java Compiler", "P/T Net Compiler" })
    public void testSetCompilerWithStandardValue(String input) {
        // given
        FormalismPlugin testee = createTestee();

        // when
        testee.setCompiler(input);

        // then
        assertEquals(input, testee.getCompiler());
    }

    @Test
    public void testSetCompilerWithNull() {
        // given
        FormalismPlugin testee = createTestee();

        // when
        testee.setCompiler(null);

        // then
        assertEquals("Java Net Compiler", testee.getCompiler()); // "Java Net Compiler" is the default compiler
    }

    @ParameterizedTest
    @ValueSource(strings = { "foo", "This Is Test", "This is a six word test" })
    public void testSetCompilerWithInvalidValue(String input) {
        // given
        FormalismPlugin testee = createTestee();

        // when
        testee.setCompiler(input);

        // then
        assertEquals("Java Net Compiler", testee.getCompiler());
    }

    @Test
    public void testGetCompiler() {
        // given
        FormalismPlugin testee = createTestee();

        // when
        String currentCompiler = testee.getCompiler();

        // then
        assertEquals("Java Net Compiler", currentCompiler);
    }

    @Test
    public void testGetCompilerFactoryByNameWithUnregisteredFactory() {
        // given
        FormalismPlugin testee = createTestee();

        // when
        ShadowCompilerFactory unregisteredFactory = testee.getCompilerFactoryByName("foo");

        // then
        assertEquals(null, unregisteredFactory);
    }

    @Test
    public void testAddCompilerFactory() {
        // given
        FormalismPlugin testee = createTestee();
        ShadowCompilerFactory testFactory = mock(ShadowCompilerFactory.class);
        ShadowCompilerFactory testFactory2 = mock(ShadowCompilerFactory.class);

        // when
        testee.addCompilerFactory("test", testFactory);
        testee.addCompilerFactory("test2", testFactory2);

        // then
        assertEquals(testFactory, testee.getCompilerFactoryByName("test"));
        assertEquals(testFactory2, testee.getCompilerFactoryByName("test2"));
        assertNotEquals(testFactory, testee.getCompilerFactoryByName("test2"));
        assertNotEquals(testFactory2, testee.getCompilerFactoryByName("test"));
    }

    @Test
    public void testRemoveCompilerFactory() {
        // given
        FormalismPlugin testee = createTestee();
        ShadowCompilerFactory testFactory = mock(ShadowCompilerFactory.class);

        // when
        testee.addCompilerFactory("test", testFactory);
        testee.removeCompilerFactory("test");

        // then
        assertEquals(null, testee.getCompilerFactoryByName("test"));
    }

    @Test
    public void testAddFormalismChangeListener() {
        // given
        FormalismPlugin testee = createTestee();
        ShadowCompilerFactory testFactory = mock(ShadowCompilerFactory.class);
        ShadowCompilerFactory listenerTest = mock(ShadowCompilerFactory.class);

        // when
        testee.addFormalismChangeListener(new FormalismChangeListener() {
            @Override
            public void formalismChanged(String compilerName, Object source, int action) {
                if (testee.getCompilerFactoryByName("notify") == null) {
                    testee.addCompilerFactory("notify", listenerTest);
                }
            }
        });
        // notify is called by addCompilerFactory and removeCompilerFactory
        testee.addCompilerFactory("test", testFactory);

        // then
        assertEquals(listenerTest, testee.getCompilerFactoryByName("notify"));
    }

    @Test
    public void testRemoveFormalismChangeListener() {
        // given
        FormalismPlugin testee = createTestee();
        ShadowCompilerFactory testFactory = mock(ShadowCompilerFactory.class);
        ShadowCompilerFactory listenerTest = mock(ShadowCompilerFactory.class);
        FormalismChangeListener listener = new FormalismChangeListener() {
            @Override
            public void formalismChanged(String compilerName, Object source, int action) {
                if (testee.getCompilerFactoryByName("notify") == null) {
                    testee.addCompilerFactory("notify", listenerTest);
                }
            }
        };


        // when
        testee.addFormalismChangeListener(listener);
        testee.removeFormalismChangeListener(listener);

        testee.addCompilerFactory("test", testFactory);

        // then
        assertEquals(null, testee.getCompilerFactoryByName("notify"));
    }

    @Test
    public void testGetKnownFormalisms() {
        // given
        FormalismPlugin testee = createTestee();
        ArrayList<String> expected = Lists.newArrayList(
            "Timed Java Compiler", "Bool Net Compiler", "Java Net Compiler", "P/T Net Compiler");

        // when
        ArrayList<String> result = Lists.newArrayList(testee.getKnownFormalisms());

        // then
        assertEquals(expected, result);
    }

    private FormalismPlugin createTestee() {
        FormalismPlugin fp;
        try {
            fp = new FormalismPlugin(new URL("https://google.de"));
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        } catch (PluginException e) {
            throw new RuntimeException(e);
        }

        fp.init();

        return fp;
    }
}