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

import org.codehaus.aspectwerkz.definition.SystemDefinition;
import org.codehaus.aspectwerkz.expression.ExpressionContext;
import org.codehaus.aspectwerkz.expression.PointcutType;
import org.codehaus.aspectwerkz.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.impl.javassist.JavassistClassInfo;
import org.codehaus.aspectwerkz.transform.Context;
import org.codehaus.aspectwerkz.transform.TransformationUtil;
import org.codehaus.aspectwerkz.transform.Transformer;
import org.codehaus.aspectwerkz.transform.TransformationConstants;
import org.codehaus.aspectwerkz.transform.TransformationConstants;

import java.util.Iterator;
import java.util.List;

import javassist.CannotCompileException;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.CodeIterator;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;

/**
 * Advises method CALL join points.
 * 
 * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
 * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
 */
public class MethodCallUnTransformer implements Transformer {
    /**
     * Transforms the call side pointcuts.
     * 
     * @param context the transformation context
     * @param klass the class set.
     */
    public void transform(final Context context, final Klass klass) throws NotFoundException, CannotCompileException {
        List definitions = context.getDefinitions();
        for (Iterator it = definitions.iterator(); it.hasNext();) {
            final SystemDefinition definition = (SystemDefinition) it.next();
            final CtClass ctClass = klass.getCtClass();
            ClassInfo classInfo = JavassistClassInfo.getClassInfo(ctClass, context.getLoader());
            if (classFilter(definition, new ExpressionContext(PointcutType.CALL, null, classInfo), ctClass)) {
                return;
            }
            ctClass.instrument(new ExprEditor() {
                public void edit(MethodCall methodCall) throws CannotCompileException {
                    try {
                        CtBehavior where = null;
                        try {
                            where = methodCall.where();
                        } catch (RuntimeException e) {
                            // <clinit> access leads to a bug in Javassist
                            where = ctClass.getClassInitializer();
                        }

                        // filter caller methods
                        if (methodFilterCaller(where)) {
                            return;
                        }

                        // get the callee method name, signature and class name
                        CtMethod calleeMethod = methodCall.getMethod();
                        String calleeClassName = methodCall.getClassName();
                        if (!(calleeMethod.getName().equals("proceedWithCallJoinPoint") && calleeClassName
                                .equals("org.codehaus.aspectwerkz.joinpoint.management.JoinPointManager"))) {
                            return;
                        }
                        System.out.println("found smtg to REMOVE");
                        System.out.println("calleeMethod = " + calleeMethod.getName());
                        System.out.println("calleeClassName = " + calleeClassName);
                        System.out.println("methodCall = " + methodCall.indexOfBytecode());
                        methodCall.replace("{java.lang.System.out.println($args[0]); $_=null;}");
                        try {
                            CodeIterator it = where.getMethodInfo().getCodeAttribute().iterator();
                            it.move(methodCall.indexOfBytecode() - 5);
                            System.out.println("it.get() = " + it.get());
                            it.next();
                            System.out.println("it.get() = " + it.get());
                            it.next();
                            System.out.println("it.get() = " + it.get());
                            it.next();
                            System.out.println("it.get() = " + it.get());
                            it.next();
                        } catch (Throwable t) {
                            t.printStackTrace();
                        }
                        return;

                        //
                        //                                // filter callee methods
                        //                                if (methodFilterCallee(calleeMethod)) {
                        //                                    return;
                        //                                }
                        //
                        //                                // create the class meta-data
                        //                                ClassMetaData calleeSideClassMetaData;
                        //                                try {
                        //                                    calleeSideClassMetaData =
                        //                                    JavassistMetaDataMaker.createClassMetaData(
                        //                                            context.getClassPool().get(calleeClassName)
                        //                                    );
                        //                                }
                        //                                catch (NotFoundException e) {
                        //                                    throw new WrappedRuntimeException(e);
                        //                                }
                        //
                        //                                // create the method meta-data
                        //                                MethodMetaData calleeSideMethodMetaData =
                        // JavassistMetaDataMaker.createMethodMetaData(
                        //                                        methodCall.getMethod()
                        //                                );
                        //
                        //                                // is this a caller side method pointcut?
                        //                                if (definition.isPickedOutByCallPointcut(
                        //                                        calleeSideClassMetaData, calleeSideMethodMetaData
                        //                                )) {
                        //
                        //// // TODO: should this caller data be passed to the
                        // join point? It is possible.
                        //// String callerMethodName =
                        // callerBehaviour.getName();
                        //// String callerMethodSignature =
                        // callerBehaviour.getSignature();
                        //// CtClass[] callerMethodParameterTypes =
                        // callerBehaviour.getParameterTypes();
                        //// int callerMethodModifiers =
                        // callerBehaviour.getModifiers();
                        //
                        //                                    // check the callee class is not the same as target
                        // class, if that is the case
                        //                                    // then we have have class loaded and set in the
                        // ___AW_clazz already
                        //                                    String declaringClassMethodName =
                        // TransformationUtil.STATIC_CLASS_FIELD;
                        //                                    CtMethod method = methodCall.getMethod();
                        //                                    CtClass declaringClass = method.getDeclaringClass();
                        //                                    if (!declaringClass.getName().replace('/',
                        // '.').equals(where.getDeclaringClass().getName().replace('/',
                        // '.'))) {
                        //                                        declaringClassMethodName =
                        // addCalleeMethodDeclaringClassField(ctClass, method);
                        //                                    }
                        //
                        //                                    // call the wrapper method instead of the callee
                        // method
                        //                                    StringBuffer body = new StringBuffer();
                        //                                    StringBuffer callBody = new StringBuffer();
                        //
                        //                                    callBody.append(TransformationUtil.JOIN_POINT_MANAGER_FIELD);
                        //                                    callBody.append('.');
                        //                                    callBody.append(TransformationUtil.PROCEED_WITH_CALL_JOIN_POINT_METHOD);
                        //                                    callBody.append('(');
                        //                                    callBody.append(TransformationUtil.calculateHash(method));
                        //                                    callBody.append(',');
                        //                                    callBody.append(1);
                        //                                    callBody.append(", args");
                        //                                    callBody.append(", $0, declaringClassMethodName, ");
                        //                                    callBody.append(TransformationUtil.JOIN_POINT_TYPE_METHOD_CALL);
                        //                                    callBody.append(");");
                        //
                        //                                    body.append('{');
                        //                                    if (method.getParameterTypes().length > 0) {
                        //                                        body.append("Object[] args = $args; ");
                        //                                    }
                        //                                    else {
                        //                                        body.append("Object[] args = null; ");
                        //                                    }
                        //                                    body.append("Class declaringClassMethodName = ");
                        //                                    body.append(declaringClassMethodName);
                        //                                    body.append("; ");
                        //
                        //                                    if (methodCall.getMethod().getReturnType() ==
                        // CtClass.voidType) {
                        //                                        body.append("$_ =
                        // ").append(callBody.toString()).append("}");
                        //                                    }
                        //                                    else if
                        // (!methodCall.getMethod().getReturnType().isPrimitive())
                        // {
                        //                                        body.append("$_ = ($r)");
                        //                                        body.append(callBody.toString());
                        //                                        body.append("}");
                        //                                    }
                        //                                    else {
                        //                                        String localResult =
                        // TransformationUtil.ASPECTWERKZ_PREFIX + "res";
                        //                                        body.append("Object ").append(localResult).append(" =
                        // ");
                        //                                        body.append(callBody.toString());
                        //                                        body.append("if (").append(localResult).append(" !=
                        // null)");
                        //                                        body.append("$_ = ($r)
                        // ").append(localResult).append("; else ");
                        //                                        body.append("$_ = ");
                        //                                        body.append(
                        //                                                JavassistHelper.getDefaultPrimitiveValue(
                        //                                                        methodCall.getMethod().getReturnType()
                        //                                                )
                        //                                        );
                        //                                        body.append("; }");
                        //                                    }
                        //
                        //                                    methodCall.replace(body.toString());
                        //                                    context.markAsAdvised();
                        //
                        //                                    //1++;
                        //                                }
                    } catch (NotFoundException nfe) {
                        nfe.printStackTrace();

                        // TODO: should we swallow this exception?
                    }
                }
            });
        }

        //TransformationUtil.setJoinPointIndex(klass.getCtClass(), 1);
    }

    /**
     * Creates a new static class field, for the declaring class of the callee method.
     * 
     * @param ctClass the class
     * @param ctMethod the method
     * @return the name of the field
     */
    private String addCalleeMethodDeclaringClassField(final CtClass ctClass, final CtMethod ctMethod) throws NotFoundException,
            CannotCompileException {
        String fieldName = TransformationConstants.STATIC_CLASS_FIELD
            + TransformationConstants.DELIMITER
            + "method"
            + TransformationConstants.DELIMITER
            + ctMethod.getDeclaringClass().getName().replace('.', '_');
        boolean hasField = false;
        CtField[] fields = ctClass.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            CtField field = fields[i];
            if (field.getName().equals(fieldName)) {
                hasField = true;
                break;
            }
        }
        if (!hasField) {
            CtField field = new CtField(ctClass.getClassPool().get("java.lang.Class"), fieldName, ctClass);
            field.setModifiers(Modifier.STATIC | Modifier.PRIVATE | Modifier.FINAL);
            ctClass.addField(field, "java.lang.Class#forName(\""
                + ctMethod.getDeclaringClass().getName().replace('/', '.')
                + "\")");
        }
        return fieldName;
    }

    /**
     * Filters the classes to be transformed.
     * 
     * @param definition the definition
     * @param ctx the context
     * @param cg the class to filter
     * @return boolean true if the method should be filtered away
     */
    public static boolean classFilter(final SystemDefinition definition, final ExpressionContext ctx, final CtClass cg) {
        if (cg.isInterface()) {
            return true;
        }
        String className = cg.getName().replace('/', '.');
        if (definition.inExcludePackage(className)) {
            return true;
        }
        if (!definition.inIncludePackage(className)) {
            return true;
        }
        if (definition.isAdvised(ctx)) {
            return false;
        }
        return true;
    }

    /**
     * Filters the caller methods.
     * 
     * @param method the method to filter
     * @return boolean true if the method should be filtered away
     */
    public static boolean methodFilterCaller(final CtBehavior method) {
        if (Modifier.isNative(method.getModifiers())
            || Modifier.isInterface(method.getModifiers())
            || method.getName().equals(TransformationConstants.CLASS_LOOKUP_METHOD)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Filters the callee methods.
     * 
     * @param method the name of method to filter
     * @return boolean true if the method should be filtered away
     * @TODO: create metadata instance and check with the system
     */
    public static boolean methodFilterCallee(final CtMethod method) {
        if (method.getName().equals("<init>")
            || method.getName().equals("<clinit>")
            || method.getName().startsWith(TransformationConstants.ORIGINAL_METHOD_PREFIX)
            || method.getName().equals(TransformationConstants.CLASS_LOOKUP_METHOD)) {
            return true;
        } else {
            return false;
        }
    }
}