/* *******************************************************************
 * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
 * 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
 *
 * Contributors:
 *     PARC     initial implementation
 * ******************************************************************/

package org.aspectj.weaver;

import java.util.Map;

import org.aspectj.bridge.ISourceLocation;
import org.aspectj.util.PartialOrder;

public abstract class ConcreteTypeMunger implements PartialOrder.PartialComparable {
	protected ResolvedTypeMunger munger;
	protected ResolvedType aspectType;

	public ConcreteTypeMunger(ResolvedTypeMunger munger, ResolvedType aspectType) {
		this.munger = munger;
		this.aspectType = aspectType;
	}

	/**
	 * Equivalence can be true for an EclipseTypeMunger and a BcelTypeMunger that represent the same transformation (just at
	 * different points in the pipeline).
	 */
	public boolean equivalentTo(Object other) {
		if (!(other instanceof ConcreteTypeMunger)) {
			return false;
		}
		ConcreteTypeMunger o = (ConcreteTypeMunger) other;
		ResolvedTypeMunger otherTypeMunger = o.getMunger();
		ResolvedTypeMunger thisTypeMunger = getMunger();
		if (thisTypeMunger instanceof NewConstructorTypeMunger && otherTypeMunger instanceof NewConstructorTypeMunger) {
			return (((NewConstructorTypeMunger) otherTypeMunger).equivalentTo(thisTypeMunger))
					&& ((o.getAspectType() == null) ? (getAspectType() == null) : o.getAspectType().equals(getAspectType()));
		} else {
			return ((otherTypeMunger == null) ? (thisTypeMunger == null) : otherTypeMunger.equals(thisTypeMunger))
					&& ((o.getAspectType() == null) ? (getAspectType() == null) : o.getAspectType().equals(getAspectType()));
		}
	}

	// public abstract boolean munge(LazyClassGen gen);

	/**
	 * returns null for mungers that are used internally, but were not part of a declared thing in source code.
	 */
	public ResolvedTypeMunger getMunger() {
		return munger;
	}

	public ResolvedType getAspectType() {
		return aspectType;
	}

	public ResolvedMember getSignature() {
		return munger.getSignature();
	}

	public World getWorld() {
		return aspectType.getWorld();
	}

	public ISourceLocation getSourceLocation() {
		if (munger == null) {
			return null;
		}
		return munger.getSourceLocation(); // XXX
	}

	public boolean matches(ResolvedType onType) {
		if (munger == null) {
			throw new RuntimeException("huh: " + this);
		}
		return munger.matches(onType, aspectType);
	}

	public ResolvedMember getMatchingSyntheticMember(Member member) {
		return munger.getMatchingSyntheticMember(member, aspectType);
	}

	public int compareTo(Object other) {
		ConcreteTypeMunger o = (ConcreteTypeMunger) other;

		ResolvedType otherAspect = o.aspectType;

		if (aspectType.equals(otherAspect)) {
			return getSignature().getStart() < o.getSignature().getStart() ? -1 : +1;
		} else if (aspectType.isAssignableFrom(o.aspectType)) {
			return +1;
		} else if (o.aspectType.isAssignableFrom(aspectType)) {
			return -1;
		} else {
			return 0;
		}
	}

	public int fallbackCompareTo(Object other) {
		// ConcreteTypeMunger o = (ConcreteTypeMunger) other;
		return 0;
	}

	/**
	 * returns true if the ITD target type used type variables, for example I&lt;T&gt;. When they are specified like this, the ITDs
	 * 'share' type variables with the generic type. Usually this method is called because we need to know whether to tailor the
	 * munger for addition to a particular type. For example: <pre><code>
	 *   interface I&lt;T&gt; {}
	 *
	 *   aspect X implements I&lt;String&gt; {
	 *     List&lt;T&gt; I&lt;T&gt;.foo { return null; }
	 *   }
	 * </code></pre> In this case the munger matches X but it matches with the form <code>
	 *   List&lt;String&gt; foo() { return null; }
	 * </code>
	 */
	public boolean isTargetTypeParameterized() {
		if (munger == null) {
			return false;
		}
		return munger.sharesTypeVariablesWithGenericType();
	}

	/**
	 * For an ITD made on a generic type that shares type variables with that target type, this method will tailor the ITD for a
	 * particular usage of the generic type - either in its raw or parameterized form.
	 */
	public abstract ConcreteTypeMunger parameterizedFor(ResolvedType targetType);

	public boolean isLateMunger() {
		if (munger == null) {
			return false;
		}
		return munger.isLateMunger();
	}

	public abstract ConcreteTypeMunger parameterizeWith(Map<String, UnresolvedType> parameterizationMap, World world);

	/**
	 * Some type mungers are created purely to help with the implementation of shadow mungers. For example to support the cflow()
	 * pointcut we create a new cflow field in the aspect, and that is added via a BcelCflowCounterFieldAdder.
	 *
	 * During compilation we need to compare sets of type mungers, and if some only come into existence after the 'shadowy' type
	 * things have been processed, we need to ignore them during the comparison.
	 *
	 * Returning true from this method indicates the type munger exists to support 'shadowy' stuff - and so can be ignored in some
	 * comparison.
	 */
	public boolean existsToSupportShadowMunging() {
		if (munger != null) {
			return munger.existsToSupportShadowMunging();
		}
		return false;
	}

	public boolean shouldOverwrite() {
		return true;
	}
}
