/**************************************************************************************
 * Copyright (c) Jonas Bonér, Alexandre Vasseur. All rights reserved.                 *
 * http://aspectwerkz.codehaus.org                                                    *
 * ---------------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the LGPL license      *
 * a copy of which has been included with this distribution in the license.txt file.  *
 **************************************************************************************/
package test.weavebench;

import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
import org.codehaus.aspectwerkz.transform.AspectWerkzPreProcessor;
import org.codehaus.aspectwerkz.hook.impl.WeavingClassLoader;
import org.codehaus.aspectwerkz.definition.SystemDefinition;
import org.codehaus.aspectwerkz.definition.DeploymentScope;
import org.codehaus.aspectwerkz.definition.SystemDefinitionContainer;
import org.codehaus.aspectwerkz.definition.DefinitionParserHelper;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Constants;
import org.objectweb.asm.Type;
import org.objectweb.asm.CodeVisitor;
import org.objectweb.asm.ClassWriter;

import java.net.URL;
import java.net.URLClassLoader;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.lang.reflect.Method;

/**
 * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
 */
public class GenerateClasses implements Constants {

    private final static String DUMP_DIR = "_dump2";

    private final static String CLASS_NAME_PREFIX = "test/weavebench/Generated_";

    public int m_classCount;

    public int m_count;

    public GenerateClasses(int classCount, int methodCount) {
        m_classCount = classCount;
        m_count = methodCount;
    }

    public void generate() throws Throwable {
        for (int i = 0; i < m_classCount; i++) {
            ClassWriter cv = AsmHelper.newClassWriter(true);

            String className = CLASS_NAME_PREFIX + i;
            cv.visit(
                    AsmHelper.JAVA_VERSION,
                    ACC_PUBLIC + ACC_SUPER + ACC_SYNTHETIC,
                    className,
                    Object.class.getName().replace('.', '/'),
                    new String[]{IGenerated.class.getName().replace('.', '/')},
                    null
            );

            CodeVisitor mv = cv.visitMethod(
                    ACC_PUBLIC,
                    "<init>",
                    "()V",
                    new String[0],
                    null
            );
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
            mv.visitVarInsn(ALOAD, 0);
            mv.visitInsn(RETURN);
            mv.visitMaxs(0, 0);

            for (int m = 0; m < m_count; m++) {
                mv = cv.visitMethod(
                        (m==0)?ACC_PUBLIC:
                        ACC_PRIVATE,//TODO change to private to have wrapper, public for no wrappers
                        "method_" + m,
                        "()I",
                        new String[0],
                        null
                );

                // call next method
                if (m != m_count-1) {
                    mv.visitVarInsn(ALOAD, 0);
                    mv.visitMethodInsn(INVOKEVIRTUAL, className, "method_" + (m+1), "()I");
                }
                AsmHelper.loadIntegerConstant(mv, m);
                mv.visitInsn(IRETURN);
                mv.visitMaxs(0, 0);
            }

            cv.visitEnd();

            AsmHelper.dumpClass(DUMP_DIR, className, cv);
        }
    }

    public static void main(String args[]) throws Throwable {
        int CLASS_COUNT = 100;
        int METHOD_COUNT = 100;
        int JP_COUNT = (METHOD_COUNT*2 -1/*last method has no call jp*/ + 1/*init exec*/)*CLASS_COUNT;
        GenerateClasses me = new GenerateClasses(CLASS_COUNT, METHOD_COUNT);

        System.out.println("********* Bench for");
        System.out.println(" classes: " + CLASS_COUNT);
        System.out.println(" methods: " + METHOD_COUNT);
        System.out.println(" jps: " + JP_COUNT);
        System.out.println("*************************************");

        me.generate();

        ClassLoader custom_1 = new URLClassLoader(
                new URL[]{(new File(DUMP_DIR)).toURL()},
                GenerateClasses.class.getClassLoader()
        );
        bench("No weaver hooked in", custom_1, CLASS_COUNT, 0);

        ClassLoader custom_2 = new WeavingClassLoader(
                new URL[]{(new File(DUMP_DIR)).toURL()},
                GenerateClasses.class.getClassLoader()
        );
        bench("Weaver hooked in and match none", custom_2, CLASS_COUNT, 0);

        ClassLoader custom_3 = new WeavingClassLoader(
                new URL[]{(new File(DUMP_DIR)).toURL()},
                GenerateClasses.class.getClassLoader()
        );
        SystemDefinition sd = SystemDefinitionContainer.getVirtualDefinitionAt(custom_3);
        sd.addDeploymentScope(DeploymentScope.MATCH_ALL);
        DefinitionParserHelper.attachDeploymentScopeDefsToVirtualAdvice(sd);
        Set defs = new HashSet();
        defs.add(sd);
        SystemDefinitionContainer.deployDefinitions(custom_3, defs);
        bench("Weaver hooked in and match all", custom_3, CLASS_COUNT, JP_COUNT);

    }

    public static void bench(String label, ClassLoader loader, int classCount, int jpCount) throws Throwable {
        System.out.println("*************************************");
        System.out.print(label);
        System.out.println("  ");
        long t = System.currentTimeMillis();
        Class[] classes = new Class[classCount];
        for (int i = 0; i < classCount; i++) {
            classes[i] = Class.forName((CLASS_NAME_PREFIX+i).replace('/','.'), false, loader);
        }

        System.out.println("  Total load time = " + (System.currentTimeMillis() - t));

        long t2 = System.currentTimeMillis();
        for (int i = 0; i < classCount; i++) {
            Class gen = classes[i];
            Method m0 = gen.getMethod("method_0", new Class[]{});
            Object res = m0.invoke(gen.newInstance(), new Object[]{});
            System.out.print(i);
        }
        System.out.println("");
        long execTimeNotWarmedUp = System.currentTimeMillis() - t2;
        System.out.println("  Exec time = " + execTimeNotWarmedUp);
        System.out.println("  Total time = " + (System.currentTimeMillis() - t));
        if (jpCount > 0)
            System.out.println("  Exec / jp ratio (ns) = " + execTimeNotWarmedUp * 1000 / jpCount);

        t2 = System.currentTimeMillis();
        for (int i = 0; i < classCount; i++) {
            Class gen = classes[i];
            Method m0 = gen.getMethod("method_0", new Class[]{});
            Object res = m0.invoke(gen.newInstance(), new Object[]{});
        }
        System.out.println("  Exec time warmed up = " + (System.currentTimeMillis() - t2));
    }


}
