package de.renew.formalism.function;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import de.renew.unify.IStateRecorder;
import de.renew.unify.Impossible;
import de.renew.unify.Tuple;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ConstructorFunctionTest {

    Constructor<String> _stringConstructor;
    ConstructorFunction _constrFunc;
    IStateRecorder _recorder;
    Constructor<ConstructorProvider> _constructorProviderConstructor;
    ConstructorFunction _constructorProviderConstructorFunc;


    @BeforeEach
    void setUp() throws NoSuchMethodException {
        _stringConstructor = String.class.getConstructor(String.class);
        _constrFunc = new ConstructorFunction(_stringConstructor);
        _constructorProviderConstructor = ConstructorProvider.class.getConstructor(String.class);
        _constructorProviderConstructorFunc =
            new ConstructorFunction(_constructorProviderConstructor);
    }

    @Test
    public void testFunctionUsingStringValidInput() throws Exception {
        //given
        String[] inputStringSingle = { "test" };
        Tuple correctInput = new Tuple(inputStringSingle, _recorder);
        //when
        String actualResultString = (String) _constrFunc.function(correctInput);
        String expectedResultString = _stringConstructor.newInstance((Object[]) inputStringSingle);
        //then
        assertEquals(expectedResultString, actualResultString);
        assertNotEquals("random string", actualResultString);
    }

    @Test
    public void testFunctionUsingStringInvalidInput() {
        //given
        String[] inputStringMultiple = { "test", "test2" };
        Tuple invalidInput = new Tuple(inputStringMultiple, _recorder);
        //when/then
        assertThrows(Impossible.class, () -> _constrFunc.function(invalidInput));
    }

    @Test
    public void testFunctionConstructorThrowsInvocationTargetException() {
        //given
        String[] iteString = { "ite" };
        Tuple iteInput = new Tuple(iteString, _recorder);
        //when/then
        assertThrows(
            Impossible.class, () -> _constructorProviderConstructorFunc.function(iteInput));
    }

    @Test
    public void testFunctionConstructorThrowsNoSuchMethodException() {
        //given
        String[] nsmeString = { "nsme" };
        Tuple nsmeInput = new Tuple(nsmeString, _recorder);
        //when/then
        assertThrows(
            Impossible.class, () -> _constructorProviderConstructorFunc.function(nsmeInput));
    }

    @Test
    public void testFunctionConstructorThrowsLinkageError() {
        //given
        String[] leString = { "le" };
        Tuple leInput = new Tuple(leString, _recorder);
        //when/then
        assertThrows(Impossible.class, () -> _constructorProviderConstructorFunc.function(leInput));
    }

    @Test
    public void testFunctionNoThrowable() {
        //given
        String[] noErrString = { "noThrow" };
        Tuple noThrowInput = new Tuple(noErrString, _recorder);
        //when/then
        assertDoesNotThrow(() -> _constructorProviderConstructorFunc.function(noThrowInput));
    }

    @Test
    public void testToString() {
        //given (in setup)
        //when
        String actualOutput = _constrFunc.toString();
        //then
        assertEquals("ConstrFunc: " + _stringConstructor.toString(), actualOutput);
        assertNotEquals("random string", actualOutput);
    }
}


/*
local class with constructor that does nothing but throw exceptions and errors
this constructor covers all exceptions and error that could be thrown when calling function() method
 */
@SuppressWarnings("checkstyle:OneTopLevelClass")
class ConstructorProvider {
    //constructor that does nothing but throw exceptions and error based on the given string
    public ConstructorProvider(String s) throws InvocationTargetException, NoSuchMethodException {
        switch (s) {
            case "ite":
                throw new InvocationTargetException(new Exception(""));
            case "nsme":
                throw new NoSuchMethodException("");
            case "le":
                throw new LinkageError();
        }
    }
}
