/* *******************************************************************
 * 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.io.IOException;
import java.util.List;
import java.util.Map;

import org.aspectj.util.FuzzyBoolean;
import org.aspectj.weaver.CompressingDataOutputStream;
import org.aspectj.weaver.ISourceContext;
import org.aspectj.weaver.IntMap;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.VersionedDataInputStream;
import org.aspectj.weaver.World;

/**
 * @author Adrian Colyer
 */
public class AnnotationPatternList extends PatternNode {

	private AnnotationTypePattern[] typePatterns;
	int ellipsisCount = 0;

	public static final AnnotationPatternList EMPTY = new AnnotationPatternList(AnnotationTypePattern.NONE);

	public static final AnnotationPatternList ANY = new AnnotationPatternList(
			new AnnotationTypePattern[] { AnnotationTypePattern.ELLIPSIS });

	public AnnotationPatternList() {
		typePatterns = AnnotationTypePattern.NONE;
		ellipsisCount = 0;
	}

	public AnnotationPatternList(AnnotationTypePattern[] arguments) {
		this.typePatterns = arguments;
		for (AnnotationTypePattern argument : arguments) {
			if (argument == AnnotationTypePattern.ELLIPSIS) {
				ellipsisCount++;
			}
		}
	}

	public AnnotationPatternList(List<AnnotationTypePattern> l) {
		this(l.toArray(AnnotationTypePattern.NONE));
	}

	protected AnnotationTypePattern[] getAnnotationPatterns() {
		return typePatterns;
	}

	public AnnotationPatternList parameterizeWith(Map<String,UnresolvedType> typeVariableMap, World w) {
		AnnotationTypePattern[] parameterizedPatterns = new AnnotationTypePattern[this.typePatterns.length];
		for (int i = 0; i < parameterizedPatterns.length; i++) {
			parameterizedPatterns[i] = this.typePatterns[i].parameterizeWith(typeVariableMap, w);
		}
		AnnotationPatternList ret = new AnnotationPatternList(parameterizedPatterns);
		ret.copyLocationFrom(this);
		return ret;
	}

	public void resolve(World inWorld) {
		for (AnnotationTypePattern typePattern : typePatterns) {
			typePattern.resolve(inWorld);
		}
	}

	public FuzzyBoolean matches(ResolvedType[] someArgs) {
		// do some quick length tests first
		int numArgsMatchedByEllipsis = (someArgs.length + ellipsisCount) - typePatterns.length;
		if (numArgsMatchedByEllipsis < 0) {
			return FuzzyBoolean.NO;
		}
		if ((numArgsMatchedByEllipsis > 0) && (ellipsisCount == 0)) {
			return FuzzyBoolean.NO;
		}
		// now work through the args and the patterns, skipping at ellipsis
		FuzzyBoolean ret = FuzzyBoolean.YES;
		int argsIndex = 0;
		for (AnnotationTypePattern typePattern : typePatterns) {
			if (typePattern == AnnotationTypePattern.ELLIPSIS) {
				// match ellipsisMatchCount args
				argsIndex += numArgsMatchedByEllipsis;
			} else if (typePattern == AnnotationTypePattern.ANY) {
				argsIndex++;
			} else {
				// match the argument type at argsIndex with the ExactAnnotationTypePattern
				// we know it is exact because nothing else is allowed in args
				if (someArgs[argsIndex].isPrimitiveType()) {
					return FuzzyBoolean.NO; // can never match
				}
				ExactAnnotationTypePattern ap = (ExactAnnotationTypePattern) typePattern;
				FuzzyBoolean matches = ap.matchesRuntimeType(someArgs[argsIndex]);
				if (matches == FuzzyBoolean.NO) {
					return FuzzyBoolean.MAYBE; // could still match at runtime
				} else {
					argsIndex++;
					ret = ret.and(matches);
				}
			}
		}
		return ret;
	}

	public int size() {
		return typePatterns.length;
	}

	public AnnotationTypePattern get(int index) {
		return typePatterns[index];
	}

	public AnnotationPatternList resolveBindings(IScope scope, Bindings bindings, boolean allowBinding) {
		for (int i = 0; i < typePatterns.length; i++) {
			AnnotationTypePattern p = typePatterns[i];
			if (p != null) {
				typePatterns[i] = typePatterns[i].resolveBindings(scope, bindings, allowBinding);
			}
		}
		return this;
	}

	public AnnotationPatternList resolveReferences(IntMap bindings) {
		int len = typePatterns.length;
		AnnotationTypePattern[] ret = new AnnotationTypePattern[len];
		for (int i = 0; i < len; i++) {
			ret[i] = typePatterns[i].remapAdviceFormals(bindings);
		}
		return new AnnotationPatternList(ret);
	}

	public String toString() {
		StringBuilder buf = new StringBuilder();
		buf.append("(");
		for (int i = 0, len = typePatterns.length; i < len; i++) {
			AnnotationTypePattern type = typePatterns[i];
			if (i > 0) {
				buf.append(", ");
			}
			if (type == AnnotationTypePattern.ELLIPSIS) {
				buf.append("..");
			} else {
				String annPatt = type.toString();
				buf.append(annPatt.startsWith("@") ? annPatt.substring(1) : annPatt);
			}
		}
		buf.append(")");
		return buf.toString();
	}

	public boolean equals(Object other) {
		if (!(other instanceof AnnotationPatternList)) {
			return false;
		}
		AnnotationPatternList o = (AnnotationPatternList) other;
		int len = o.typePatterns.length;
		if (len != this.typePatterns.length) {
			return false;
		}
		for (int i = 0; i < len; i++) {
			if (!this.typePatterns[i].equals(o.typePatterns[i])) {
				return false;
			}
		}
		return true;
	}

	public int hashCode() {
		int result = 41;
		for (AnnotationTypePattern typePattern : typePatterns) {
			result = 37 * result + typePattern.hashCode();
		}
		return result;
	}

	public static AnnotationPatternList read(VersionedDataInputStream s, ISourceContext context) throws IOException {
		short len = s.readShort();
		AnnotationTypePattern[] arguments = new AnnotationTypePattern[len];
		for (int i = 0; i < len; i++) {
			arguments[i] = AnnotationTypePattern.read(s, context);
		}
		AnnotationPatternList ret = new AnnotationPatternList(arguments);
		ret.readLocation(context, s);
		return ret;
	}

	public void write(CompressingDataOutputStream s) throws IOException {
		s.writeShort(typePatterns.length);
		for (AnnotationTypePattern typePattern : typePatterns) {
			typePattern.write(s);
		}
		writeLocation(s);
	}

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

	public Object traverse(PatternNodeVisitor visitor, Object data) {
		Object ret = accept(visitor, data);
		for (AnnotationTypePattern typePattern : typePatterns) {
			typePattern.traverse(visitor, ret);
		}
		return ret;
	}

}
