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

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.aspectj.util.FuzzyBoolean;
import org.aspectj.weaver.Advice;
import org.aspectj.weaver.AjcMemberMaker;
import org.aspectj.weaver.CompressingDataOutputStream;
import org.aspectj.weaver.CrosscuttingMembers;
import org.aspectj.weaver.ISourceContext;
import org.aspectj.weaver.Member;
import org.aspectj.weaver.NameMangler;
import org.aspectj.weaver.ResolvedMemberImpl;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.Shadow;
import org.aspectj.weaver.ShadowMunger;
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.Test;

public class PerCflow extends PerClause {
	private final boolean isBelow;
	private final Pointcut entry;

	public PerCflow(Pointcut entry, boolean isBelow) {
		this.entry = entry;
		this.isBelow = isBelow;
	}

	// -----

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

	@Override
	public Object traverse(PatternNodeVisitor visitor, Object data) {
		Object ret = accept(visitor, data);
		if (this.entry != null)
			this.entry.traverse(visitor, ret);
		return ret;
	}

	public int couldMatchKinds() {
		return Shadow.ALL_SHADOW_KINDS_BITS;
	}

	public FuzzyBoolean fastMatch(FastMatchInfo type) {
		return FuzzyBoolean.MAYBE;
	}

	protected FuzzyBoolean matchInternal(Shadow shadow) {
		return FuzzyBoolean.YES;
	}

	public void resolveBindings(IScope scope, Bindings bindings) {
		// assert bindings == null;
		entry.resolve(scope);
	}

	public Pointcut parameterizeWith(Map<String,UnresolvedType> typeVariableMap, World w) {
		PerCflow ret = new PerCflow(entry.parameterizeWith(typeVariableMap, w), isBelow);
		ret.copyLocationFrom(this);
		return ret;
	}

	protected Test findResidueInternal(Shadow shadow, ExposedState state) {
		Expr myInstance = Expr.makeCallExpr(AjcMemberMaker.perCflowAspectOfMethod(inAspect), Expr.NONE, inAspect);
		state.setAspectInstance(myInstance);
		return Test.makeCall(AjcMemberMaker.perCflowHasAspectMethod(inAspect), Expr.NONE);
	}

	public PerClause concretize(ResolvedType inAspect) {
		PerCflow ret = new PerCflow(entry, isBelow);
		ret.inAspect = inAspect;
		if (inAspect.isAbstract()) {
			return ret;
		}

		Member cflowStackField = new ResolvedMemberImpl(Member.FIELD, inAspect, Modifier.PUBLIC | Modifier.STATIC,
				UnresolvedType.forName(NameMangler.CFLOW_STACK_TYPE), NameMangler.PERCFLOW_FIELD_NAME, UnresolvedType.NONE);

		World world = inAspect.getWorld();

		CrosscuttingMembers xcut = inAspect.crosscuttingMembers;

		Collection<ShadowMunger> previousCflowEntries = xcut.getCflowEntries();
		Pointcut concreteEntry = entry.concretize(inAspect, inAspect, 0, null);
		if (concreteEntry.getSourceLocation() == null) {
			concreteEntry.setLocation(sourceContext, start, end);
		}
		List<ShadowMunger> innerCflowEntries = new ArrayList<>(xcut.getCflowEntries());
		innerCflowEntries.removeAll(previousCflowEntries);

		xcut.addConcreteShadowMunger(Advice.makePerCflowEntry(world, concreteEntry, isBelow, cflowStackField, inAspect,
				innerCflowEntries));

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

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

		return ret;
	}

	public void write(CompressingDataOutputStream s) throws IOException {
		PERCFLOW.write(s);
		entry.write(s);
		s.writeBoolean(isBelow);
		writeLocation(s);
	}

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

	public PerClause.Kind getKind() {
		return PERCFLOW;
	}

	public Pointcut getEntry() {
		return entry;
	}

	public String toString() {
		return "percflow(" + inAspect + " on " + entry + ")";
	}

	public String toDeclarationString() {
		if (isBelow) {
			return "percflowbelow(" + entry + ")";
		}
		return "percflow(" + entry + ")";
	}

	public boolean equals(Object other) {
		if (!(other instanceof PerCflow)) {
			return false;
		}
		PerCflow pc = (PerCflow) other;
		return (pc.isBelow && isBelow) && ((pc.inAspect == null) ? (inAspect == null) : pc.inAspect.equals(inAspect))
				&& ((pc.entry == null) ? (entry == null) : pc.entry.equals(entry));
	}

	public int hashCode() {
		int result = 17;
		result = 37 * result + (isBelow ? 0 : 1);
		result = 37 * result + ((inAspect == null) ? 0 : inAspect.hashCode());
		result = 37 * result + ((entry == null) ? 0 : entry.hashCode());
		return result;
	}

}
