package targetast;

import com.google.common.reflect.TypeToken;
import de.dhbwstuttgart.core.JavaTXCompiler;
import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.parser.scope.JavaClassName;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.target.generate.ASTToTargetAST;
import de.dhbwstuttgart.target.tree.TargetStructure;
import de.dhbwstuttgart.typeinference.result.ResultSet;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.nio.file.Path;
import java.util.*;

import static org.junit.jupiter.api.Assertions.*;

public class ASTToTypedTargetAST {

    @Test
    public void emptyClass() {
        ClassOrInterface emptyClass = new ClassOrInterface(0, new JavaClassName("EmptyClass"), new ArrayList<>(), Optional.empty(), Optional.empty(), new ArrayList<>(), new ArrayList<>(), new GenericDeclarationList(new ArrayList<>(), new NullToken()), new RefType(new JavaClassName("Object"), new NullToken()), false, false, new ArrayList<>(), new ArrayList<>(), new NullToken(), null);
        ResultSet emptyResultSet = new ResultSet(new HashSet<>());
        TargetStructure emptyTargetClass = new ASTToTargetAST(List.of(emptyResultSet), TestCodegen.createClassLoader()).convert(emptyClass);
        assert emptyTargetClass.getName().equals("EmptyClass");
        assert emptyTargetClass.methods().size() == 0;
        assert emptyTargetClass.fields().size() == 0;
    }

    @Test
    public void overloading() throws Exception {
        var classLoader = TestCodegen.createClassLoader();

        var file = Path.of(System.getProperty("user.dir"), "/resources/bytecode/javFiles/Overloading.jav").toFile();
        var compiler = new JavaTXCompiler(file);
        var resultSet = compiler.typeInference(file);
        var converter = new ASTToTargetAST(compiler, resultSet, classLoader);
        var classes = compiler.sourceFiles.get(file).getClasses();

        var overloading = TestCodegen.generateClass(converter.convert(classes.get(0)), classLoader);
        var overloading2 = TestCodegen.generateClass(converter.convert(classes.get(1)), classLoader);

        var test1 = overloading.getDeclaredMethod("test", overloading);
        test1.setAccessible(true);
        var test2 = overloading.getDeclaredMethod("test", overloading2);
        test2.setAccessible(true);
        Object overloadingInstance = overloading.getDeclaredConstructor().newInstance();
        Object overloading2Instance = overloading2.getDeclaredConstructor().newInstance();
        assertEquals(test1.invoke(overloadingInstance, overloadingInstance), "Overloading");
        assertEquals(test2.invoke(overloadingInstance, overloading2Instance), "Overloading2");
    }

    @Test
    public void tphsAndGenerics() throws Exception {
        var classLoader = TestCodegen.createClassLoader();

        var file = Path.of(System.getProperty("user.dir"), "/resources/bytecode/javFiles/Tph2.jav").toFile();
        var compiler = new JavaTXCompiler(file);
        var resultSet = compiler.typeInference(file);
        var converter = new ASTToTargetAST(compiler, resultSet, classLoader);
        var classes = compiler.sourceFiles.get(file).getClasses();

        var tphAndGenerics = TestCodegen.generateClass(converter.convert(classes.get(0)), classLoader);
    }

    @Test
    public void cycles() throws Exception {
        var classLoader = TestCodegen.createClassLoader();

        var file = Path.of(System.getProperty("user.dir"), "/resources/bytecode/javFiles/Cycle.jav").toFile();
        var compiler = new JavaTXCompiler(file);
        var resultSet = compiler.typeInference(file);
        var converter = new ASTToTargetAST(compiler, resultSet, classLoader);
        var classes = compiler.sourceFiles.get(file).getClasses();

        var cycle = TestCodegen.generateClass(converter.convert(classes.get(0)), classLoader);
    }

    @Test
    public void infimum() throws Exception {
        var classLoader = TestCodegen.createClassLoader();

        var file = Path.of(System.getProperty("user.dir"), "/resources/bytecode/javFiles/Infimum.jav").toFile();
        var compiler = new JavaTXCompiler(file);
        var resultSet = compiler.typeInference(file);
        var converter = new ASTToTargetAST(compiler, resultSet, classLoader);
        var classes = compiler.sourceFiles.get(file).getClasses();

        var infimum = TestCodegen.generateClass(converter.convert(classes.get(0)), classLoader);
    }

    @Test
    public void gen() throws Exception {
        var classLoader = TestCodegen.createClassLoader();

        var file = Path.of(System.getProperty("user.dir"), "/resources/bytecode/javFiles/Gen.jav").toFile();
        var compiler = new JavaTXCompiler(file);
        var resultSet = compiler.typeInference(file);
        var converter = new ASTToTargetAST(compiler, resultSet, classLoader);
        var classes = compiler.sourceFiles.get(file).getClasses();

        var generics = TestCodegen.generateClass(converter.convert(classes.get(0)), TestCodegen.createClassLoader());
        var m = generics.getDeclaredMethod("m", Vector.class);
        var mReturnType = m.getGenericReturnType();
        assertEquals(mReturnType, m.getParameters()[0].getParameterizedType());
        assertEquals(mReturnType, new TypeToken<Vector<Integer>>() {
        }.getType());
    }

    @Test
    public void definedGenerics() throws Exception {
        var classLoader = TestCodegen.createClassLoader();

        var file = Path.of(System.getProperty("user.dir"), "/resources/bytecode/javFiles/Generics.jav").toFile();
        var compiler = new JavaTXCompiler(file);
        var resultSet = compiler.typeInference(file);
        var converter = new ASTToTargetAST(compiler, resultSet, classLoader);
        var classes = compiler.sourceFiles.get(file).getClasses();

        var generics = TestCodegen.generateClass(converter.convert(classes.get(0)), classLoader);
        var B = generics.getTypeParameters()[0];
        var mt1 = generics.getDeclaredMethod("mt1", Object.class);
        var constructor = generics.getDeclaredConstructor(Object.class);

        assertEquals(B, mt1.getGenericReturnType());
        assertEquals(B, mt1.getParameters()[0].getParameterizedType());
        assertEquals(B, constructor.getParameters()[0].getParameterizedType());
    }

    @Test
    public void definedGenerics2() throws Exception {
        var classLoader = TestCodegen.createClassLoader();

        var file = Path.of(System.getProperty("user.dir"), "/resources/bytecode/javFiles/Generics2.jav").toFile();
        var compiler = new JavaTXCompiler(file);
        var resultSet = compiler.typeInference(file);
        var converter = new ASTToTargetAST(compiler, resultSet, classLoader);
        var classes = compiler.sourceFiles.get(file).getClasses();

        var generics2 = TestCodegen.generateClass(converter.convert(classes.get(0)), classLoader);
        assertEquals(generics2.getTypeParameters()[0].getBounds()[0], String.class);
        var m = generics2.getDeclaredMethod("m1", Object.class);
        assertEquals(m.getTypeParameters()[0].getBounds()[0], Integer.class);
        var param = m.getTypeParameters()[0];
    }

    @Test
    @Disabled("Not implemented")
    public void definedGenerics3() throws Exception {
        var classLoader = TestCodegen.createClassLoader();

        var file = Path.of(System.getProperty("user.dir"), "/resources/bytecode/javFiles/Generics3.jav").toFile();
        var compiler = new JavaTXCompiler(file);
        var resultSet = compiler.typeInference(file);
        var converter = new ASTToTargetAST(compiler, resultSet, classLoader);
        var classes = compiler.sourceFiles.get(file).getClasses();

        var generics3 = TestCodegen.generateClass(converter.convert(classes.get(0)), classLoader);
    }

    @Test
    public void definedGenerics4() throws Exception {
        var classLoader = TestCodegen.createClassLoader();

        var file = Path.of(System.getProperty("user.dir"), "/resources/bytecode/javFiles/Generics4.jav").toFile();
        var compiler = new JavaTXCompiler(file);
        var resultSet = compiler.typeInference(file);
        var converter = new ASTToTargetAST(compiler, resultSet, classLoader);
        var classes = compiler.sourceFiles.get(file).getClasses();

        var generics4 = TestCodegen.generateClass(converter.convert(classes.get(0)), classLoader);

        // var instance = generics4.getDeclaredConstructor().newInstance();
        // var method = generics4.getDeclaredMethod("m2", Object.class);
        // method.invoke(instance, new Object());
    }
}
