/*******************************************************************************
 * Copyright (c) 2000, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 
 *******************************************************************************/
package org.eclipse.dltk.internal.core.search.matching;

import java.io.IOException;

import org.eclipse.dltk.compiler.CharOperation;
import org.eclipse.dltk.core.search.SearchPattern;
import org.eclipse.dltk.core.search.index.EntryResult;
import org.eclipse.dltk.core.search.index.Index;
import org.eclipse.dltk.core.search.indexing.IIndexConstants;

public class SuperTypeReferencePattern extends DLTKSearchPattern {

	public char[] superQualification;
	public char[] superSimpleName;
	public char superClassOrInterface;

	// set to CLASS_SUFFIX for only matching classes
	// set to INTERFACE_SUFFIX for only matching interfaces
	// set to TYPE_SUFFIX for matching both classes and interfaces
	public char typeSuffix;
	public char[] pkgName;
	public char[] simpleName;
	public char[] enclosingTypeName;
	public char classOrInterface;
	public int modifiers;
	public char[][] typeParameterSignatures;

	protected int superRefKind;
	public static final int ALL_SUPER_TYPES = 0;
	public static final int ONLY_SUPER_INTERFACES = 1; // used for IMPLEMENTORS
	public static final int ONLY_SUPER_CLASSES = 2; // used for hierarchy with a
													// class focus

	protected static char[][] CATEGORIES = { IIndexConstants.SUPER_REF };

	public static char[] createIndexKey(int modifiers, char[] packageName,
			char[] typeName, char[][] enclosingTypeNames,
			char[][] typeParameterSignatures, char classOrInterface,
			char[] superTypeName, char superClassOrInterface) {

		if (superTypeName == null)
			superTypeName = "(base object)".toCharArray();
		char[] superSimpleName = CharOperation.lastSegment(superTypeName, '.');
		char[] superQualification = null;
		if (superSimpleName != superTypeName) {
			int length = superTypeName.length - superSimpleName.length - 1;
			superQualification = new char[length];
			System.arraycopy(superTypeName, 0, superQualification, 0, length);
		}

		// if the supertype name contains a $, then split it into: source name
		// and append the $ prefix to the qualification
		// e.g. p.A$B ---> p.A$ + B
		char[] superTypeSourceName = CharOperation.lastSegment(superSimpleName,
				'$');
		if (superTypeSourceName != superSimpleName) {
			int start = superQualification == null ? 0
					: superQualification.length + 1;
			int prefixLength = superSimpleName.length
					- superTypeSourceName.length;
			char[] mangledQualification = new char[start + prefixLength];
			if (superQualification != null) {
				System.arraycopy(superQualification, 0, mangledQualification,
						0, start - 1);
				mangledQualification[start - 1] = '.';
			}
			System.arraycopy(superSimpleName, 0, mangledQualification, start,
					prefixLength);
			superQualification = mangledQualification;
			superSimpleName = superTypeSourceName;
		}

		char[] simpleName = CharOperation.lastSegment(typeName, '.');
		char[] enclosingTypeName = CharOperation.concatWith(enclosingTypeNames,
				'$');
		if (superQualification != null
				&& CharOperation.equals(superQualification, packageName))
			packageName = IIndexConstants.ONE_ZERO; // save some space

		char[] typeParameters = CharOperation.NO_CHAR;
		int typeParametersLength = 0;
		if (typeParameterSignatures != null) {
			StringBuffer buffer = new StringBuffer();
			for (int i = 0, length = typeParameterSignatures.length; i < length; i++) {
				char[] typeParameter = typeParameterSignatures[i];
				buffer.append(typeParameter);
				typeParametersLength += typeParameter.length;
				if (i != length - 1) {
					buffer.append(',');
					typeParametersLength++;
				}
			}
			typeParameters = new char[typeParametersLength];
			buffer.getChars(0, typeParametersLength, typeParameters, 0);
		}

		// superSimpleName / superQualification / simpleName / enclosingTypeName
		// / typeParameters / packageName / superClassOrInterface
		// classOrInterface modifiers
		int superLength = superSimpleName == null ? 0 : superSimpleName.length;
		int superQLength = superQualification == null ? 0
				: superQualification.length;
		int simpleLength = simpleName == null ? 0 : simpleName.length;
		int enclosingLength = enclosingTypeName == null ? 0
				: enclosingTypeName.length;
		int packageLength = packageName == null ? 0 : packageName.length;
		char[] result = new char[superLength + superQLength + simpleLength
				+ enclosingLength + typeParametersLength + packageLength + 9];
		int pos = 0;
		if (superLength > 0) {
			System.arraycopy(superSimpleName, 0, result, pos, superLength);
			pos += superLength;
		}
		result[pos++] = IIndexConstants.SEPARATOR;
		if (superQLength > 0) {
			System.arraycopy(superQualification, 0, result, pos, superQLength);
			pos += superQLength;
		}
		result[pos++] = IIndexConstants.SEPARATOR;
		if (simpleLength > 0) {
			System.arraycopy(simpleName, 0, result, pos, simpleLength);
			pos += simpleLength;
		}
		result[pos++] = IIndexConstants.SEPARATOR;
		if (enclosingLength > 0) {
			System
					.arraycopy(enclosingTypeName, 0, result, pos,
							enclosingLength);
			pos += enclosingLength;
		}
		result[pos++] = IIndexConstants.SEPARATOR;
		if (typeParametersLength > 0) {
			System.arraycopy(typeParameters, 0, result, pos,
					typeParametersLength);
			pos += typeParametersLength;
		}
		result[pos++] = IIndexConstants.SEPARATOR;
		if (packageLength > 0) {
			System.arraycopy(packageName, 0, result, pos, packageLength);
			pos += packageLength;
		}
		result[pos++] = IIndexConstants.SEPARATOR;
		result[pos++] = superClassOrInterface;
		result[pos++] = classOrInterface;
		result[pos] = (char) modifiers;
		return result;
	}

	public SuperTypeReferencePattern(char[] superQualification,
			char[] superSimpleName, int superRefKind, int matchRule) {

		this(matchRule);

		this.superQualification = isCaseSensitive() ? superQualification
				: CharOperation.toLowerCase(superQualification);
		this.superSimpleName = (isCaseSensitive() || isCamelCase()) ? superSimpleName
				: CharOperation.toLowerCase(superSimpleName);
//		((InternalSearchPattern) this).mustResolve = superQualification != null;
		this.superRefKind = superRefKind;
	}

	public SuperTypeReferencePattern(char[] superQualification,
			char[] superSimpleName, int superRefKind, char typeSuffix,
			int matchRule) {

		this(superQualification, superSimpleName, superRefKind, matchRule);
		this.typeSuffix = typeSuffix;
//		((InternalSearchPattern) this).mustResolve = superQualification != null
//				|| typeSuffix != TYPE_SUFFIX;
	}

	SuperTypeReferencePattern(int matchRule) {
		super(IIndexConstants.SUPER_REF_PATTERN, matchRule);
	}

	/*
	 * superSimpleName / superQualification / simpleName / enclosingTypeName /
	 * typeParameters / pkgName / superClassOrInterface classOrInterface
	 * modifiers
	 */
	public void decodeIndexKey(char[] key) {
		int slash = CharOperation.indexOf(IIndexConstants.SEPARATOR, key, 0);
		this.superSimpleName = CharOperation.subarray(key, 0, slash);

		// some values may not have been know when indexed so decode as null
		int start = slash + 1;
		slash = CharOperation.indexOf(IIndexConstants.SEPARATOR, key, start);
		this.superQualification = slash == start ? null : CharOperation
				.subarray(key, start, slash);

		slash = CharOperation.indexOf(IIndexConstants.SEPARATOR, key, start = slash + 1);
		this.simpleName = CharOperation.subarray(key, start, slash);

		start = ++slash;
		if (key[start] == IIndexConstants.SEPARATOR) {
			this.enclosingTypeName = null;
		} else {
			slash = CharOperation.indexOf(IIndexConstants.SEPARATOR, key, start);
			if (slash == (start + 1) && key[start] == IIndexConstants.ZERO_CHAR) {
				this.enclosingTypeName = IIndexConstants.ONE_ZERO;
			} else {
				char[] names = CharOperation.subarray(key, start, slash);
				this.enclosingTypeName = names;
			}
		}

		start = ++slash;
		if (key[start] == IIndexConstants.SEPARATOR) {
			this.typeParameterSignatures = null;
		} else {
			slash = CharOperation.indexOf(IIndexConstants.SEPARATOR, key, start);
			this.typeParameterSignatures = CharOperation.splitOn(',', key,
					start, slash);
		}

		start = ++slash;
		if (key[start] == IIndexConstants.SEPARATOR) {
			this.pkgName = null;
		} else {
			slash = CharOperation.indexOf(IIndexConstants.SEPARATOR, key, start);
			if (slash == (start + 1) && key[start] == IIndexConstants.ZERO_CHAR) {
				this.pkgName = this.superQualification;
			} else {
				char[] names = CharOperation.subarray(key, start, slash);
				this.pkgName = names;
			}
		}

		this.superClassOrInterface = key[slash + 1];
		this.classOrInterface = key[slash + 2];
		this.modifiers = key[slash + 3]; // implicit cast to int type
	}

	public SearchPattern getBlankPattern() {
		return new SuperTypeReferencePattern(R_EXACT_MATCH | R_CASE_SENSITIVE);
	}

	public char[][] getIndexCategories() {
		return CATEGORIES;
	}

	public boolean matchesDecodedKey(SearchPattern decodedPattern) {
		SuperTypeReferencePattern pattern = (SuperTypeReferencePattern) decodedPattern;
		if (this.superRefKind == ONLY_SUPER_CLASSES
				&& pattern.enclosingTypeName != IIndexConstants.ONE_ZERO/* not an anonymous */)
			// consider enumerations as classes, reject interfaces and
			// annotations
			if (pattern.superClassOrInterface == IIndexConstants.ANNOTATION_TYPE_SUFFIX)
				return false;

		if (pattern.superQualification != null)
			if (!matchesName(this.superQualification,
					pattern.superQualification))
				return false;

		return matchesName(this.superSimpleName, pattern.superSimpleName);
	}

	public EntryResult[] queryIn(Index index) throws IOException {
		char[] key = this.superSimpleName; // can be null
		int matchRule = getMatchRule();

		// cannot include the superQualification since it may not exist in the
		// index
		switch (getMatchMode()) {
		case R_EXACT_MATCH:
			if (this.isCamelCase)
				break;
			// do a prefix query with the superSimpleName
			matchRule &= ~R_EXACT_MATCH;
			matchRule |= R_PREFIX_MATCH;
			if (this.superSimpleName != null)
				key = CharOperation.append(this.superSimpleName, IIndexConstants.SEPARATOR);
			break;
		case R_PREFIX_MATCH:
			// do a prefix query with the superSimpleName
			break;
		case R_PATTERN_MATCH:
			// do a pattern query with the superSimpleName
			break;
		case R_REGEXP_MATCH:
			// TODO (frederic) implement regular expression match
			break;
		}

		return index.query(getIndexCategories(), key, matchRule); // match
																	// rule is
																	// irrelevant
																	// when the
																	// key is
																	// null
	}

	protected StringBuffer print(StringBuffer output) {
		switch (this.superRefKind) {
		case ALL_SUPER_TYPES:
			output.append("SuperTypeReferencePattern: <"); //$NON-NLS-1$
			break;
		case ONLY_SUPER_INTERFACES:
			output.append("SuperInterfaceReferencePattern: <"); //$NON-NLS-1$
			break;
		case ONLY_SUPER_CLASSES:
			output.append("SuperClassReferencePattern: <"); //$NON-NLS-1$
			break;
		}
		if (superSimpleName != null)
			output.append(superSimpleName);
		else
			output.append("*"); //$NON-NLS-1$
		output.append(">"); //$NON-NLS-1$
		return super.print(output);
	}
}
