/* *******************************************************************
 * Copyright (c) 2004 IBM Corporation.
 * All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * ******************************************************************/
package org.aspectj.weaver.patterns;

import java.util.Comparator;

import org.aspectj.weaver.Shadow;

public class PointcutEvaluationExpenseComparator implements Comparator<Pointcut> {

	private static final int MATCHES_NOTHING = -1;
	private static final int WITHIN = 1;
	private static final int ATWITHIN = 2;
	private static final int STATICINIT = 3;
	private static final int ADVICEEXECUTION = 4;
	private static final int HANDLER = 5;
	private static final int GET_OR_SET = 6;
	private static final int WITHINCODE = 7;
	private static final int ATWITHINCODE = 8;
	private static final int EXE_INIT_PREINIT = 9;
	private static final int CALL_WITH_DECLARING_TYPE = 10;
	private static final int THIS_OR_TARGET = 11;
	private static final int CALL_WITHOUT_DECLARING_TYPE = 12;
	private static final int ANNOTATION = 13;
	private static final int AT_THIS_OR_TARGET = 14;
	private static final int ARGS = 15;
	private static final int AT_ARGS = 16;
	private static final int CFLOW = 17;
	private static final int IF = 18;
	private static final int OTHER = 20;

	/**
	 * Compare 2 pointcuts based on an estimate of how expensive they may be to evaluate.
	 *
	 * within
	 *
	 * {@literal @}within staticinitialization [make sure this has a fast match method] adviceexecution handler get, set withincode
	 * {@literal @}withincode execution, initialization, preinitialization call
	 * {@literal @}annotation this, target
	 * {@literal @}this, {@literal @}target args
	 * {@literal @}args cflow, cflowbelow if
	 */
	public int compare(Pointcut p1, Pointcut p2) {
		// important property for a well-defined comparator
		if (p1.equals(p2)) {
			return 0;
		}
		int result = getScore(p1) - getScore(p2);
		if (result == 0) {
			// they have the same evaluation expense, but are not 'equal'
			// sort by hashCode
			int p1code = p1.hashCode();
			int p2code = p2.hashCode();
			if (p1code == p2code) {
				return 0;
			} else if (p1code < p2code) {
				return -1;
			} else {
				return +1;
			}
		}
		return result;
	}

	// a higher score means a more expensive evaluation
	private int getScore(Pointcut p) {
		if (p.couldMatchKinds() == Shadow.NO_SHADOW_KINDS_BITS) {
			return MATCHES_NOTHING;
		}
		if (p instanceof WithinPointcut) {
			return WITHIN;
		}
		if (p instanceof WithinAnnotationPointcut) {
			return ATWITHIN;
		}
		if (p instanceof KindedPointcut) {
			KindedPointcut kp = (KindedPointcut) p;
			Shadow.Kind kind = kp.getKind();
			if (kind == Shadow.AdviceExecution) {
				return ADVICEEXECUTION;
			} else if ((kind == Shadow.ConstructorCall) || (kind == Shadow.MethodCall)) {
				TypePattern declaringTypePattern = kp.getSignature().getDeclaringType();
				if (declaringTypePattern instanceof AnyTypePattern) {
					return CALL_WITHOUT_DECLARING_TYPE;
				} else {
					return CALL_WITH_DECLARING_TYPE;
				}
			} else if ((kind == Shadow.ConstructorExecution) || (kind == Shadow.MethodExecution) || (kind == Shadow.Initialization)
					|| (kind == Shadow.PreInitialization)) {
				return EXE_INIT_PREINIT;
			} else if (kind == Shadow.ExceptionHandler) {
				return HANDLER;
			} else if ((kind == Shadow.FieldGet) || (kind == Shadow.FieldSet)) {
				return GET_OR_SET;
			} else if (kind == Shadow.StaticInitialization) {
				return STATICINIT;
			} else {
				return OTHER;
			}
		}
		if (p instanceof AnnotationPointcut) {
			return ANNOTATION;
		}
		if (p instanceof ArgsPointcut) {
			return ARGS;
		}
		if (p instanceof ArgsAnnotationPointcut) {
			return AT_ARGS;
		}
		if (p instanceof CflowPointcut || p instanceof ConcreteCflowPointcut) {
			return CFLOW;
		}
		if (p instanceof HandlerPointcut) {
			return HANDLER;
		}
		if (p instanceof IfPointcut) {
			return IF;
		}
		if (p instanceof ThisOrTargetPointcut) {
			return THIS_OR_TARGET;
		}
		if (p instanceof ThisOrTargetAnnotationPointcut) {
			return AT_THIS_OR_TARGET;
		}
		if (p instanceof WithincodePointcut) {
			return WITHINCODE;
		}
		if (p instanceof WithinCodeAnnotationPointcut) {
			return ATWITHINCODE;
		}
		if (p instanceof NotPointcut) {
			return getScore(((NotPointcut) p).getNegatedPointcut());
		}
		if (p instanceof AndPointcut) {
			int leftScore = getScore(((AndPointcut) p).getLeft());
			int rightScore = getScore(((AndPointcut) p).getRight());
			if (leftScore < rightScore) {
				return leftScore;
			} else {
				return rightScore;
			}
		}
		if (p instanceof OrPointcut) {
			int leftScore = getScore(((OrPointcut) p).getLeft());
			int rightScore = getScore(((OrPointcut) p).getRight());
			if (leftScore > rightScore) {
				return leftScore;
			} else {
				return rightScore;
			}
		}
		return OTHER;
	}
}
