package org.wildfly.swarm.cdi.jaxrsapi.runtime;

import java.util.List;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

class ClientServiceFactory implements Opcodes {

    private ClientServiceFactory() {
    }


    static byte[] createImpl(String implName, ClassInfo classInfo) {
        ClassWriter cw = new ClassWriter(0);
        MethodVisitor mv;
        AnnotationVisitor av0;

        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER,
                 implName.replace('.', '/'),
                 null,
                 "java/lang/Object",
                 new String[]{classInfo.name().toString().replace('.', '/')}
        );

        int lastDot = implName.lastIndexOf('.');
        String simpleName = implName.substring(lastDot + 1);

        cw.visitSource(simpleName + ".java", null);
        {
            av0 = cw.visitAnnotation("Ljavax/enterprise/context/ApplicationScoped;", true);
            av0.visitEnd();
        }
        cw.visitInnerClass("javax/ws/rs/client/Invocation$Builder", "javax/ws/rs/client/Invocation", "Builder", ACC_PUBLIC + ACC_STATIC + ACC_ABSTRACT + ACC_INTERFACE);

        {
            mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(14, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLineNumber(15, l1);
            mv.visitInsn(RETURN);
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLocalVariable("this",
                                  buildTypeDef(implName),
                                  null,
                                  l0,
                                  l2,
                                  0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }

        List<AnnotationInstance> annotations = classInfo.annotations().get(DotName.createSimple("org.wildfly.swarm.client.jaxrs.Service"));
        String baseUrl = (String) annotations.get(0).value("baseUrl").value();
        int lineNum = 18;

        classInfo.asClass().methods()
                .stream()
                .forEachOrdered(method -> {
                    createMethod(cw, implName, classInfo.name().toString(), method, lineNum, baseUrl);
                });
        cw.visitEnd();

        return cw.toByteArray();
    }

    static void createMethod(ClassWriter cw, String implName, String clientInterfaceName, MethodInfo method, int lineNum, String baseUrl) {
        MethodVisitor mv;

        {
            mv = cw.visitMethod(ACC_PUBLIC, method.name(), buildMethodDef(method), null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(lineNum++, l0);
            mv.visitLdcInsn(Type.getType(buildTypeDef(clientInterfaceName)));
            mv.visitTypeInsn(NEW, "org/jboss/resteasy/client/jaxrs/ResteasyClientBuilder");
            mv.visitInsn(DUP);
            mv.visitMethodInsn(INVOKESPECIAL, "org/jboss/resteasy/client/jaxrs/ResteasyClientBuilder", "<init>", "()V", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "org/jboss/resteasy/client/jaxrs/ResteasyClientBuilder", "build", "()Lorg/jboss/resteasy/client/jaxrs/ResteasyClient;", false);
            mv.visitLdcInsn(baseUrl);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLineNumber(lineNum++, l1);
            mv.visitMethodInsn(INVOKEVIRTUAL, "org/jboss/resteasy/client/jaxrs/ResteasyClient", "target", "(Ljava/lang/String;)Lorg/jboss/resteasy/client/jaxrs/ResteasyWebTarget;", false);
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLineNumber(lineNum - 2, l2);
            mv.visitMethodInsn(INVOKESTATIC, "org/wildfly/swarm/cdi/jaxrsapi/deployment/ProxyBuilder", "builder", "(Ljava/lang/Class;Ljavax/ws/rs/client/WebTarget;)Lorg/wildfly/swarm/cdi/jaxrsapi/deployment/ProxyBuilder;", false);
            Label l3 = new Label();
            mv.visitLabel(l3);
            mv.visitLineNumber(lineNum++, l3);
            mv.visitMethodInsn(INVOKEVIRTUAL, "org/wildfly/swarm/cdi/jaxrsapi/deployment/ProxyBuilder", "build", "()Ljava/lang/Object;", false);
            mv.visitTypeInsn(CHECKCAST, clientInterfaceName.replace('.', '/'));
            for (int i = 1; i <= method.parameters().size(); i++) {
                mv.visitVarInsn(ALOAD, i);
            }
            Label l4 = new Label();
            mv.visitLabel(l4);
            mv.visitLineNumber(lineNum++, l4);
            mv.visitMethodInsn(INVOKEINTERFACE, clientInterfaceName.replace('.', '/'), method.name(), buildMethodDef(method), true);
            Label l5 = new Label();
            mv.visitLabel(l5);
            if (method.returnType().kind().equals(org.jboss.jandex.Type.Kind.VOID)) {
                mv.visitLineNumber(lineNum++, l5);
                mv.visitInsn(RETURN);
            } else {
                mv.visitLineNumber(lineNum - 4, l5);
                mv.visitInsn(ARETURN);
            }
            Label l6 = new Label();
            mv.visitLabel(l6);
            int methodParams = 0;
            mv.visitLocalVariable("this", buildTypeDef(implName), null, l0, l6, methodParams++);
            for (AnnotationInstance anno : method.annotations()) {
                if (anno.name().toString().contains("QueryParam") || anno.name().toString().contains("PathParam")) {
                    short position = anno.target().asMethodParameter().position();
                    org.jboss.jandex.Type parameterType = anno.target().asMethodParameter().method().parameters().get(position);
                    mv.visitLocalVariable(String.valueOf(anno.value().value()), buildTypeDef(parameterType.name().toString()), null, l0, l6, methodParams++);
                }
            }
            mv.visitMaxs(3, methodParams);
            lineNum += 4;
            mv.visitEnd();
        }
    }

    static String buildTypeDef(String name) {
        return "L" + name.replace('.', '/') + ";";
    }

    static String buildMethodDef(MethodInfo method) {
        StringBuilder builder = new StringBuilder();

        // Method Parameters
        builder.append("(");
        for (org.jboss.jandex.Type type : method.parameters()) {
            builder.append(buildTypeDef(type.name().toString()));
        }
        builder.append(")");

        // Method Return Type
        if (method.returnType().kind().equals(org.jboss.jandex.Type.Kind.VOID)) {
            builder.append("V");
        } else {
            builder.append(buildTypeDef(method.returnType().name().toString()));
        }

        return builder.toString();
    }
}
