package de.dhbwstuttgart.bytecode.utilities;

import de.dhbwstuttgart.bytecode.signature.Signature;
import de.dhbwstuttgart.syntaxtree.FormalParameter;
import de.dhbwstuttgart.syntaxtree.statement.ArgumentList;
import de.dhbwstuttgart.syntaxtree.statement.Expression;
import de.dhbwstuttgart.syntaxtree.statement.LambdaExpression;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureVisitor;
import org.objectweb.asm.signature.SignatureWriter;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;

public class ByteCodeForFunNGenerator {

    public static void generateBCForFunN(LambdaExpression lambdaExpression, String methDesc, String path) {
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);

        SignatureWriter methSig = new SignatureWriter();

        int numberOfParams = 0;
        SignatureVisitor paramVisitor = methSig.visitParameterType();
        Iterator<FormalParameter> itr = lambdaExpression.params.iterator();
        while (itr.hasNext()) {
            numberOfParams++;
            // getBounds
            paramVisitor.visitTypeVariable(CONSTANTS.T + numberOfParams);
            itr.next();
        }
        methSig.visitReturnType().visitTypeVariable(CONSTANTS.R);
        // ")"+lam.getReturn.getBounds
        Signature sig = new Signature(numberOfParams);
        String name = CONSTANTS.FUN + numberOfParams + CONSTANTS.$$;
        classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC+Opcodes.ACC_INTERFACE + Opcodes.ACC_ABSTRACT, name, sig.toString(),
                Type.getInternalName(Object.class), null);
        MethodVisitor mvApply = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT, "apply", methDesc,
                methSig.toString(), null);
        mvApply.visitEnd();
        writeClassFile(classWriter.toByteArray(), name, path);
    }

    public static void generateBCForFunN(ArgumentList argumentList, String methDesc, String path) {
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);

        SignatureWriter methSig = new SignatureWriter();

        int numberOfParams = 0;
        SignatureVisitor paramVisitor = methSig.visitParameterType();
        Iterator<Expression> itr1 = argumentList.getArguments().iterator();

        while(itr1.hasNext()) {
            numberOfParams++;
            // getBounds
            paramVisitor.visitTypeVariable(CONSTANTS.T + numberOfParams);
            itr1.next();
        }

        methSig.visitReturnType().visitTypeVariable(CONSTANTS.R);
        // ")"+lam.getReturn.getBounds
        Signature sig = new Signature(numberOfParams);
        String name = CONSTANTS.FUN + numberOfParams + CONSTANTS.$$;
        classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC+Opcodes.ACC_INTERFACE + Opcodes.ACC_ABSTRACT, name, sig.toString(),
                Type.getInternalName(Object.class), null);
        MethodVisitor mvApply = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT, "apply", methDesc,
                methSig.toString(), null);
        mvApply.visitEnd();
        writeClassFile(classWriter.toByteArray(), name, path);
    }


    public static void writeClassFile(byte[] bytecode, String name, String path) {
        FileOutputStream output;
        try {
            System.out.println("generating " + name + ".class file...");
            output = new FileOutputStream(
                    new File(path + name + CONSTANTS.EXTENSIONCLASS));
            output.write(bytecode);
            output.close();
            System.out.println(name + ".class file generated");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
