package org.bdware.sc.gen;

import org.bdware.sc.py.bean.PYClass;
import org.bdware.sc.py.bean.PYMethod;
import org.bdware.sc.py.bean.PYModule;
import org.bdware.sc.py.bean.PYPackage;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.io.IOException;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class PYGenerator implements Opcodes {
    static final String PKG_NAME = "com/yancloud/callpy/";
    private PYPackage pkgObj;

    public PYPackage getPkgObj() {
        return pkgObj;
    }

    public void setPkgObj(PYPackage pkgObj) {
        this.pkgObj = pkgObj;
    }

    public void genClassFiles(ZipOutputStream zout, PYPackage pyPkg) throws Exception {
        if (null == pyPkg) {
            if (null == this.pkgObj) {
                System.out.println("please use setPkgObj method to specify pkgObj");
                return;
            }
        } else {
            this.pkgObj = pyPkg;
        }
        Map<String, byte[]> clzs = genClasses();
        Set<String> dirs = new HashSet<>();
        for (String str : clzs.keySet()) {
            System.out.println("[PYGenerator] genFile:" + str);
            zout.putNextEntry(new ZipEntry(str + ".class"));
            zout.write(clzs.get(str));
            zout.closeEntry();
            System.out.println("add entry:" + str + ".class");
        }
    }

    private void putDirs(Set<String> dirs, String dir, ZipOutputStream zout) throws IOException {
        dir = dir.replaceAll("[^/]*$", "");
        if (!dirs.contains(dir) && dir.length() > 1) {
            putDirs(dirs, dir.substring(0, dir.length() - 1), zout);
            zout.putNextEntry(new ZipEntry(dir));
            zout.closeEntry();
            dirs.add(dir);
            System.out.println("add entry:" + dir);
        }
    }

    public Map<String, byte[]> genClasses() {
        Map<String, byte[]> clzs = new HashMap<>();
        String pkgName = pkgObj.getName();
        System.out.println("[genClasses]pkgName:  " + pkgName);
        for (PYModule module : pkgObj.getModules()) {
            String moduleName = module.getName();
            System.out.println("[genClasses]moduleName: " + moduleName);
            // 生成module中的每个类
            genClasses(clzs, module.getClzList(), pkgName + "_" + moduleName + "_");
            // 生成module中静态方法
            genFuncs(clzs, module.getFuncList(), pkgName + "_" + moduleName);
        }
        return clzs;
    }

    private void genFuncs(Map<String, byte[]> clzs, List<PYMethod> funcList, String classNameStr) {
        System.out.println("[genFuncs]className: " + classNameStr);
        if (funcList.size() > 0) {
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
            MethodVisitor mv;
            // 生成类
            cw.visit(
                    V1_7,
                    ACC_PUBLIC + ACC_SUPER,
                    PKG_NAME + classNameStr,
                    null,
                    "com/yancloud/py/bean/PYObject",
                    null);
            // 生成该module中的静态方法
            for (PYMethod pyFunction : funcList) {
                String functionName = pyFunction.getName();
                {
                    mv =
                            cw.visitMethod(
                                    ACC_PUBLIC + ACC_STATIC + ACC_VARARGS,
                                    functionName,
                                    "([Ljava/lang/Object;)Ljava/lang/String;",
                                    null,
                                    null);
                    mv.visitCode();
                    Label l0 = new Label();
                    mv.visitLabel(l0);
                    mv.visitTypeInsn(NEW, "com/yancloud/py/bean/PYMethodParams");
                    mv.visitInsn(DUP);
                    mv.visitMethodInsn(INVOKESPECIAL, "com/yancloud/py/bean/PYMethodParams", "<init>", "()V");
                    mv.visitVarInsn(ASTORE, 1);
                    Label l1 = new Label();
                    mv.visitLabel(l1);
                    mv.visitVarInsn(ALOAD, 1);
                    mv.visitVarInsn(ALOAD, 0);
                    mv.visitMethodInsn(
                            INVOKEVIRTUAL,
                            "com/yancloud/py/bean/PYMethodParams",
                            "setParams",
                            "(Ljava/lang/Object;)V");
                    Label l2 = new Label();
                    mv.visitLabel(l2);
                    mv.visitFieldInsn(
                            GETSTATIC, "com/yancloud/py/PYEntry", "instance", "Lcom/yancloud/py/PYEntry;");
                    mv.visitLdcInsn(classNameStr);
                    mv.visitLdcInsn(functionName);
                    mv.visitVarInsn(ALOAD, 1);
                    mv.visitMethodInsn(
                            INVOKEVIRTUAL,
                            "com/yancloud/py/PYEntry",
                            "invokeModuleFunc",
                            "(Ljava/lang/String;Ljava/lang/String;Lcom/yancloud/py/bean/PYMethodParams;)Ljava/lang/String;");
                    mv.visitInsn(ARETURN);
                    Label l3 = new Label();
                    mv.visitLabel(l3);
                    mv.visitLocalVariable("params", "[Ljava/lang/Object;", null, l0, l3, 0);
                    mv.visitLocalVariable(
                            "paramObj", "Lcom/yancloud/py/bean/PYMethodParams;", null, l1, l3, 1);
                    mv.visitMaxs(4, 2);
                    mv.visitEnd();
                }
            }
            cw.visitEnd();
            byte[] b = cw.toByteArray();
            clzs.put(PKG_NAME + classNameStr, b);
        }
    }

    private void genClasses(Map<String, byte[]> clzs, List<PYClass> clzList, String moduleSig) {
        for (PYClass pyClass : clzList) {
            String className = pyClass.getName();
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
            MethodVisitor mv;
            String classNameStr = moduleSig + className;
            System.out.println("[genClasses]className: " + classNameStr);

            // 生成类
            cw.visit(
                    V1_7,
                    ACC_PUBLIC + ACC_SUPER,
                    PKG_NAME + classNameStr,
                    null,
                    "com/yancloud/py/bean/PYObject",
                    null);

            // 生成该类的构造方法
            {
                mv =
                        cw.visitMethod(
                                ACC_PUBLIC + ACC_VARARGS, "<init>", "([Ljava/lang/Object;)V", null, null);
                mv.visitCode();
                Label l0 = new Label();
                mv.visitLabel(l0);
                mv.visitVarInsn(ALOAD, 0);
                mv.visitMethodInsn(INVOKESPECIAL, "com/yancloud/py/bean/PYObject", "<init>", "()V");
                Label l1 = new Label();
                mv.visitLabel(l1);
                mv.visitTypeInsn(NEW, "com/yancloud/py/bean/PYMethodParams");
                mv.visitInsn(DUP);
                mv.visitMethodInsn(INVOKESPECIAL, "com/yancloud/py/bean/PYMethodParams", "<init>", "()V");
                mv.visitVarInsn(ASTORE, 2);
                Label l2 = new Label();
                mv.visitLabel(l2);
                mv.visitVarInsn(ALOAD, 2);
                mv.visitVarInsn(ALOAD, 1);
                mv.visitMethodInsn(
                        INVOKEVIRTUAL,
                        "com/yancloud/py/bean/PYMethodParams",
                        "setParams",
                        "(Ljava/lang/Object;)V");
                Label l3 = new Label();
                mv.visitLabel(l3);
                mv.visitVarInsn(ALOAD, 0);
                mv.visitFieldInsn(
                        GETSTATIC, "com/yancloud/py/PYEntry", "instance", "Lcom/yancloud/py/PYEntry;");
                mv.visitLdcInsn(classNameStr);
                mv.visitVarInsn(ALOAD, 2);
                mv.visitMethodInsn(
                        INVOKEVIRTUAL,
                        "com/yancloud/py/PYEntry",
                        "creatObject",
                        "(Ljava/lang/String;Lcom/yancloud/py/bean/PYMethodParams;)Ljava/lang/String;");
                mv.visitMethodInsn(
                        INVOKEVIRTUAL, PKG_NAME + classNameStr, "setID", "(Ljava/lang/String;)V");
                Label l4 = new Label();
                mv.visitLabel(l4);
                mv.visitInsn(RETURN);
                Label l5 = new Label();
                mv.visitLabel(l5);
                mv.visitLocalVariable("this", "Lcom/yancloud/gen/P$M$C;", null, l0, l5, 0);
                mv.visitLocalVariable("params", "[Ljava/lang/Object;", null, l0, l5, 1);
                mv.visitLocalVariable("paramObj", "Lcom/yancloud/py/bean/PYMethodParams;", null, l2, l5, 2);
                mv.visitMaxs(4, 3);
                mv.visitEnd();
            }

            // 遍历methods
            for (PYMethod pyMethod : pyClass.getMethods()) {
                String methodName = pyMethod.getName();
                // 生成该类中的每个method
                if (pyMethod.isStatic()) {
                    {
                        mv =
                                cw.visitMethod(
                                        ACC_PUBLIC + ACC_STATIC + ACC_VARARGS,
                                        methodName,
                                        "([Ljava/lang/Object;)Ljava/lang/String;",
                                        null,
                                        null);
                        mv.visitCode();
                        Label l0 = new Label();
                        mv.visitLabel(l0);
                        mv.visitTypeInsn(NEW, "com/yancloud/py/bean/PYMethodParams");
                        mv.visitInsn(DUP);
                        mv.visitMethodInsn(
                                INVOKESPECIAL, "com/yancloud/py/bean/PYMethodParams", "<init>", "()V");
                        mv.visitVarInsn(ASTORE, 1);
                        Label l1 = new Label();
                        mv.visitLabel(l1);
                        mv.visitVarInsn(ALOAD, 1);
                        mv.visitVarInsn(ALOAD, 0);
                        mv.visitMethodInsn(
                                INVOKEVIRTUAL,
                                "com/yancloud/py/bean/PYMethodParams",
                                "setParams",
                                "(Ljava/lang/Object;)V");
                        Label l2 = new Label();
                        mv.visitLabel(l2);
                        mv.visitFieldInsn(
                                GETSTATIC, "com/yancloud/py/PYEntry", "instance", "Lcom/yancloud/py/PYEntry;");
                        mv.visitLdcInsn(classNameStr + "." + methodName);
                        mv.visitVarInsn(ALOAD, 1);
                        mv.visitMethodInsn(
                                INVOKEVIRTUAL,
                                "com/yancloud/py/PYEntry",
                                "invokeClzStaticMethod",
                                "(Ljava/lang/String;Lcom/yancloud/py/bean/PYMethodParams;)Ljava/lang/String;");
                        mv.visitInsn(ARETURN);
                        Label l3 = new Label();
                        mv.visitLabel(l3);
                        mv.visitLocalVariable("params", "[Ljava/lang/Object;", null, l0, l3, 0);
                        mv.visitLocalVariable(
                                "paramObj", "Lcom/yancloud/py/bean/PYMethodParams;", null, l1, l3, 1);
                        mv.visitMaxs(3, 2);
                        mv.visitEnd();
                    }
                } else {
                    {
                        mv =
                                cw.visitMethod(
                                        ACC_PUBLIC + ACC_VARARGS,
                                        methodName,
                                        "([Ljava/lang/Object;)Ljava/lang/String;",
                                        null,
                                        null);
                        mv.visitCode();
                        Label l0 = new Label();
                        mv.visitLabel(l0);
                        mv.visitTypeInsn(NEW, "com/yancloud/py/bean/PYMethodParams");
                        mv.visitInsn(DUP);
                        mv.visitMethodInsn(
                                INVOKESPECIAL, "com/yancloud/py/bean/PYMethodParams", "<init>", "()V");
                        mv.visitVarInsn(ASTORE, 2);
                        Label l1 = new Label();
                        mv.visitLabel(l1);
                        mv.visitVarInsn(ALOAD, 2);
                        mv.visitVarInsn(ALOAD, 1);
                        mv.visitMethodInsn(
                                INVOKEVIRTUAL,
                                "com/yancloud/py/bean/PYMethodParams",
                                "setParams",
                                "(Ljava/lang/Object;)V");
                        Label l2 = new Label();
                        mv.visitLabel(l2);
                        mv.visitFieldInsn(
                                GETSTATIC, "com/yancloud/py/PYEntry", "instance", "Lcom/yancloud/py/PYEntry;");
                        mv.visitVarInsn(ALOAD, 0);
                        mv.visitMethodInsn(
                                INVOKEVIRTUAL, "com/yancloud/gen/P$M$C", "getID", "()Ljava/lang/String;");
                        mv.visitLdcInsn(classNameStr + "." + methodName);
                        mv.visitVarInsn(ALOAD, 2);
                        mv.visitMethodInsn(
                                INVOKEVIRTUAL,
                                "com/yancloud/py/PYEntry",
                                "invokeObjectMethod",
                                "(Ljava/lang/String;Ljava/lang/String;Lcom/yancloud/py/bean/PYMethodParams;)Ljava/lang/String;");
                        mv.visitInsn(ARETURN);
                        Label l3 = new Label();
                        mv.visitLabel(l3);
                        mv.visitLocalVariable("this", "Lcom/yancloud/gen/P$M$C;", null, l0, l3, 0);
                        mv.visitLocalVariable("params", "[Ljava/lang/Object;", null, l0, l3, 1);
                        mv.visitLocalVariable(
                                "paramObj", "Lcom/yancloud/py/bean/PYMethodParams;", null, l1, l3, 2);
                        mv.visitMaxs(4, 3);
                        mv.visitEnd();
                    }
                }
            }

            // 遍历funcs
            for (PYMethod pyFunction : pyClass.getFuncs()) {
                String functionName = pyFunction.getName();
                // 生成该类中的每个function
                {
                    mv =
                            cw.visitMethod(
                                    ACC_PUBLIC + ACC_VARARGS,
                                    functionName,
                                    "([Ljava/lang/Object;)Ljava/lang/String;",
                                    null,
                                    null);
                    mv.visitCode();
                    Label l0 = new Label();
                    mv.visitLabel(l0);
                    mv.visitTypeInsn(NEW, "com/yancloud/py/bean/PYMethodParams");
                    mv.visitInsn(DUP);
                    mv.visitMethodInsn(INVOKESPECIAL, "com/yancloud/py/bean/PYMethodParams", "<init>", "()V");
                    mv.visitVarInsn(ASTORE, 2);
                    Label l1 = new Label();
                    mv.visitLabel(l1);
                    mv.visitVarInsn(ALOAD, 2);
                    mv.visitVarInsn(ALOAD, 1);
                    mv.visitMethodInsn(
                            INVOKEVIRTUAL,
                            "com/yancloud/py/bean/PYMethodParams",
                            "setParams",
                            "(Ljava/lang/Object;)V");
                    Label l2 = new Label();
                    mv.visitLabel(l2);
                    mv.visitFieldInsn(
                            GETSTATIC, "com/yancloud/py/PYEntry", "instance", "Lcom/yancloud/py/PYEntry;");
                    mv.visitLdcInsn(classNameStr + "." + functionName);
                    mv.visitVarInsn(ALOAD, 2);
                    mv.visitMethodInsn(
                            INVOKEVIRTUAL,
                            "com/yancloud/py/PYEntry",
                            "invokeClzClassMethod",
                            "(Ljava/lang/String;Lcom/yancloud/py/bean/PYMethodParams;)Ljava/lang/String;");
                    mv.visitInsn(ARETURN);
                    Label l3 = new Label();
                    mv.visitLabel(l3);
                    mv.visitLocalVariable("this", "Lcom/yancloud/gen/P$M$C;", null, l0, l3, 0);
                    mv.visitLocalVariable("params", "[Ljava/lang/Object;", null, l0, l3, 1);
                    mv.visitLocalVariable(
                            "paramObj", "Lcom/yancloud/py/bean/PYMethodParams;", null, l1, l3, 2);
                    mv.visitMaxs(3, 3);
                    mv.visitEnd();
                }
            }
            cw.visitEnd();
            byte[] b = cw.toByteArray();
            clzs.put(PKG_NAME + classNameStr, b);
        }
    }
}
