/**************************************************************************************
 * 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.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.ConstructorInfo;
import org.codehaus.aspectwerkz.reflect.FieldInfo;
import org.codehaus.aspectwerkz.reflect.MethodInfo;
import org.codehaus.aspectwerkz.reflect.ReflectionInfo;
import org.codehaus.aspectwerkz.reflect.StaticInitializationInfo;

import gnu.trove.TIntIntHashMap;
import gnu.trove.TObjectIntHashMap;

/**
 * The expression context for AST evaluation.
 *
 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
 * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
 */
public class ExpressionContext {
    public static final int INFO_NOT_AVAILABLE = -1;

    public static final int METHOD_INFO = 0;

    public static final int CONSTRUCTOR_INFO = 1;

    public static final int FIELD_INFO = 2;

    public static final int CLASS_INFO = 3;
    
    private static final int STATIC_INFO = 4;

    private final int m_reflectionInfoType;

    private final PointcutType m_pointcutType;

    private final ReflectionInfo m_matchingReflectionInfo;

    private final ReflectionInfo m_withinReflectionInfo;

    private boolean m_inCflowSubAST = false;

    private boolean m_cflowEvaluation = false;

    private boolean m_hasBeenVisitingCflow = false;

    private int m_currentTargetArgsIndex = 0;

    /**
     * Expression to advised target (method / ctor) argument index map.
     * It depends on the matching context and the pointcut signature, as well as args(..)
     */
    public gnu.trove.TObjectIntHashMap m_exprIndexToTargetIndex = new TObjectIntHashMap();

    /**
     * The variable name corresponding to the this(..) designator,
     * or null if nothing is bound (this(<type>) or no this(..))
     */
    public String m_thisBoundedName = null;

    /**
     * The variable name corresponding to the target(..) designator,
     * or null if nothing is bound (target(<type>) or no target(..))
     */
    public String m_targetBoundedName = null;

    /**
     * Set to true when we encounter a poincut using target(..) and when match cannot be done without a
     * runtime check with instance of.
     */
    public boolean m_targetWithRuntimeCheck = false;

    /**
     * Creates a new expression context.
     *
     * @param pointcutType
     * @param reflectionInfo       - can be null f.e. with early evaluation of CALL pointcut
     * @param withinReflectionInfo
     */
    public ExpressionContext(final PointcutType pointcutType,
                             final ReflectionInfo reflectionInfo,
                             final ReflectionInfo withinReflectionInfo) {
        if (pointcutType == null) {
            throw new IllegalArgumentException("pointcut type can not be null");
        }
        m_pointcutType = pointcutType;
        m_matchingReflectionInfo = reflectionInfo;
        if (withinReflectionInfo != null) {
            m_withinReflectionInfo = withinReflectionInfo;
        } else {
            if (PointcutType.EXECUTION.equals(pointcutType)
                || PointcutType.STATIC_INITIALIZATION.equals(pointcutType)
                || PointcutType.WITHIN.equals(pointcutType)) {
                m_withinReflectionInfo = m_matchingReflectionInfo;
            } else {
                m_withinReflectionInfo = null;
            }
        }
        if (reflectionInfo instanceof MethodInfo) {
            m_reflectionInfoType = METHOD_INFO;
        } else if (reflectionInfo instanceof ConstructorInfo) {
            m_reflectionInfoType = CONSTRUCTOR_INFO;
        } else if (reflectionInfo instanceof FieldInfo) {
            m_reflectionInfoType = FIELD_INFO;
        } else if (reflectionInfo instanceof ClassInfo) {
            m_reflectionInfoType = CLASS_INFO;
        } else if (reflectionInfo instanceof StaticInitializationInfo) {
        	m_reflectionInfoType = STATIC_INFO;
        } else {
            m_reflectionInfoType = INFO_NOT_AVAILABLE;// used for early eval on CALL
        }
    }

    public ReflectionInfo getReflectionInfo() {
        return m_matchingReflectionInfo;
    }

    public ReflectionInfo getWithinReflectionInfo() {
        return m_withinReflectionInfo;
    }

    public boolean hasExecutionPointcut() {
        return m_pointcutType.equals(PointcutType.EXECUTION);
    }

    public boolean hasCallPointcut() {
        return m_pointcutType.equals(PointcutType.CALL);
    }

    public boolean hasSetPointcut() {
        return m_pointcutType.equals(PointcutType.SET);
    }

    public boolean hasGetPointcut() {
        return m_pointcutType.equals(PointcutType.GET);
    }

    public boolean hasHandlerPointcut() {
        return m_pointcutType.equals(PointcutType.HANDLER);
    }

    public boolean hasStaticInitializationPointcut() {
        return m_pointcutType.equals(PointcutType.STATIC_INITIALIZATION);
    }

    public boolean hasWithinPointcut() {
        return m_pointcutType.equals(PointcutType.WITHIN);
    }
//
//    public boolean hasHasMethodPointcut() {
//        return m_pointcutType.equals(PointcutType.HAS_METHOD);
//    }
//
//    public boolean hasHasFieldPointcut() {
//        return m_pointcutType.equals(PointcutType.HAS_FIELD);
//    }

    public boolean hasWithinReflectionInfo() {
        return m_withinReflectionInfo != null;
    }

    public boolean hasMethodInfo() {
        return m_reflectionInfoType == METHOD_INFO;
    }

    public boolean hasConstructorInfo() {
        return m_reflectionInfoType == CONSTRUCTOR_INFO;
    }

    public boolean hasFieldInfo() {
        return m_reflectionInfoType == FIELD_INFO;
    }

    public boolean hasClassInfo() {
        return m_reflectionInfoType == CLASS_INFO;
    }

    public boolean hasReflectionInfo() {
        return m_reflectionInfoType != INFO_NOT_AVAILABLE;
    }

    public void setInCflowSubAST(final boolean inCflowAST) {
        m_inCflowSubAST = inCflowAST;
    }

    public boolean inCflowSubAST() {
        return m_inCflowSubAST;
    }

    public void setHasBeenVisitingCflow(final boolean hasBeenVisitingCflow) {
        m_hasBeenVisitingCflow = hasBeenVisitingCflow;
    }

    public boolean hasBeenVisitingCflow() {
        return m_hasBeenVisitingCflow;
    }

    public boolean getCflowEvaluation() {
        return m_cflowEvaluation;
    }

    public void setCflowEvaluation(boolean cflowEvaluation) {
        m_cflowEvaluation = cflowEvaluation;
    }

    public int getCurrentTargetArgsIndex() {
        return m_currentTargetArgsIndex;
    }

    public void setCurrentTargetArgsIndex(int argsIndex) {
        this.m_currentTargetArgsIndex = argsIndex;
    }

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof ExpressionContext)) {
            return false;
        }
        final ExpressionContext expressionContext = (ExpressionContext) o;
        if (m_reflectionInfoType != expressionContext.m_reflectionInfoType) {
            return false;
        }
        if (!m_matchingReflectionInfo.equals(expressionContext.m_matchingReflectionInfo)) {
            return false;
        }
        if (!m_pointcutType.equals(expressionContext.m_pointcutType)) {
            return false;
        }
        if ((m_withinReflectionInfo != null) ?
            (!m_withinReflectionInfo
                .equals(expressionContext.m_withinReflectionInfo)) :
            (expressionContext.m_withinReflectionInfo != null)) {
            return false;
        }
        return true;
    }

    public int hashCode() {
        int result;
        result = m_pointcutType.hashCode();
        result = (29 * result) + m_matchingReflectionInfo.hashCode();
        result = (29 * result) + ((m_withinReflectionInfo != null) ? m_withinReflectionInfo.hashCode() : 0);
        result = (29 * result) + m_reflectionInfoType;
        return result;
    }

    public PointcutType getPointcutType() {
        return m_pointcutType;
    }

    public void resetRuntimeState() {
        m_targetBoundedName = null;
        m_thisBoundedName = null;
        m_exprIndexToTargetIndex = new TObjectIntHashMap();
        m_targetWithRuntimeCheck = false;
    }
}