/**************************************************************************************
 * 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 org.codehaus.aspectwerkz.cflow;

import org.objectweb.asm.Constants;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.CodeVisitor;
import org.objectweb.asm.Label;
import org.codehaus.aspectwerkz.transform.TransformationConstants;
import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
import org.codehaus.aspectwerkz.transform.inlining.compiler.AbstractJoinPointCompiler;

/**
 * Compiler for the JIT cflow Aspect
 *
 * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
 */
public class CflowCompiler implements Constants, TransformationConstants {

    public final static String JIT_CFLOW_CLASS = "org/codehaus/aspectwerkz/cflow/Cflow_";
    private final static String ABSTRACT_CFLOW_CLASS = "org/codehaus/aspectwerkz/cflow/AbstractCflowSystemAspect";
    private final static String INSTANCE_CFLOW_FIELD_NAME = "INSTANCE";
    private final static String[] EMPTY_STRING_ARRAY = new String[0];
    public static final String IN_CFLOW_METOD_NAME = "inCflow";
    public static final String IN_CFLOW_METOD_SIGNATURE = "()Z";
    public static final String CFLOW_ASPECTOF_METHOD_NAME = "aspectOf";

    /**
     * Checks if a class name (ASM style) is a cflow name
     *
     * @param className
     * @return
     */
    public static boolean isCflowClass(String className) {
        return className.indexOf(JIT_CFLOW_CLASS) >= 0;
    }

    /**
     * The jit cflow aspect class name (with /)
     */
    private final String m_className;

    /**
     * The jit cflow aspect class name (with /)
     */
    private final String m_classSignature;

    private ClassWriter m_cw;

    /**
     * private ctor
     * @param cflowId
     */
    private CflowCompiler(int cflowId) {
        m_className = getCflowAspectClassName(cflowId);
        m_classSignature = "L"+m_className+";";
    }

    /**
     * compile the jit cflow aspect
     * @return bytecode for the concrete jit cflow aspect
     */
    private byte[] compile() {
        m_cw = AsmHelper.newClassWriter(true);

        // class extends AbstractCflowsystemAspect
        m_cw.visit(
                AsmHelper.JAVA_VERSION,
                ACC_PUBLIC + ACC_SUPER + ACC_SYNTHETIC,
                m_className,
                ABSTRACT_CFLOW_CLASS,
                EMPTY_STRING_ARRAY,
                null
        );

        // static INSTANCE field
        m_cw.visitField(
                ACC_PRIVATE + ACC_STATIC,
                INSTANCE_CFLOW_FIELD_NAME,
                m_classSignature,
                null,
                null
        );

        // private ctor
        CodeVisitor ctor = m_cw.visitMethod(
                ACC_PRIVATE,
                INIT_METHOD_NAME,
                NO_PARAM_RETURN_VOID_SIGNATURE,
                EMPTY_STRING_ARRAY,
                null
        );
        // invoke the constructor of abstract
        ctor.visitVarInsn(ALOAD, 0);
        ctor.visitMethodInsn(INVOKESPECIAL, ABSTRACT_CFLOW_CLASS, INIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE);
        ctor.visitInsn(RETURN);
        ctor.visitMaxs(0, 0);

        // static isInCflow() delegators
        CodeVisitor isInCflow = m_cw.visitMethod(
                ACC_PUBLIC + ACC_STATIC,
                IS_IN_CFLOW_METOD_NAME,
                IS_IN_CFLOW_METOD_SIGNATURE,
                EMPTY_STRING_ARRAY,
                null
        );
        isInCflow.visitFieldInsn(GETSTATIC, m_className, INSTANCE_CFLOW_FIELD_NAME, m_classSignature);
        Label isNull = new Label();
        isInCflow.visitJumpInsn(IFNULL, isNull);
        isInCflow.visitFieldInsn(GETSTATIC, m_className, INSTANCE_CFLOW_FIELD_NAME, m_classSignature);
        isInCflow.visitMethodInsn(INVOKEVIRTUAL, ABSTRACT_CFLOW_CLASS, IN_CFLOW_METOD_NAME, IN_CFLOW_METOD_SIGNATURE);
        isInCflow.visitInsn(IRETURN);
        isInCflow.visitLabel(isNull);
        isInCflow.visitInsn(ICONST_0);
        isInCflow.visitInsn(IRETURN);
        isInCflow.visitMaxs(0, 0);

        // static aspectOf()
        CodeVisitor aspectOf = m_cw.visitMethod(
                ACC_PUBLIC + ACC_STATIC,
                CFLOW_ASPECTOF_METHOD_NAME,
                "()"+m_classSignature,
                EMPTY_STRING_ARRAY,
                null
        );
        aspectOf.visitFieldInsn(GETSTATIC, m_className, INSTANCE_CFLOW_FIELD_NAME, m_classSignature);
        Label isNotNull = new Label();
        aspectOf.visitJumpInsn(IFNONNULL, isNotNull);
        aspectOf.visitTypeInsn(NEW, m_className);
        aspectOf.visitInsn(DUP);
        aspectOf.visitMethodInsn(INVOKESPECIAL, m_className, INIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE);
        aspectOf.visitFieldInsn(PUTSTATIC, m_className, INSTANCE_CFLOW_FIELD_NAME, m_classSignature);
        aspectOf.visitLabel(isNotNull);
        aspectOf.visitFieldInsn(GETSTATIC, m_className, INSTANCE_CFLOW_FIELD_NAME, m_classSignature);
        aspectOf.visitInsn(ARETURN);
        aspectOf.visitMaxs(0, 0);

        m_cw.visitEnd();

        return m_cw.toByteArray();
    }

    /**
     * The naming strategy for jit cflow aspect
     * @param cflowID
     * @return org.codehaus.aspectwerkz.cflow.Cflow_cflowID
     */
    public static String getCflowAspectClassName(int cflowID) {
        return JIT_CFLOW_CLASS + cflowID;
    }

    /**
     * If necessary, compile a jit cflow aspect and attach it to the given classloader
     *
     * @param loader
     * @param cflowID
     * @return
     */
    public static Class compileCflowAspectAndAttachToClassLoader(ClassLoader loader, int cflowID) {
        //TODO we need a Class.forName check first to avoid unecessary compilation
        // else it will fail in defineClass and fallback on Class.forName ie uneeded compilation
        // -> price to pay between compilation + exception in the worse case vs Class.forName each time
        CompiledCflowAspect cflowAspect = compileCflowAspect(cflowID);

        if (AbstractJoinPointCompiler.DUMP_JIT_CLASSES) {
            try {
                AsmHelper.dumpClass("_dump", getCflowAspectClassName(cflowID), cflowAspect.bytecode);
            } catch (Throwable t) {;}
        }
        
        Class cflowAspectClass = AsmHelper.defineClass(
                loader,
                cflowAspect.bytecode,
                getCflowAspectClassName(cflowID)
        );
        return cflowAspectClass;
    }

    /**
     * Compile a jit cflow aspect
     *
     * @param cflowID
     * @return
     */
    public static CompiledCflowAspect compileCflowAspect(int cflowID) {
        CompiledCflowAspect cflowAspect = new CompiledCflowAspect();
        CflowCompiler compiler = new CflowCompiler(cflowID);
        cflowAspect.bytecode = compiler.compile();
        cflowAspect.className = compiler.m_className;
        return cflowAspect;
    }

    /**
     * Information about a compiled Cflow Aspect
     */
    public static class CompiledCflowAspect {
        public byte[] bytecode;
        public String className;// ASM style

        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof CompiledCflowAspect)) return false;

            final CompiledCflowAspect compiledCflowAspect = (CompiledCflowAspect) o;

            if (!className.equals(compiledCflowAspect.className)) return false;

            return true;
        }

        public int hashCode() {
            return className.hashCode();
        }
    }
}
