package com.gradle.ares

import com.gradle.MyLogger
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.Label
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.commons.GeneratorAdapter

/**
 * Created by Glen on 2017/2/8.
 */

public class MethodLifeCycleVisitor extends MethodBaseVisitor {

    public final static String[] SuperClass = [
            "android/app/Activity",
            "android/support/v4/app/FragmentActivity",
            "android/support/v7/app/AppCompatActivity",
            "android/support/v4/app/Fragment",
            "android/support/v4/app/ListFragment",
            "android/support/v4/app/DialogFragment",
            "android/app/Fragment",
            "android/app/ListFragment",
            "android/app/DialogFragment"
    ] as String[]

    public final static MethodModel[] Modify = [
            new MethodModel("onCreate", "(Landroid/os/Bundle;)V", 1),
            new MethodModel("onResume", "()V", 0),
            new MethodModel("onPause", "()V", 0),
            new MethodModel("onDestroy", "()V", 0),
            new MethodModel("onSaveInstanceState", "(Landroid/os/Bundle;)V", 1),
            new MethodModel("onRestoreInstanceState", "(Landroid/os/Bundle;)V", 1),
            new MethodModel("setUserVisibleHint", "(Z)V", 1),
            new MethodModel("onHiddenChanged", "(Z)V", 1)
    ] as MethodModel[]

    public
    static MethodModel getAgent(int access, String className, String superName, String name, String desc) {
        for (MethodModel item : Modify) {
            if (item.name == name && item.desc == desc) {
                item.access = access
                item.className = className
                item.superName = superName
                return item
            }
        }
        return null
    }

    public
    static void addMethods(Set<MethodModel> agentSet, String className, String superName, ClassVisitor cv) {
        addLifeCycleFieldMethod(className, cv)
        for (MethodModel agent : Modify) {
            if (!agentSet.contains(agent)) {
                //method
                MyLogger.lifecycle("lifecycle:add:" + agent.name + "_" + agent.desc)

                MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, agent.name, agent.desc, null, null)
                GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ASM5, mv, Opcodes.ACC_PUBLIC, agent.name, agent.desc)
                adapter.visitCode()
                adapter.loadThis()
                adapter.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, AresValue.GetLifeMethod, "()L" + AresValue.LifeClass + ";", false)
                for (int i = 0; i < agent.count; i++) adapter.loadArg(i)
                adapter.visitMethodInsn(Opcodes.INVOKEVIRTUAL, AresValue.LifeClass, agent.name, agent.desc, false)

                adapter.loadThis()
                for (int i = 0; i < agent.count; i++) adapter.loadArg(i)
                adapter.visitMethodInsn(Opcodes.INVOKESPECIAL, superName, agent.name, agent.desc, false)
                adapter.visitInsn(Opcodes.RETURN)
                adapter.visitMaxs(0, 0)
                adapter.visitEnd()
            }
        }
    }

    /**
     * 注入代理字段及初始化方法
     * @param cv
     */
    private static void addLifeCycleFieldMethod(String className, ClassVisitor cv) {
        //desc
        String fieldName = "\$lifeCycleAresDelegate"
        String fieldDesc = "L" + AresValue.LifeClass + ";"
        //field
        FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC, fieldName, fieldDesc, null, null)
        fv.visitEnd()
        //method
        MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, AresValue.GetLifeMethod, "()" + fieldDesc, null, null)
        mv.visitCode()
        mv.visitVarInsn(Opcodes.ALOAD, 0)
        mv.visitFieldInsn(Opcodes.GETFIELD, className, fieldName, fieldDesc)
        Label label = new Label()
        mv.visitJumpInsn(Opcodes.IFNONNULL, label)
        mv.visitVarInsn(Opcodes.ALOAD, 0)
        mv.visitVarInsn(Opcodes.ALOAD, 0)
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, AresValue.LifeClass, "create", "(Ljava/lang/Object;)" + fieldDesc, false)
        mv.visitFieldInsn(Opcodes.PUTFIELD, className, fieldName, fieldDesc)
        mv.visitLabel(label)
        mv.visitVarInsn(Opcodes.ALOAD, 0)
        mv.visitFieldInsn(Opcodes.GETFIELD, className, fieldName, fieldDesc)
        mv.visitInsn(Opcodes.ARETURN)
        mv.visitMaxs(0, 0)
        mv.visitEnd()
    }

    MethodLifeCycleVisitor(MethodVisitor mv, MethodModel agent) {
        super(mv, agent)
        this.agent = agent
    }

    @Override
    void visitCode() {
        super.visitCode()
        visitVarInsn(Opcodes.ALOAD, 0)
        visitMethodInsn(Opcodes.INVOKEVIRTUAL, this.agent.className, AresValue.GetLifeMethod, "()L" + AresValue.LifeClass + ";", false)
        for (int i = 0; i < this.agent.count; i++) loadArg(i)
        visitMethodInsn(Opcodes.INVOKEVIRTUAL, AresValue.LifeClass, this.agent.name, this.agent.desc, false)
    }
}
