/**************************************************************************************
 * 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.transform.inlining;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.CodeVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.Constants;
import org.objectweb.asm.CodeAdapter;
import org.codehaus.aspectwerkz.transform.Context;
import org.codehaus.aspectwerkz.transform.TransformationUtil;

import java.util.Iterator;

/**
 * A ClassAdapter that take care of all weaved class and add the glue between the class and its JIT dependancies. Add
 * private static final Class ___AW_Clazz field. Add private static void ___AW_$_AW_$initJoinPoints() method. Add or
 * patch <clinit>method.
 * 
 * @TODO: for multi weaving, we could go on in adding several AW initJoinPoints_xxWeaveCount method, but then cannot be
 *        done with RW
 * 
 * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
 */
public class JoinPointInitClassAdapter extends ClassAdapter implements Constants, TransformationConstants {

    private static final String TARGET_CLASS_FIELD_NAME = "___AW_Clazz";

    private final ContextImpl m_ctx;

    private boolean m_hasClinitMethod = false;

    private boolean m_hasInitJoinPointsMethod = false;

    private boolean m_hasClassField = false;

    /**
     * Creates a new instance.
     * 
     * @param cv
     * @param ctx
     */
    public JoinPointInitClassAdapter(final ClassVisitor cv, final Context ctx) {
        super(cv);
        m_ctx = (ContextImpl) ctx;
    }

    /**
     * Visits the methods. If the AW joinPointsInit method is found, remember that, it means we are in a multi-weaving
     * scheme. Patch the <clinit>method if already present. TODO: multi-weaving will lead to several invocation of AW
     * initJoinPoints and several assigment of __AW_Clazz in the patched clinit which slows down a bit the load time
     * 
     * @see org.objectweb.asm.ClassVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String[],
     *      org.objectweb.asm.Attribute)
     */
    public CodeVisitor visitMethod(
        final int access,
        final String name,
        final String desc,
        final String[] exceptions,
        final Attribute attrs) {

        // unweaved class
        //TODO: I found it unefficient - would be better to skip the whole ClassAdapter if no JP
        if (!m_ctx.isAdvised()) {
            return cv.visitMethod(access, name, desc, exceptions, attrs);
        }

        if (CLINIT_METHOD_NAME.equals(name)) {

            m_hasClinitMethod = true;

            // at the beginning of the existing <clinit> method
            //      ___AWClazz = Class.forName("TargetClassName");
            //      ___AW_$_AW_$initJoinPoints();
            CodeVisitor ca = new InsertBeforeClinitCodeAdapter(cv.visitMethod(access, name, desc, exceptions, attrs));

            ca.visitMaxs(0, 0);
            return ca;

        } else if (TransformationUtil.JOIN_POINTS_INIT_METHOD.equals(name)) {

            m_hasInitJoinPointsMethod = true;

            // add the gathered JIT dependancies for multi-weaving support
            CodeVisitor ca = new InsertBeforeInitJoinPointsCodeAdapter(cv.visitMethod(
                access,
                name,
                desc,
                exceptions,
                attrs));

            ca.visitMaxs(0, 0);
            return ca;

        } else {
            // leave as-is
            return cv.visitMethod(access, name, desc, exceptions, attrs);
        }
    }

    /**
     * Remember if we have already the static class field for multi-weaving scheme.
     * 
     * @param access
     * @param name
     * @param desc
     * @param value
     * @param attrs
     */
    public void visitField(int access, String name, String desc, Object value, Attribute attrs) {
        if (TransformationUtil.STATIC_CLASS_FIELD.equals(name)) {
            m_hasClassField = true;
        }
        // leave as-is
        cv.visitField(access, name, desc, value, attrs);
    }

    /**
     * Finalize the visit. Add static class field if needed, add initJoinPoints method if needed, add <clinit>if
     * needed.
     */
    public void visitEnd() {
        if (!m_ctx.isAdvised()) {
            return;
        }

        if (!m_hasClassField) {
            // create field
            //      private final static Class ___AW_Clazz = Class.forName("TargetClassName");
            cv.visitField(
                ACC_PRIVATE | ACC_FINAL | ACC_STATIC,
                TARGET_CLASS_FIELD_NAME,
                CLASS_CLASS_SIGNATURE,
                null,
                null);
        }

        if (!m_hasClinitMethod) {
            // create the <clinit> method since there is none
            //      static {
            //          ___AWClazz = Class.forName("TargetClassName");
            //          ___AW_$_AW_$initJoinPoints();
            //      }
            CodeVisitor ca = new InsertBeforeClinitCodeAdapter(cv.visitMethod(
                ACC_STATIC,
                CLINIT_METHOD_NAME,
                "()V",
                null,
                null));
            ca.visitInsn(RETURN);
            ca.visitMaxs(0, 0);
        }

        if (!m_hasInitJoinPointsMethod) {
            // create method
            //      private static void ___AW_$_AW_$initJoinPoints() {
            CodeVisitor mv = new InsertBeforeInitJoinPointsCodeAdapter(cv.visitMethod(
                ACC_PRIVATE + ACC_STATIC,
                "___AW_$_AW_$initJoinPoints",
                "()V",
                null,
                null));
            mv.visitInsn(RETURN);
            mv.visitMaxs(0, 0);
        }
    }

    /**
     * Handles the method body of the <clinit>method.
     * 
     * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
     */
    public class InsertBeforeClinitCodeAdapter extends CodeAdapter {

        public InsertBeforeClinitCodeAdapter(CodeVisitor ca) {
            super(ca);
            cv.visitLdcInsn(m_ctx.getClassName().replace('/', '.'));
            cv.visitMethodInsn(INVOKESTATIC, CLASS_CLASS, "forName", "(Ljava/lang/String;)Ljava/lang/Class;");
            cv.visitFieldInsn(PUTSTATIC, m_ctx.getClassName(), TARGET_CLASS_FIELD_NAME, CLASS_CLASS_SIGNATURE);
            cv.visitMethodInsn(INVOKESTATIC, m_ctx.getClassName(), "___AW_$_AW_$initJoinPoints", "()V");
        }
    }

    /**
     * Handles the method body of the AW initJoinPoints method.
     * 
     * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
     */
    public class InsertBeforeInitJoinPointsCodeAdapter extends CodeAdapter {

        public InsertBeforeInitJoinPointsCodeAdapter(CodeVisitor ca) {
            super(ca);
            // 1 - call "InlinedJoinPointManager.loadJoinPointBase(___AW_Clazz)"
            cv.visitFieldInsn(GETSTATIC, m_ctx.getClassName(), TARGET_CLASS_FIELD_NAME, CLASS_CLASS_SIGNATURE);
            cv.visitMethodInsn(
                INVOKESTATIC,
                "org/codehaus/aspectwerkz/joinpoint/management/InlinedJoinPointManager",
                "loadJoinPointBase",
                "(Ljava/lang/Class;)V");

            // 2 - loop over emitted jp
            // call "InlinedJoinPointManager.loadJoinPoint(...)"
            for (Iterator iterator = m_ctx.getEmittedInlinedJoinPoint().iterator(); iterator.hasNext();) {
                ContextImpl.EmittedInlinedJoinPoint jp = (ContextImpl.EmittedInlinedJoinPoint) iterator.next();
                cv.visitFieldInsn(GETSTATIC, m_ctx.getClassName(), TARGET_CLASS_FIELD_NAME, CLASS_CLASS_SIGNATURE);
                cv.visitLdcInsn(new Integer(jp.joinPointType));//TODO: how to do better
                cv.visitLdcInsn(jp.calleeName);
                cv.visitLdcInsn(jp.calleeDesc);
                cv.visitLdcInsn(new Integer(jp.calleeModifiers));//TODO: how to do better
                AsmHelper.addNullableString(cv, jp.callerClassName);
                AsmHelper.addNullableString(cv, jp.callerName);
                AsmHelper.addNullableString(cv, jp.callerDesc);
                cv.visitLdcInsn(new Integer(jp.sequence));//TODO: how to do better
                cv.visitLdcInsn(new Integer(jp.joinPointHash));//TODO: how to do better
                cv.visitLdcInsn(jp.joinPointClassName);
                cv
                        .visitMethodInsn(
                            INVOKESTATIC,
                            "org/codehaus/aspectwerkz/joinpoint/management/InlinedJoinPointManager",
                            "loadJoinPoint",
                            "(Ljava/lang/Class;ILjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)V");
            }
        }
    }

}