/**************************************************************************************
 * 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.objectweb.asm.Type;
import org.codehaus.aspectwerkz.definition.SystemDefinition;
import org.codehaus.aspectwerkz.expression.ExpressionContext;
import org.codehaus.aspectwerkz.expression.PointcutType;
import org.codehaus.aspectwerkz.joinpoint.management.JoinPointType;
import org.codehaus.aspectwerkz.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.MethodInfo;
import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
import org.codehaus.aspectwerkz.reflect.impl.asm.AsmMethodInfo;
import org.codehaus.aspectwerkz.transform.Context;
import org.codehaus.aspectwerkz.transform.TransformationUtil;

import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;

import javax.swing.border.EtchedBorder;

/**
 * Instruments method call join points.
 * 
 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
 */
public class MethodCallClassAdapter extends ClassAdapter implements Constants, TransformationConstants {

    private final ContextImpl m_ctx;

    private final ClassLoader m_loader;

    private final ClassInfo m_classInfo;

    /**
     * Creates a new instance.
     * 
     * @param cv
     * @param loader
     * @param classInfo
     * @param ctx
     */
    public MethodCallClassAdapter(final ClassVisitor cv,
                                  final ClassLoader loader,
                                  final ClassInfo classInfo,
                                  final Context ctx) {
        super(cv);
        m_loader = loader;
        m_classInfo = classInfo;
        m_ctx = (ContextImpl) ctx;
    }

    /**
     * Visits the caller methods.
     */
    public CodeVisitor visitMethod(
        final int access,
        final String name,
        final String desc,
        final String[] exceptions,
        final Attribute attrs) {

        CodeVisitor ca = new ReplaceInvokeInstructionCodeAdapter(
            cv.visitMethod(access, name, desc, exceptions, attrs),
            m_loader,
            m_classInfo,
            m_ctx.getClassName(),
            name,
            desc);

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

    /**
     * Replaces 'INVOKEXXX' instructions with a call to the compiled JoinPoint instance.
     * 
     * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
     */
    public class ReplaceInvokeInstructionCodeAdapter extends CodeAdapter {

        private final CodeVisitor m_visitor;
        
        private final ClassLoader m_loader;

        private final ClassInfo m_classInfo;

        private final String m_callerClassName;

        private final String m_callerMethodName;

        private final String m_callerMethodDesc;

        private final MethodInfo m_callerMethodInfo;

        private int m_sequence = 1;

        /**
         * Creates a new instance.
         * 
         * @param cv
         * @param calleeClassName
         * @param calleeMethodName
         * @param calleeMethodDesc
         */
        public ReplaceInvokeInstructionCodeAdapter(final CodeVisitor ca,
                                                   final ClassLoader loader,
                                                   final ClassInfo classInfo,
                                                   final String callerClassName,
                                                   final String callerMethodName,
                                                   final String callerMethodDesc) {
            super(ca);
            m_visitor = ca;
            m_loader = loader;
            m_classInfo = classInfo;
            m_callerClassName = callerClassName;
            m_callerMethodName = callerMethodName;
            m_callerMethodDesc = callerMethodDesc;

            m_callerMethodInfo = m_classInfo.getMethod(AsmHelper.calculateMethodHash(
                m_callerMethodName,
                m_callerMethodDesc));
            if (m_callerMethodInfo == null) {
                throw new Error("caller method info metadata structure could not be build for method: "
                    + callerMethodName
                    + " "
                    + callerMethodDesc);
            }
        }

        /**
         * Visits 'INVOKEXXX' instructions.
         */
        public void visitMethodInsn(
            final int opcode,
            final String calleeClassName,
            final String calleeMethodName,
            final String calleeMethodDesc) {

            int joinPointHash = AsmHelper.calculateMethodHash(calleeMethodName, calleeMethodDesc);

            MethodInfo calleeMethodInfo = m_classInfo.getMethod(joinPointHash);
            if (calleeMethodInfo == null) {
                throw new Error("callee method info metadata structure could not be build for method: "
                    + calleeMethodName
                    + " "
                    + calleeMethodDesc);
            }

            ExpressionContext ctx = new ExpressionContext(PointcutType.CALL, calleeMethodInfo, m_callerMethodInfo);

            if (methodFilter(m_ctx.getDefinitions(), ctx, calleeMethodInfo)) {
                visitMethodInsn(opcode, calleeClassName, calleeMethodName, calleeMethodDesc);

            } else {
                m_ctx.markAsAdvised();
                m_sequence++; // single place of incrementation, is used in multiple places

                // TODO is caller class sufficient in the name or do we need callee classname?
                String joinPointClassName = JoinPointCompiler.getJoinPointClassName(
                    m_callerClassName,
                    JoinPointType.METHOD_EXECUTION,
                    joinPointHash);

                if (Modifier.isStatic(m_callerMethodInfo.getModifiers())) {
                    m_visitor.visitInsn(ACONST_NULL);
                } else {
                    m_visitor.visitVarInsn(ALOAD, 0);
                }

                StringBuffer sig = new StringBuffer();
                int index = calleeMethodDesc.lastIndexOf(')');
                sig.append(calleeMethodDesc.substring(0, index));
                sig.append('L');
                sig.append(calleeClassName);
                sig.append(';');
                sig.append(calleeMethodDesc.substring(index, calleeMethodDesc.length()));
                m_visitor.visitMethodInsn(INVOKESTATIC, joinPointClassName, "invoke", sig.toString());

                AsmHelper.addReturnStatement(m_visitor, Type.getReturnType(calleeMethodDesc));
                m_visitor.visitMaxs(0, 0);

                switch (opcode) {
                    case INVOKEINTERFACE:

                        break;
                    case INVOKESPECIAL:

                        break;
                    case INVOKESTATIC:

                        break;
                    case INVOKEVIRTUAL:

                        break;
                }

                // emit the joinpoint
                m_ctx.addEmittedInlinedJoinPoint(new ContextImpl.EmittedInlinedJoinPoint(
                    JoinPointType.METHOD_CALL,
                    calleeClassName,
                    calleeMethodName,
                    calleeMethodDesc,
                    calleeMethodInfo.getModifiers(),
                    m_callerClassName,
                    m_callerMethodName,
                    m_callerMethodDesc,
                    m_sequence,
                    joinPointHash,
                    joinPointClassName));
            }
        }

        /**
         * Filters out the methods that are not eligible for transformation.
         * 
         * @param definitions
         * @param ctx
         * @param method
         * @return boolean true if the method should be filtered out
         */
        public boolean methodFilter(
            final List definitions,
            final ExpressionContext ctx,
            final MethodInfo methodInfo) {
            if (Modifier.isAbstract(methodInfo.getModifiers())
                || Modifier.isNative(methodInfo.getModifiers())
                || methodInfo.getName().equals(INIT_METHOD_NAME)
                || methodInfo.getName().equals(CLINIT_METHOD_NAME)
                || methodInfo.getName().startsWith(TransformationUtil.ORIGINAL_METHOD_PREFIX)) {
                return true;
            }
            for (Iterator it = definitions.iterator(); it.hasNext();) {
                if (((SystemDefinition) it.next()).hasPointcut(ctx)) {
                    return false;
                } else {
                    continue;
                }
            }
            return true;
        }
    }
}