/* *******************************************************************
 * Copyright (c) 2005 IBM
 * 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:
 *     Andy Clement     initial implementation
 * ******************************************************************/

package org.aspectj.weaver.patterns;

import java.io.IOException;
import java.util.Map;

import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.Message;
import org.aspectj.util.FuzzyBoolean;
import org.aspectj.weaver.Advice;
import org.aspectj.weaver.AjcMemberMaker;
import org.aspectj.weaver.CompressingDataOutputStream;
import org.aspectj.weaver.ISourceContext;
import org.aspectj.weaver.Member;
import org.aspectj.weaver.PerTypeWithinTargetTypeMunger;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.ResolvedTypeMunger;
import org.aspectj.weaver.Shadow;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.VersionedDataInputStream;
import org.aspectj.weaver.World;
import org.aspectj.weaver.ast.Expr;
import org.aspectj.weaver.ast.Literal;
import org.aspectj.weaver.ast.Test;

// PTWIMPL Represents a parsed pertypewithin()
public class PerTypeWithin extends PerClause {

	private TypePattern typePattern;

	// Any shadow could be considered within a pertypewithin() type pattern
	private static final int kindSet = Shadow.ALL_SHADOW_KINDS_BITS;

	public TypePattern getTypePattern() {
		return typePattern;
	}

	public PerTypeWithin(TypePattern p) {
		typePattern = p;
	}

	@Override
	public Object accept(PatternNodeVisitor visitor, Object data) {
		return visitor.visit(this, data);
	}

	@Override
	public int couldMatchKinds() {
		return kindSet;
	}

	@Override
	public Pointcut parameterizeWith(Map<String,UnresolvedType> typeVariableMap, World w) {
		PerTypeWithin ret = new PerTypeWithin(typePattern.parameterizeWith(typeVariableMap, w));
		ret.copyLocationFrom(this);
		return ret;
	}

	// -----
	@Override
	public FuzzyBoolean fastMatch(FastMatchInfo info) {
		if (typePattern.annotationPattern instanceof AnyAnnotationTypePattern) {
			return isWithinType(info.getType());
		}
		return FuzzyBoolean.MAYBE;
	}

	@Override
	protected FuzzyBoolean matchInternal(Shadow shadow) {
		ResolvedType enclosingType = shadow.getIWorld().resolve(shadow.getEnclosingType(), true);
		if (enclosingType.isMissing()) {
			// PTWIMPL ?? Add a proper message
			IMessage msg = new Message("Cant find type pertypewithin matching...", shadow.getSourceLocation(), true,
					new ISourceLocation[] { getSourceLocation() });
			shadow.getIWorld().getMessageHandler().handleMessage(msg);
		}

		// See pr106554 - we can't put advice calls in an interface when the
		// advice is defined
		// in a pertypewithin aspect - the JPs only exist in the static
		// initializer and can't
		// call the localAspectOf() method.
		if (enclosingType.isInterface()) {
			return FuzzyBoolean.NO;
		}
		if (!(enclosingType.canBeSeenBy(inAspect) || inAspect.isPrivilegedAspect())) {
			return FuzzyBoolean.NO;
		}

		typePattern.resolve(shadow.getIWorld());
		return isWithinType(enclosingType);
	}

	@Override
	public void resolveBindings(IScope scope, Bindings bindings) {
		typePattern = typePattern.resolveBindings(scope, bindings, false, false);
	}

	@Override
	protected Test findResidueInternal(Shadow shadow, ExposedState state) {
		// Member ptwField =
		// AjcMemberMaker.perTypeWithinField(shadow.getEnclosingType
		// (),inAspect);

		Expr myInstance = Expr.makeCallExpr(AjcMemberMaker.perTypeWithinLocalAspectOf(shadow.getEnclosingType(), inAspect/*
																														 * shadow.
																														 * getEnclosingType
																														 * ( )
																														 */),
				Expr.NONE, inAspect);
		state.setAspectInstance(myInstance);

		// this worked at one point
		// Expr myInstance =
		// Expr.makeFieldGet(ptwField,shadow.getEnclosingType()
		// .resolve(shadow.getIWorld()));//inAspect);
		// state.setAspectInstance(myInstance);

		// return Test.makeFieldGetCall(ptwField,null,Expr.NONE);
		// cflowField, cflowCounterIsValidMethod, Expr.NONE

		// This is what is in the perObject variant of this ...
		// Expr myInstance =
		// Expr.makeCallExpr(AjcMemberMaker.perTypeWithinAspectOfMethod(inAspect)
		// ,
		// new Expr[] {getVar(shadow)}, inAspect);
		// state.setAspectInstance(myInstance);
		// return
		// Test.makeCall(AjcMemberMaker.perTypeWithinHasAspectMethod(inAspect),
		// new Expr[] { getVar(shadow) });
		//

		return match(shadow).alwaysTrue() ? Literal.TRUE : Literal.FALSE;
	}

	@Override
	public PerClause concretize(ResolvedType inAspect) {
		PerTypeWithin ret = new PerTypeWithin(typePattern);
		ret.copyLocationFrom(this);
		ret.inAspect = inAspect;
		if (inAspect.isAbstract()) {
			return ret;
		}

		World world = inAspect.getWorld();

		SignaturePattern sigpat = new SignaturePattern(Member.STATIC_INITIALIZATION, ModifiersPattern.ANY, TypePattern.ANY,
				TypePattern.ANY,// typePattern,
				NamePattern.ANY, TypePatternList.ANY, ThrowsPattern.ANY, AnnotationTypePattern.ANY);

		Pointcut staticInitStar = new KindedPointcut(Shadow.StaticInitialization, sigpat);
		Pointcut withinTp = new WithinPointcut(typePattern);
		Pointcut andPcut = new AndPointcut(staticInitStar, withinTp);
		// We want the pointcut to be:
		// 'staticinitialization(*) && within(<typepattern>)' -
		// we *cannot* shortcut this to staticinitialization(<typepattern>)
		// because it doesnt mean the same thing.

		// This munger will initialize the aspect instance field in the matched type

		inAspect.crosscuttingMembers.addConcreteShadowMunger(Advice.makePerTypeWithinEntry(world, andPcut, inAspect));

		ResolvedTypeMunger munger = new PerTypeWithinTargetTypeMunger(inAspect, ret);
		inAspect.crosscuttingMembers.addTypeMunger(world.getWeavingSupport().concreteTypeMunger(munger, inAspect));

		// ATAJ: add a munger to add the aspectOf(..) to the @AJ aspects
		if (inAspect.isAnnotationStyleAspect() && !inAspect.isAbstract()) {
			inAspect.crosscuttingMembers.addLateTypeMunger(world.getWeavingSupport().makePerClauseAspect(inAspect, getKind()));
		}

		// ATAJ inline around advice support - don't use a late munger to allow
		// around inling for itself
		if (inAspect.isAnnotationStyleAspect() && !world.isXnoInline()) {
			inAspect.crosscuttingMembers.addTypeMunger(world.getWeavingSupport().createAccessForInlineMunger(inAspect));
		}

		return ret;

	}

	@Override
	public void write(CompressingDataOutputStream s) throws IOException {
		PERTYPEWITHIN.write(s);
		typePattern.write(s);
		writeLocation(s);
	}

	public static PerClause readPerClause(VersionedDataInputStream s, ISourceContext context) throws IOException {
		PerClause ret = new PerTypeWithin(TypePattern.read(s, context));
		ret.readLocation(context, s);
		return ret;
	}

	@Override
	public PerClause.Kind getKind() {
		return PERTYPEWITHIN;
	}

	@Override
	public String toString() {
		return "pertypewithin(" + typePattern + ")";
	}

	@Override
	public String toDeclarationString() {
		return toString();
	}

	private FuzzyBoolean isWithinType(ResolvedType type) {
		while (type != null) {
			if (typePattern.matchesStatically(type)) {
				return FuzzyBoolean.YES;
			}
			type = type.getDeclaringType();
		}
		return FuzzyBoolean.NO;
	}

	@Override
	public boolean equals(Object other) {
		if (!(other instanceof PerTypeWithin)) {
			return false;
		}
		PerTypeWithin pc = (PerTypeWithin) other;
		return ((pc.inAspect == null) ? (inAspect == null) : pc.inAspect.equals(inAspect))
				&& ((pc.typePattern == null) ? (typePattern == null) : pc.typePattern.equals(typePattern));
	}

	@Override
	public int hashCode() {
		int result = 17;
		result = 37 * result + ((inAspect == null) ? 0 : inAspect.hashCode());
		result = 37 * result + ((typePattern == null) ? 0 : typePattern.hashCode());
		return result;
	}

}
