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

import org.codehaus.aspectwerkz.expression.ast.ASTAnd;
import org.codehaus.aspectwerkz.expression.ast.ASTAttribute;
import org.codehaus.aspectwerkz.expression.ast.ASTCall;
import org.codehaus.aspectwerkz.expression.ast.ASTCflow;
import org.codehaus.aspectwerkz.expression.ast.ASTCflowBelow;
import org.codehaus.aspectwerkz.expression.ast.ASTClassPattern;
import org.codehaus.aspectwerkz.expression.ast.ASTConstructorPattern;
import org.codehaus.aspectwerkz.expression.ast.ASTExecution;
import org.codehaus.aspectwerkz.expression.ast.ASTExpression;
import org.codehaus.aspectwerkz.expression.ast.ASTFieldPattern;
import org.codehaus.aspectwerkz.expression.ast.ASTGet;
import org.codehaus.aspectwerkz.expression.ast.ASTHandler;
import org.codehaus.aspectwerkz.expression.ast.ASTMethodPattern;
import org.codehaus.aspectwerkz.expression.ast.ASTModifier;
import org.codehaus.aspectwerkz.expression.ast.ASTParameter;
import org.codehaus.aspectwerkz.expression.ast.ASTPointcutReference;
import org.codehaus.aspectwerkz.expression.ast.ASTRoot;
import org.codehaus.aspectwerkz.expression.ast.ASTSet;
import org.codehaus.aspectwerkz.expression.ast.ASTStaticInitialization;
import org.codehaus.aspectwerkz.expression.ast.ASTWithin;
import org.codehaus.aspectwerkz.expression.ast.ASTWithinCode;
import org.codehaus.aspectwerkz.expression.ast.ExpressionParserVisitor;
import org.codehaus.aspectwerkz.expression.ast.Node;
import org.codehaus.aspectwerkz.expression.ast.SimpleNode;
import org.codehaus.aspectwerkz.expression.ast.ASTArgs;
import org.codehaus.aspectwerkz.expression.ast.ASTArgParameter;
import org.codehaus.aspectwerkz.expression.ast.ASTHasField;
import org.codehaus.aspectwerkz.expression.ast.ASTHasMethod;
import org.codehaus.aspectwerkz.expression.ast.ASTTarget;
import org.codehaus.aspectwerkz.expression.ast.ASTThis;
import org.codehaus.aspectwerkz.expression.ast.ASTNot;
import org.codehaus.aspectwerkz.expression.regexp.TypePattern;
import org.codehaus.aspectwerkz.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.MemberInfo;
import org.codehaus.aspectwerkz.reflect.ReflectionInfo;
import org.codehaus.aspectwerkz.reflect.MethodInfo;
import org.codehaus.aspectwerkz.reflect.ConstructorInfo;
import org.codehaus.aspectwerkz.reflect.FieldInfo;
import org.codehaus.aspectwerkz.reflect.ClassInfoHelper;
import org.codehaus.aspectwerkz.reflect.StaticInitializationInfo;
import org.codehaus.aspectwerkz.annotation.AnnotationInfo;
import org.codehaus.aspectwerkz.util.Util;

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

/**
 * The advised class filter visitor.
 * <p/>
 * Visit() methods are returning Boolean.TRUE/FALSE or null when decision cannot be taken.
 * Using null allow composition of OR/AND with NOT in the best way.
 *
 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
 * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
 * @author Michael Nascimento
 * @author <a href="mailto:the_mindstorm@evolva.ro">Alex Popescu</a>
 */
public class AdvisedClassFilterExpressionVisitor extends ExpressionVisitor implements ExpressionParserVisitor {

    /**
     * Creates a new expression.
     *
     * @param expression the expression as a string
     * @param namespace  the namespace
     * @param root       the AST root
     */
    public AdvisedClassFilterExpressionVisitor(final ExpressionInfo expressionInfo, final String expression,
                                               final String namespace, final Node root) {
        super(expressionInfo, expression, namespace, root);
    }

    // ============ Boot strap =============
    public Object visit(SimpleNode node, Object data) {
        return node.jjtGetChild(0).jjtAccept(this, data);
    }

    public Object visit(ASTRoot node, Object data) {
        Node child = node.jjtGetChild(0);
        Boolean match = (Boolean) child.jjtAccept(this, data);
        return match;
    }

    public Object visit(ASTExpression node, Object data) {
        Node child = node.jjtGetChild(0);
        Boolean match = (Boolean) child.jjtAccept(this, data);
        return match;
    }

    public Object visit(ASTNot node, Object data) {
        return super.visit(node,data);
    }

    // ============ Pointcut types =============
    public Object visit(ASTPointcutReference node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        ExpressionNamespace namespace = ExpressionNamespace.getNamespace(m_namespace);
        AdvisedClassFilterExpressionVisitor expression = namespace.getAdvisedClassExpression(node.getName());
        return expression.matchUndeterministic(context);
    }

    public Object visit(ASTExecution node, Object data) {
        ExpressionContext context = (ExpressionContext) data;

        // only the last node might be the pattern, others are annotations
        Node patternNode = node.jjtGetChild(node.jjtGetNumChildren() - 1);
        boolean checkPattern = !(patternNode instanceof ASTAttribute);
        
        if(checkPattern) {
            if (context.hasWithinPointcut() || context.hasExecutionPointcut()) {
                if (context.hasExecutionPointcut()) {
                    // reflectionInfo was given
                    return patternNode.jjtAccept(this, context.getReflectionInfo());
                } else {
                    // only withinInfo was given
                    return patternNode.jjtAccept(this, context.getWithinReflectionInfo()); 
                }
            } else {
                return Boolean.FALSE;
            }
        } else {
        	return null;
        }
    }

    public Object visit(ASTCall node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        
        // only the last node might be the pattern, others are annotations
        Node patternNode = node.jjtGetChild(node.jjtGetNumChildren() - 1);
        boolean checkPattern = !(patternNode instanceof ASTAttribute);
        
        if(checkPattern) {
            if (context.hasWithinPointcut() || context.hasCallPointcut()) {
                if (context.hasReflectionInfo()) {
                        return patternNode.jjtAccept(this, context.getReflectionInfo());
                } else {
                    return null;
                }
            } else {
                return Boolean.FALSE;
            }
        } else {
        	return null;
        }
    }

    public Object visit(ASTSet node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        
        // only the last node might be the pattern, others are annotations
        Node patternNode = node.jjtGetChild(node.jjtGetNumChildren() - 1);
        boolean checkPattern = !(patternNode instanceof ASTAttribute);
        
        // for set evaluation, the reflection info may be null at the early matching phase
        // when we will allow for field interception within non declaring class
        if(checkPattern) {
            if (context.hasWithinPointcut() || context.hasSetPointcut()) {
                if (context.hasReflectionInfo()) {
                        return patternNode.jjtAccept(this, context.getReflectionInfo());
                } else {
                    return null;
                }
            } else {
                return Boolean.FALSE;
            }
        } else {
        	return null;
        }
    }

    public Object visit(ASTGet node, Object data) {
        ExpressionContext context = (ExpressionContext) data;

        // only the last node might be the pattern, others are annotations
        Node patternNode = node.jjtGetChild(node.jjtGetNumChildren() - 1);
        boolean checkPattern = !(patternNode instanceof ASTAttribute);
        
        // for get evaluation, the reflection info may be null at the early matching phase
        // since we allow for field interception within non declaring class
        if(checkPattern) {
            if (context.hasWithinPointcut() || context.hasGetPointcut()) {
                if (context.hasReflectionInfo()) {
                	return patternNode.jjtAccept(this, context.getReflectionInfo());
                } else {
                    return null;
                }
            } else {
                return Boolean.FALSE;
            }
        } else {
        	return null;
        }
    }

    public Object visit(ASTHandler node, Object data) {
        return null;
    }

    public Object visit(ASTStaticInitialization node, Object data) {
        ExpressionContext context = (ExpressionContext) data;

        if (context.hasStaticInitializationPointcut() && context.hasWithinReflectionInfo()) {
        	ReflectionInfo reflectInfo = context.getWithinReflectionInfo();
            if (reflectInfo instanceof StaticInitializationInfo) {
                reflectInfo = ((StaticInitializationInfo) reflectInfo).getDeclaringType();
            }
        	if (reflectInfo instanceof ClassInfo) {
                // In an annotated subtree, the last child node represents the pattern
                Node patternNode = node.jjtGetChild(node.jjtGetNumChildren() - 1);
                if (!(patternNode instanceof ASTAttribute)) {
                    Boolean matchPattern = (Boolean) patternNode.jjtAccept(this, reflectInfo);
                    if (Boolean.FALSE.equals(matchPattern)) {
                        return Boolean.FALSE;
                    }
                }

                // match on the annotations since the pattern was not there or matched
            	boolean matchedAnnotations = visitAttributes(node, reflectInfo);
            	if (!matchedAnnotations) {
            		return Boolean.FALSE;
            	} else {
                    return null;//match but early phase
                }
        	} else {
        		return Boolean.FALSE;
        	}
        } else {
            return Boolean.FALSE;
        }
    }

    public Object visit(ASTWithinCode node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        ReflectionInfo withinInfo = context.getWithinReflectionInfo();

        if (node.isStaticInitializer()) {
            // transform the node in a within node to do an exact match on the within info
            //TODO would be worth to do the fastNode creation only once somewhere
            ASTWithin fastNode = new ASTWithin(0);
            for (int i = 0; i < node.jjtGetChild(0).jjtGetNumChildren(); i++) {
                  fastNode.jjtAddChild(node.jjtGetChild(0).jjtGetChild(i), i);
            }
            return super.visit(fastNode, data);
        } else {
	        Node patternNode = node.jjtGetChild(node.jjtGetNumChildren() - 1);
	        boolean checkPattern = !(patternNode instanceof ASTAttribute);
	
	        if (checkPattern) {
	            if (withinInfo instanceof MemberInfo) {
	                return patternNode.jjtAccept(this, withinInfo);
	            } else if (withinInfo instanceof ClassInfo) {
	                Boolean matchDeclaringType = (Boolean) patternNode.jjtAccept(this, withinInfo);
	                if (Boolean.FALSE.equals(matchDeclaringType)) {
	                    return Boolean.FALSE;
	                } else {
	                    // may be we match now but not later?
	                    return null;
	                }
	            } else {
	                return null;
	            }
	        } else {
	            return null;
	        }
        }
    }

    public Object visit(ASTCflow node, Object data) {
        return null;
    }

    public Object visit(ASTCflowBelow node, Object data) {
        return null;
    }

    public Object visit(ASTArgs node, Object data) {
        return null;
    }

    public Object visit(ASTTarget node, Object data) {
        return null;// is that good enough ? For execution PC we would optimize some
    }

    public Object visit(ASTThis node, Object data) {
        ExpressionContext context = (ExpressionContext) data;
        if (context.hasWithinReflectionInfo()) {
            ReflectionInfo withinInfo = context.getWithinReflectionInfo();
            if (withinInfo instanceof MemberInfo) {
                return Util.booleanValueOf(
                        ClassInfoHelper.instanceOf(
                                ((MemberInfo) withinInfo).getDeclaringType(),
                                node.getBoundedType(m_expressionInfo)
                        )
                );
            } else if (withinInfo instanceof ClassInfo) {
                return Util.booleanValueOf(
                        ClassInfoHelper.instanceOf((ClassInfo) withinInfo, node.getBoundedType(m_expressionInfo))
                );
            }
        }
        return Boolean.FALSE;
    }

    // ============ Patterns =============
//    public Object visit(ASTClassPattern node, Object data) {
//        if(null == data) {
//            return null;
//        } else if( !(data instanceof ClassInfo) ) {
//        	return Boolean.FALSE;
//        }
//
//        ClassInfo classInfo = (ClassInfo) data;
//        if (node.getTypePattern().matchType(classInfo) && visitAttributes(node, classInfo)) {
//            return Boolean.TRUE;
//        } else {
//            return Boolean.FALSE;
//        }
//    }

    public Object visit(ASTMethodPattern node, Object data) {
        if (data instanceof ClassInfo) {
            ClassInfo classInfo = (ClassInfo) data;
            if (node.getDeclaringTypePattern().matchType(classInfo)) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        } else if (data instanceof MethodInfo) {
            MethodInfo methodInfo = (MethodInfo) data;
            if (node.getDeclaringTypePattern().matchType(methodInfo.getDeclaringType())) {
                return null;// it might not match further because of modifiers etc
            }
            return Boolean.FALSE;
        }
        return Boolean.FALSE;
    }

    public Object visit(ASTConstructorPattern node, Object data) {
        if (data instanceof ClassInfo) {
            ClassInfo classInfo = (ClassInfo) data;
            if (node.getDeclaringTypePattern().matchType(classInfo)) {
                // we matched but the actual match result may be false
                return Boolean.TRUE;
            }
        } else if (data instanceof ConstructorInfo) {
            ConstructorInfo constructorInfo = (ConstructorInfo) data;
            if (node.getDeclaringTypePattern().matchType(constructorInfo.getDeclaringType())) {
                return null;// it might not match further because of modifiers etc
            }
            return Boolean.FALSE;
        }
        return Boolean.FALSE;
    }

    public Object visit(ASTFieldPattern node, Object data) {
        if (data instanceof ClassInfo) {
            ClassInfo classInfo = (ClassInfo) data;
            if (node.getDeclaringTypePattern().matchType(classInfo)) {
                // we matched but the actual match result may be false
                return Boolean.TRUE;
            }
        } else if (data instanceof FieldInfo) {
            FieldInfo fieldInfo = (FieldInfo) data;
            if (node.getDeclaringTypePattern().matchType(fieldInfo.getDeclaringType())) {
                return null;// it might not match further because of modifiers etc
            }
            return Boolean.FALSE;
        }
        return Boolean.FALSE;
    }

    public Object visit(ASTParameter node, Object data) {
        ClassInfo parameterType = (ClassInfo) data;
        if (node.getDeclaringClassPattern().matchType(parameterType)) {
            // we matched but the actual match result may be false
            return Boolean.TRUE;
        } else {
            return Boolean.FALSE;
        }
    }

    public Object visit(ASTArgParameter node, Object data) {
        // never called
        return Boolean.TRUE;
    }

    public Object visit(ASTAttribute node, Object data) {
        // called for class level annotation matching f.e. in a within context
        boolean matchAnnotation = false;
        List annotations = (List) data;
        for (Iterator it = annotations.iterator(); it.hasNext();) {
            AnnotationInfo annotation = (AnnotationInfo) it.next();
            if (annotation.getName().equals(node.getName())) {
                matchAnnotation = true;
            }
        }
        if (node.isNot()) {
            return Util.booleanValueOf(!matchAnnotation);
        } else {
            return Util.booleanValueOf(matchAnnotation);
        }
    }

    public Object visit(ASTModifier node, Object data) {
        // TODO
        return null;
    }

    /**
     * Returns the string representation of the AST.
     *
     * @return
     */
    public String toString() {
        return m_expression;
    }
}
