/* *******************************************************************
 * Copyright (c) 2008 Contributors
 * 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.MessageUtil;
import org.aspectj.util.FuzzyBoolean;
import org.aspectj.weaver.AnnotatedElement;
import org.aspectj.weaver.BCException;
import org.aspectj.weaver.CompressingDataOutputStream;
import org.aspectj.weaver.ISourceContext;
import org.aspectj.weaver.IntMap;
import org.aspectj.weaver.ReferenceType;
import org.aspectj.weaver.ResolvedMember;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.VersionedDataInputStream;
import org.aspectj.weaver.WeaverMessages;
import org.aspectj.weaver.World;

/**
 * Represents an attempt to bind the field of an annotation within a pointcut. For example:
 * <pre><code>
 * before(Level lev): execution(* *(..)) &amp;&amp; @annotation(TraceAnnotation(lev))
 * </code></pre>
 * <p>This binding annotation type pattern will be for 'lev'.</p>
 */
public class BindingAnnotationFieldTypePattern extends ExactAnnotationTypePattern implements BindingPattern {

	protected int formalIndex;
	UnresolvedType formalType; // In this construct the formal type differs from the annotation type

	public BindingAnnotationFieldTypePattern(UnresolvedType formalType, int formalIndex, UnresolvedType theAnnotationType) {
		super(theAnnotationType, null);
		this.formalIndex = formalIndex;
		this.formalType = formalType;
	}

	public void resolveBinding(World world) {
		if (resolved) {
			return;
		}
		resolved = true;
		formalType = world.resolve(formalType);
		annotationType = world.resolve(annotationType);
		ResolvedType annoType = (ResolvedType) annotationType;
		if (!annoType.isAnnotation()) {
			IMessage m = MessageUtil
					.error(WeaverMessages.format(WeaverMessages.REFERENCE_TO_NON_ANNOTATION_TYPE, annoType.getName()),
							getSourceLocation());
			world.getMessageHandler().handleMessage(m);
			resolved = false;
		}
	}

	@Override
	public AnnotationTypePattern parameterizeWith(Map typeVariableMap, World w) {
		throw new BCException("Parameterization not implemented for annotation field binding construct (compiler limitation)");
		// UnresolvedType newAnnotationType = annotationType;
		// if (annotationType.isTypeVariableReference()) {
		// TypeVariableReference t = (TypeVariableReference) annotationType;
		// String key = t.getTypeVariable().getName();
		// if (typeVariableMap.containsKey(key)) {
		// newAnnotationType = (UnresolvedType) typeVariableMap.get(key);
		// }
		// } else if (annotationType.isParameterizedType()) {
		// newAnnotationType = annotationType.parameterize(typeVariableMap);
		// }
		// BindingAnnotationTypePattern ret = new BindingAnnotationTypePattern(newAnnotationType, this.formalIndex);
		// if (newAnnotationType instanceof ResolvedType) {
		// ResolvedType rat = (ResolvedType) newAnnotationType;
		// verifyRuntimeRetention(rat.getWorld(), rat);
		// }
		// ret.copyLocationFrom(this);
		// return ret;
	}

	@Override
	public int getFormalIndex() {
		return formalIndex;
	}

	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof BindingAnnotationFieldTypePattern)) {
			return false;
		}
		BindingAnnotationFieldTypePattern btp = (BindingAnnotationFieldTypePattern) obj;
		return (btp.formalIndex == formalIndex) && (annotationType.equals(btp.annotationType))
				&& (formalType.equals(btp.formalType));
	}

	@Override
	public int hashCode() {
		return (annotationType.hashCode() * 37 + formalIndex * 37) + formalType.hashCode();
	}

	@Override
	public AnnotationTypePattern remapAdviceFormals(IntMap bindings) {
		if (!bindings.hasKey(formalIndex)) {
			throw new BCException("Annotation field binding reference must be bound (compiler limitation)");
			// must be something like returning the unbound form: return new ExactAnnotationTypePattern(annotationType,
			// null);
		} else {
			int newFormalIndex = bindings.get(formalIndex);
			BindingAnnotationFieldTypePattern baftp = new BindingAnnotationFieldTypePattern(formalType, newFormalIndex,
					annotationType);
			baftp.formalName = formalName;
			return baftp;
		}
	}

	@Override
	public void write(CompressingDataOutputStream s) throws IOException {
		s.writeByte(AnnotationTypePattern.BINDINGFIELD2);
		formalType.write(s); // the type of the field within the annotation
		s.writeShort((short) formalIndex);
		annotationType.write(s); // the annotation type
		s.writeUTF(formalName);
		writeLocation(s);
	}

	public static AnnotationTypePattern read(VersionedDataInputStream s, ISourceContext context) throws IOException {
		AnnotationTypePattern ret = new BindingAnnotationFieldTypePattern(UnresolvedType.read(s), s.readShort(),
				UnresolvedType.read(s));
		ret.readLocation(context, s);
		return ret;
	}

	public static AnnotationTypePattern read2(VersionedDataInputStream s, ISourceContext context) throws IOException {
		BindingAnnotationFieldTypePattern ret = new BindingAnnotationFieldTypePattern(UnresolvedType.read(s), s.readShort(),
				UnresolvedType.read(s));
		ret.formalName = s.readUTF();
		ret.readLocation(context, s);
		return ret;
	}

	@Override
	public FuzzyBoolean matches(AnnotatedElement annotated, ResolvedType[] parameterAnnotations) {
		// Inheritance irrelevant because @annotation(Anno(x)) only supported at method execution join points (compiler limitation)
		// boolean checkSupers = false;
		// if (getResolvedAnnotationType().hasAnnotation(UnresolvedType.AT_INHERITED)) {
		// if (annotated instanceof ResolvedType) {
		// checkSupers = true;
		// }
		// }
		//
		if (annotated.hasAnnotation(annotationType)) {
			if (annotationType instanceof ReferenceType) {
				ReferenceType rt = (ReferenceType) annotationType;
				if (rt.getRetentionPolicy() != null && rt.getRetentionPolicy().equals("SOURCE")) {
					rt.getWorld()
							.getMessageHandler()
							.handleMessage(
									MessageUtil.warn(WeaverMessages.format(WeaverMessages.NO_MATCH_BECAUSE_SOURCE_RETENTION,
											annotationType, annotated), getSourceLocation()));
					return FuzzyBoolean.NO;
				}
				ResolvedMember[] methods = rt.getDeclaredMethods();
				boolean found = false;
				for (int i = 0; i < methods.length && !found; i++) {
					if (methods[i].getReturnType().equals(formalType)) {
						found = true;
					}
				}
				return (found ? FuzzyBoolean.YES : FuzzyBoolean.NO);
			}
		}
		// else if (checkSupers) {
		// ResolvedType toMatchAgainst = ((ResolvedType) annotated).getSuperclass();
		// while (toMatchAgainst != null) {
		// if (toMatchAgainst.hasAnnotation(annotationType)) {
		// return FuzzyBoolean.YES;
		// }
		// toMatchAgainst = toMatchAgainst.getSuperclass();
		// }
		// }
		//
		return FuzzyBoolean.NO;
	}

	public UnresolvedType getFormalType() {
		return formalType;
	}

}
