package org.kgusarov.integration.spring.netty.support.invoke.assembler;

import com.google.common.primitives.Primitives;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

import static org.kgusarov.integration.spring.netty.support.invoke.assembler.Descriptors.*;
import static org.kgusarov.integration.spring.netty.support.invoke.assembler.Descriptors.MH_DESCRIPTOR;
import static org.kgusarov.integration.spring.netty.support.invoke.assembler.LabelAssembler.createLabel;
import static org.objectweb.asm.Opcodes.*;

/**
 * Internal API: code generation support - method handle private static final field
 */
public final class MethodHandleFieldAssembler {
    private MethodHandleFieldAssembler() {
    }

    public static void assembleMethodHandleField(final ClassWriter cw, final Method targetMethod, final String invokerName) {
        final String desc = Type.getDescriptor(MethodHandle.class);
        cw.visitField(ACC_PRIVATE | ACC_FINAL + ACC_STATIC, "HANDLE", desc, null, null);
        cw.visitEnd();

        final MethodVisitor ctor = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
        final Label cs = createLabel();
        final Label ce = createLabel();

        ctor.visitCode();
        ctor.visitLabel(cs);

        final String clName = targetMethod.getDeclaringClass().getCanonicalName();
        final String targetMethodName = targetMethod.getName();
        ctor.visitLdcInsn(clName);
        ctor.visitLdcInsn(targetMethodName);

        final Parameter[] parameters = targetMethod.getParameters();
        ctor.visitLdcInsn(parameters.length);

        final Type classType = Type.getType(Class.class);
        final String descriptor = classType.getInternalName();
        ctor.visitTypeInsn(ANEWARRAY, descriptor);

        for (int i = 0; i < parameters.length; i++) {
            ctor.visitInsn(DUP);
            ctor.visitLdcInsn(i);

            final Class<?> paramClass = parameters[i].getType();
            if (paramClass.isPrimitive()) {
                final Class<?> wrapper = Primitives.wrap(paramClass);
                final String holderName = Type.getType(wrapper).getInternalName();

                ctor.visitFieldInsn(GETSTATIC, holderName, "TYPE", CL_DESCRIPTOR);
            } else {
                final Type parameterType = Type.getType(paramClass);
                ctor.visitLdcInsn(parameterType);
            }

            ctor.visitInsn(AASTORE);
        }

        ctor.visitMethodInsn(INVOKESTATIC, MHC_INTERNAL_NAME, "createUniversal",
                '(' + STR_DESCRIPTOR + STR_DESCRIPTOR + CLA_DESCRIPTOR + ')' + MH_DESCRIPTOR, false);
        ctor.visitFieldInsn(PUTSTATIC, invokerName, "HANDLE", MH_DESCRIPTOR);
        ctor.visitInsn(RETURN);

        ctor.visitLabel(ce);
        ctor.visitEnd();

        ctor.visitMaxs(0, 0);
    }
}