package net.bytebuddy.build.gradle;

import net.bytebuddy.utility.OpenedClassReader;
import net.bytebuddy.utility.nullability.MaybeNull;
import org.objectweb.asm.*;

import java.util.HashMap;
import java.util.Map;

public class UnwrappingClassVisitor extends ClassVisitor {

    private final net.bytebuddy.jar.asm.ClassVisitor classVisitor;

    protected UnwrappingClassVisitor(net.bytebuddy.jar.asm.ClassVisitor classVisitor) {
        super(OpenedClassReader.ASM_API);
        this.classVisitor = classVisitor;
    }

    @MaybeNull
    public static ClassVisitor of(@MaybeNull net.bytebuddy.jar.asm.ClassVisitor classVisitor) {
        return classVisitor == null ? null : new UnwrappingClassVisitor(classVisitor);
    }

    @Override
    public void visit(int version, int access, String name, @MaybeNull String signature, @MaybeNull String superName, @MaybeNull String[] interfaces) {
        classVisitor.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public void visitSource(@MaybeNull String source, @MaybeNull String debug) {
        classVisitor.visitSource(source, debug);
    }

    @Override
    public ModuleVisitor visitModule(String name, int access, @MaybeNull String version) {
        return WrappingModuleVisitor.of(classVisitor.visitModule(name, access, version));
    }

    @Override
    public void visitNestHost(String nestHost) {
        classVisitor.visitNestHost(nestHost);
    }

    @Override
    public void visitOuterClass(String owner, @MaybeNull String name, @MaybeNull String descriptor) {
        classVisitor.visitOuterClass(owner, name, descriptor);
    }

    @Override
    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
        return WrappingAnnotationVisitor.of(classVisitor.visitAnnotation(descriptor, visible));
    }

    @Override
    public AnnotationVisitor visitTypeAnnotation(int typeRef, @MaybeNull TypePath typePath, String descriptor, boolean visible) {
        return WrappingAnnotationVisitor.of(classVisitor.visitTypeAnnotation(typeRef, typePath == null ? null : net.bytebuddy.jar.asm.TypePath.fromString(typePath.toString()), descriptor, visible));
    }

    @Override
    public void visitAttribute(Attribute attribute) {
        /* do nothing */
    }

    @Override
    public void visitNestMember(String nestMember) {
        classVisitor.visitNestMember(nestMember);
    }

    @Override
    public void visitPermittedSubclass(String permittedSubclass) {
        classVisitor.visitPermittedSubclass(permittedSubclass);
    }

    @Override
    public void visitInnerClass(String name, @MaybeNull String outerName, @MaybeNull String innerName, int access) {
        classVisitor.visitInnerClass(name, outerName, innerName, access);
    }

    @Override
    public RecordComponentVisitor visitRecordComponent(String name, String descriptor, @MaybeNull String signature) {
        return WrappingRecordComponentVisitor.of(classVisitor.visitRecordComponent(name, descriptor, signature));
    }

    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, @MaybeNull String signature, Object value) {
        return WrappingFieldVisitor.of(classVisitor.visitField(access, name, descriptor, signature, value));
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, @MaybeNull String signature, @MaybeNull String[] exceptions) {
        return WrappingMethodVisitor.of(classVisitor.visitMethod(access, name, descriptor, signature, exceptions));
    }

    @Override
    public void visitEnd() {
        classVisitor.visitEnd();
    }

    public static class WrappingModuleVisitor extends ModuleVisitor {

        private final net.bytebuddy.jar.asm.ModuleVisitor moduleVisitor;

        protected WrappingModuleVisitor(net.bytebuddy.jar.asm.ModuleVisitor moduleVisitor) {
            super(OpenedClassReader.ASM_API);
            this.moduleVisitor = moduleVisitor;
        }

        @MaybeNull
        public static ModuleVisitor of(@MaybeNull net.bytebuddy.jar.asm.ModuleVisitor moduleVisitor) {
            return moduleVisitor == null ? null : new WrappingModuleVisitor(moduleVisitor);
        }

        @Override
        public void visitMainClass(String mainClass) {
            moduleVisitor.visitMainClass(mainClass);
        }

        @Override
        public void visitPackage(String packaze) {
            super.visitPackage(packaze);
        }

        @Override
        public void visitRequire(String module, int access, @MaybeNull String version) {
            moduleVisitor.visitRequire(module, access, version);
        }

        @Override
        public void visitExport(String packaze, int access, @MaybeNull String... modules) {
            moduleVisitor.visitExport(packaze, access, modules);
        }

        @Override
        public void visitOpen(String packaze, int access, @MaybeNull String... modules) {
            moduleVisitor.visitOpen(packaze, access, modules);
        }

        @Override
        public void visitUse(String service) {
            moduleVisitor.visitUse(service);
        }

        @Override
        public void visitProvide(String service, String... providers) {
            moduleVisitor.visitProvide(service, providers);
        }

        @Override
        public void visitEnd() {
            moduleVisitor.visitEnd();
        }
    }

    public static class WrappingAnnotationVisitor extends AnnotationVisitor {

        private final net.bytebuddy.jar.asm.AnnotationVisitor annotationVisitor;

        protected WrappingAnnotationVisitor(net.bytebuddy.jar.asm.AnnotationVisitor annotationVisitor) {
            super(OpenedClassReader.ASM_API);
            this.annotationVisitor = annotationVisitor;
        }

        @MaybeNull
        public static AnnotationVisitor of(@MaybeNull net.bytebuddy.jar.asm.AnnotationVisitor annotationVisitor) {
            return annotationVisitor == null ? null : new WrappingAnnotationVisitor(annotationVisitor);
        }

        @Override
        public void visit(@MaybeNull String name, Object value) {
            annotationVisitor.visit(name, value);
        }

        @Override
        public void visitEnum(@MaybeNull String name, String descriptor, String value) {
            super.visitEnum(name, descriptor, value);
        }

        @Override
        public AnnotationVisitor visitAnnotation(@MaybeNull String name, String descriptor) {
            return WrappingAnnotationVisitor.of(annotationVisitor.visitAnnotation(name, descriptor));
        }

        @Override
        public AnnotationVisitor visitArray(@MaybeNull String name) {
            return WrappingAnnotationVisitor.of(annotationVisitor.visitArray(name));
        }

        @Override
        public void visitEnd() {
            annotationVisitor.visitEnd();
        }
    }

    public static class WrappingRecordComponentVisitor extends RecordComponentVisitor {

        private final net.bytebuddy.jar.asm.RecordComponentVisitor recordComponentVisitor;

        protected WrappingRecordComponentVisitor(net.bytebuddy.jar.asm.RecordComponentVisitor recordComponentVisitor) {
            super(OpenedClassReader.ASM_API);
            this.recordComponentVisitor = recordComponentVisitor;
        }

        @MaybeNull
        public static RecordComponentVisitor of(@MaybeNull net.bytebuddy.jar.asm.RecordComponentVisitor recordComponentVisitor) {
            return recordComponentVisitor == null ? null : new WrappingRecordComponentVisitor(recordComponentVisitor);
        }

        @Override
        public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
            return WrappingAnnotationVisitor.of(recordComponentVisitor.visitAnnotation(descriptor, visible));
        }

        @Override
        public AnnotationVisitor visitTypeAnnotation(int typeRef, @MaybeNull TypePath typePath, String descriptor, boolean visible) {
            return WrappingAnnotationVisitor.of(recordComponentVisitor.visitTypeAnnotation(typeRef, typePath == null ? null : net.bytebuddy.jar.asm.TypePath.fromString(typePath.toString()), descriptor, visible));
        }

        @Override
        public void visitAttribute(Attribute attribute) {
            /* do nothing */
        }

        @Override
        public void visitEnd() {
            recordComponentVisitor.visitEnd();
        }
    }

    public static class WrappingFieldVisitor extends FieldVisitor {

        private final net.bytebuddy.jar.asm.FieldVisitor fieldVisitor;

        protected WrappingFieldVisitor(net.bytebuddy.jar.asm.FieldVisitor fieldVisitor) {
            super(OpenedClassReader.ASM_API);
            this.fieldVisitor = fieldVisitor;
        }

        @MaybeNull
        public static FieldVisitor of(@MaybeNull net.bytebuddy.jar.asm.FieldVisitor fieldVisitor) {
            return fieldVisitor == null ? null : new WrappingFieldVisitor(fieldVisitor);
        }

        @Override
        public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
            return WrappingAnnotationVisitor.of(fieldVisitor.visitAnnotation(descriptor, visible));
        }

        @Override
        public AnnotationVisitor visitTypeAnnotation(int typeRef, @MaybeNull TypePath typePath, String descriptor, boolean visible) {
            return WrappingAnnotationVisitor.of(fieldVisitor.visitTypeAnnotation(typeRef, typePath == null ? null : net.bytebuddy.jar.asm.TypePath.fromString(typePath.toString()), descriptor, visible));
        }

        @Override
        public void visitAttribute(Attribute attribute) {
            /* do nothing */
        }

        @Override
        public void visitEnd() {
            fieldVisitor.visitEnd();
        }
    }

    public static class WrappingMethodVisitor extends MethodVisitor {

        private final net.bytebuddy.jar.asm.MethodVisitor methodVisitor;

        private final Map<Label, net.bytebuddy.jar.asm.Label> labels = new HashMap<>();

        protected WrappingMethodVisitor(net.bytebuddy.jar.asm.MethodVisitor methodVisitor) {
            super(OpenedClassReader.ASM_API);
            this.methodVisitor = methodVisitor;
        }

        @MaybeNull
        public static MethodVisitor of(@MaybeNull net.bytebuddy.jar.asm.MethodVisitor methodVisitor) {
            return methodVisitor == null ? null : new WrappingMethodVisitor(methodVisitor);
        }

        @Override
        public void visitParameter(@MaybeNull String name, int access) {
            methodVisitor.visitParameter(name, access);
        }

        @Override
        public AnnotationVisitor visitAnnotationDefault() {
            return WrappingAnnotationVisitor.of(methodVisitor.visitAnnotationDefault());
        }

        @Override
        public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
            return WrappingAnnotationVisitor.of(methodVisitor.visitAnnotation(descriptor, visible));
        }

        @Override
        public AnnotationVisitor visitTypeAnnotation(int typeRef, @MaybeNull TypePath typePath, String descriptor, boolean visible) {
            return WrappingAnnotationVisitor.of(methodVisitor.visitTypeAnnotation(typeRef, typePath == null ? null : net.bytebuddy.jar.asm.TypePath.fromString(typePath.toString()), descriptor, visible));
        }

        @Override
        public void visitAnnotableParameterCount(int parameterCount, boolean visible) {
            methodVisitor.visitAnnotableParameterCount(parameterCount, visible);
        }

        @Override
        public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) {
            return WrappingAnnotationVisitor.of(methodVisitor.visitParameterAnnotation(parameter, descriptor, visible));
        }

        @Override
        public void visitAttribute(Attribute attribute) {
            /* do nothing */
        }

        @Override
        public void visitCode() {
            methodVisitor.visitCode();
        }

        @Override
        public void visitFrame(int type, int numLocal, @MaybeNull Object[] local, int numStack, @MaybeNull Object[] stack) {
            methodVisitor.visitFrame(type, numLocal, frames(local), numStack, frames(stack));
        }

        @Override
        public void visitInsn(int opcode) {
            methodVisitor.visitInsn(opcode);
        }

        @Override
        public void visitIntInsn(int opcode, int operand) {
            methodVisitor.visitIntInsn(opcode, operand);
        }

        @Override
        public void visitVarInsn(int opcode, int varIndex) {
            methodVisitor.visitVarInsn(opcode, varIndex);
        }

        @Override
        public void visitTypeInsn(int opcode, String type) {
            methodVisitor.visitTypeInsn(opcode, type);
        }

        @Override
        public void visitFieldInsn(int opcode, String owner, String name, @MaybeNull String descriptor) {
            methodVisitor.visitFieldInsn(opcode, owner, name, descriptor);
        }

        @Override
        @SuppressWarnings("deprecation")
        public void visitMethodInsn(int opcode, String owner, String name, String descriptor) {
            methodVisitor.visitMethodInsn(opcode, owner, name, descriptor);
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
            methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
        }

        @Override
        public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
            methodVisitor.visitInvokeDynamicInsn(name, descriptor, handle(bootstrapMethodHandle), ldc(bootstrapMethodArguments));
        }

        @Override
        public void visitJumpInsn(int opcode, Label label) {
            methodVisitor.visitJumpInsn(opcode, label(label));
        }

        @Override
        public void visitLabel(Label label) {
            methodVisitor.visitLabel(label(label));
        }

        @Override
        public void visitLdcInsn(Object value) {
            methodVisitor.visitLdcInsn(ldc(value));
        }

        @Override
        public void visitIincInsn(int varIndex, int increment) {
            methodVisitor.visitIincInsn(varIndex, increment);
        }

        @Override
        public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
            methodVisitor.visitTableSwitchInsn(min, max, label(dflt), label(labels));
        }

        @Override
        public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
            methodVisitor.visitLookupSwitchInsn(label(dflt), keys, label(labels));
        }

        @Override
        public void visitMultiANewArrayInsn(String descriptor, int numDimensions) {
            methodVisitor.visitMultiANewArrayInsn(descriptor, numDimensions);
        }

        @Override
        public AnnotationVisitor visitInsnAnnotation(int typeRef, @MaybeNull TypePath typePath, String descriptor, boolean visible) {
            return WrappingAnnotationVisitor.of(methodVisitor.visitInsnAnnotation(typeRef, typePath == null ? null : net.bytebuddy.jar.asm.TypePath.fromString(typePath.toString()), descriptor, visible));
        }

        @Override
        public void visitTryCatchBlock(Label start, Label end, Label handler, @MaybeNull String type) {
            methodVisitor.visitTryCatchBlock(label(start), label(end), label(handler), type);
        }

        @Override
        public AnnotationVisitor visitTryCatchAnnotation(int typeRef, @MaybeNull TypePath typePath, String descriptor, boolean visible) {
            return WrappingAnnotationVisitor.of(methodVisitor.visitTryCatchAnnotation(typeRef, typePath == null ? null : net.bytebuddy.jar.asm.TypePath.fromString(typePath.toString()), descriptor, visible));
        }

        @Override
        public void visitLocalVariable(@MaybeNull String name, @MaybeNull String descriptor, @MaybeNull String signature, Label start, Label end, int index) {
            methodVisitor.visitLocalVariable(name, descriptor, signature, label(start), label(end), index);
        }

        @Override
        public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, @MaybeNull TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) {
            return WrappingAnnotationVisitor.of(methodVisitor.visitLocalVariableAnnotation(typeRef, typePath == null ? null : net.bytebuddy.jar.asm.TypePath.fromString(typePath.toString()), label(start), label(end), index, descriptor, visible));
        }

        @Override
        public void visitLineNumber(int line, Label start) {
            methodVisitor.visitLineNumber(line, label(start));
        }

        @Override
        public void visitMaxs(int maxStack, int maxLocals) {
            methodVisitor.visitMaxs(maxStack, maxLocals);
        }

        @Override
        public void visitEnd() {
            methodVisitor.visitEnd();
        }

        private net.bytebuddy.jar.asm.Label[] label(Label[] source) {
            net.bytebuddy.jar.asm.Label[] target = new net.bytebuddy.jar.asm.Label[source.length];
            for (int index = 0; index < source.length; index++) {
                target[index] = label(source[index]);
            }
            return target;
        }

        private net.bytebuddy.jar.asm.Label label(Label source) {
            net.bytebuddy.jar.asm.Label target = labels.get(source);
            if (target == null) {
                target = new net.bytebuddy.jar.asm.Label();
                labels.put(source, target);
            }
            return target;
        }

        @MaybeNull
        private Object[] frames(@MaybeNull Object[] source) {
            if (source == null) {
                return null;
            }
            Object[] target = new Object[source.length];
            for (int index = 0; index < source.length; index++) {
                target[index] = source[index] instanceof Label
                        ? label((Label) source[index])
                        : source[index];
            }
            return target;
        }

        private static net.bytebuddy.jar.asm.Handle handle(Handle handle) {
            return new net.bytebuddy.jar.asm.Handle(handle.getTag(),
                    handle.getOwner(),
                    handle.getName(),
                    handle.getDesc(),
                    handle.isInterface());
        }

        private static net.bytebuddy.jar.asm.ConstantDynamic constantDynamic(ConstantDynamic constantDynamic) {
            Object[] argument = new Object[constantDynamic.getBootstrapMethodArgumentCount()];
            for (int index = 0; index < argument.length; index++) {
                argument[index] = ldc(constantDynamic.getBootstrapMethodArgument(index));
            }
            return new net.bytebuddy.jar.asm.ConstantDynamic(constantDynamic.getName(),
                    constantDynamic.getDescriptor(),
                    handle(constantDynamic.getBootstrapMethod()),
                    argument);
        }

        private static Object ldc(Object source) {
            if (source instanceof Handle) {
                return handle((Handle) source);
            } else if (source instanceof Type) {
                return net.bytebuddy.jar.asm.Type.getType(((Type) source).getDescriptor());
            } else if (source instanceof ConstantDynamic) {
                return constantDynamic((ConstantDynamic) source);
            } else {
                return source;
            }
        }

        private static Object[] ldc(Object[] source) {
            Object[] target = new Object[source.length];
            for (int index = 0; index < target.length; index++) {
                target[index] = ldc(source[index]);
            }
            return target;
        }
    }
}