/*******************************************************************************
 * Copyright (c) 2000, 2014 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.aspectj.org.eclipse.jdt.internal.core.search.matching;

import java.io.IOException;

import org.aspectj.org.eclipse.jdt.core.BindingKey;
import org.aspectj.org.eclipse.jdt.core.Flags;
import org.aspectj.org.eclipse.jdt.core.IMethod;
import org.aspectj.org.eclipse.jdt.core.JavaModelException;
import org.aspectj.org.eclipse.jdt.core.compiler.CharOperation;
import org.aspectj.org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.aspectj.org.eclipse.jdt.core.search.SearchPattern;
import org.aspectj.org.eclipse.jdt.internal.compiler.ExtraFlags;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.aspectj.org.eclipse.jdt.internal.core.index.EntryResult;
import org.aspectj.org.eclipse.jdt.internal.core.index.Index;
import org.aspectj.org.eclipse.jdt.internal.core.util.Util;

public class ConstructorPattern extends JavaSearchPattern {

protected boolean findDeclarations = true;
protected boolean findReferences = true;

public char[] declaringQualification;
public char[] declaringSimpleName;

public char[][] parameterQualifications;
public char[][] parameterSimpleNames;
public int parameterCount;
public boolean varargs = false;

// Signatures and arguments for generic search
char[][][] parametersTypeSignatures;
char[][][][] parametersTypeArguments;
boolean constructorParameters = false;
char[][] constructorArguments;

protected static char[][] REF_CATEGORIES = { CONSTRUCTOR_REF };
protected static char[][] REF_AND_DECL_CATEGORIES = { CONSTRUCTOR_REF, CONSTRUCTOR_DECL };
protected static char[][] DECL_CATEGORIES = { CONSTRUCTOR_DECL };

public final static int FINE_GRAIN_MASK =
	IJavaSearchConstants.SUPER_REFERENCE |
	IJavaSearchConstants.QUALIFIED_REFERENCE |
	IJavaSearchConstants.THIS_REFERENCE |
	IJavaSearchConstants.IMPLICIT_THIS_REFERENCE |
	IJavaSearchConstants.METHOD_REFERENCE_EXPRESSION;


/**
 * Constructor entries are encoded as described
 * 
 * Binary constructor for class
 * TypeName '/' Arity '/' TypeModifers '/' PackageName '/' Signature '/' ParameterNamesopt '/' Modifiers
 * Source constructor for class
 * TypeName '/' Arity '/' TypeModifers '/' PackageName '/' ParameterTypes '/' ParameterNamesopt '/' Modifiers
 * Constructor with 0 arity for class
 * TypeName '/' 0 '/' TypeModifers '/' PackageName '/' Modifiers
 * Constructor for enum, interface (annotation) and class with default constructor
 * TypeName '/' # '/' TypeModifers '/' PackageName
 * Constructor for member type
 * TypeName '/' Arity '/' TypeModifers
 * 
 * TypeModifiers contains some encoded extra information
 * 		{@link ExtraFlags#IsMemberType}
 * 		{@link ExtraFlags#HasNonPrivateStaticMemberTypes}
 * 		{@link ExtraFlags#ParameterTypesStoredAsSignature}
 */
public static char[] createDeclarationIndexKey(
		char[] typeName,
		int argCount,
		char[] signature,
		char[][] parameterTypes,
		char[][] parameterNames,
		int modifiers,
		char[] packageName,
		int typeModifiers,
		int extraFlags) {
	
	char[] countChars;
	char[] parameterTypesChars = null;
	char[] parameterNamesChars = null;
	
	if (argCount < 0) {
		countChars = DEFAULT_CONSTRUCTOR;
	} else {
		countChars = argCount < 10
		? COUNTS[argCount]
		: ("/" + String.valueOf(argCount)).toCharArray(); //$NON-NLS-1$
		
		if (argCount > 0) {
			if (signature == null) {
				if (parameterTypes != null && parameterTypes.length == argCount) {
					char[][] parameterTypeErasures = new char[argCount][];
					for (int i = 0; i < parameterTypes.length; i++) {
						parameterTypeErasures[i] = getTypeErasure(parameterTypes[i]);
					}
					parameterTypesChars = CharOperation.concatWith(parameterTypeErasures, PARAMETER_SEPARATOR);
				}
			} else {
				extraFlags |= ExtraFlags.ParameterTypesStoredAsSignature;
			}
			
			if (parameterNames != null && parameterNames.length == argCount) {
				parameterNamesChars = CharOperation.concatWith(parameterNames, PARAMETER_SEPARATOR);
			}
		}
	}
	
	boolean isMemberType = (extraFlags & ExtraFlags.IsMemberType) != 0;
	
	int typeNameLength = typeName == null ? 0 : typeName.length;
	int packageNameLength = packageName == null ? 0 : packageName.length;
	int countCharsLength = countChars.length;
	int parameterTypesLength = signature == null ? (parameterTypesChars == null ? 0 : parameterTypesChars.length): signature.length;
	int parameterNamesLength = parameterNamesChars == null ? 0 : parameterNamesChars.length;
	
	int resultLength = typeNameLength + countCharsLength + 3; // SEPARATOR=1 + TypeModifers=2
	if (!isMemberType) {
		resultLength += packageNameLength + 1; // SEPARATOR=1
		if (argCount >= 0) {
			resultLength += 3; // SEPARATOR=1 + Modifiers=2
		}
		
		if (argCount > 0) {
			resultLength += parameterTypesLength + parameterNamesLength + 2; //SEPARATOR=1 + SEPARATOR=1
		}
	}
	
	char[] result = new char[resultLength];
	
	int pos = 0;
	if (typeNameLength > 0) {
		System.arraycopy(typeName, 0, result, pos, typeNameLength);
		pos += typeNameLength;
	}
	
	if (countCharsLength > 0) {
		System.arraycopy(countChars, 0, result, pos, countCharsLength);
		pos += countCharsLength;
	}
	
	int typeModifiersWithExtraFlags = typeModifiers | encodeExtraFlags(extraFlags);
	result[pos++] = SEPARATOR;
	result[pos++] = (char) typeModifiersWithExtraFlags;
	result[pos++] = (char) (typeModifiersWithExtraFlags>>16);
	
	if (!isMemberType) {
		result[pos++] = SEPARATOR;
		if (packageNameLength > 0) {
			System.arraycopy(packageName, 0, result, pos, packageNameLength);
			pos += packageNameLength;
		}
		
		if (argCount == 0) {
			result[pos++] = SEPARATOR;
			result[pos++] = (char) modifiers;
			result[pos++] = (char) (modifiers>>16);
		} else if (argCount > 0) {
			result[pos++] = SEPARATOR;
			if (parameterTypesLength > 0) {
				if (signature == null) {
					System.arraycopy(parameterTypesChars, 0, result, pos, parameterTypesLength);
				} else {
					System.arraycopy(CharOperation.replaceOnCopy(signature, SEPARATOR, '\\'), 0, result, pos, parameterTypesLength);
				}
				pos += parameterTypesLength;
			}
			
			result[pos++] = SEPARATOR;
			if (parameterNamesLength > 0) {
				System.arraycopy(parameterNamesChars, 0, result, pos, parameterNamesLength);
				pos += parameterNamesLength;
			}
			
			result[pos++] = SEPARATOR;
			result[pos++] = (char) modifiers;
			result[pos++] = (char) (modifiers>>16);
		}
		
	}
	
	return result;
}
public static char[] createDefaultDeclarationIndexKey(
		char[] typeName,
		char[] packageName,
		int typeModifiers,
		int extraFlags) {
	return createDeclarationIndexKey(
			typeName,
			-1, // used to identify default constructor
			null,
			null,
			null,
			0, //
			packageName,
			typeModifiers,
			extraFlags);
}

/**
 * Constructor entries are encoded as TypeName '/' Arity:
 * e.g. 'X/0'
 */
public static char[] createIndexKey(char[] typeName, int argCount) {
	char[] countChars = argCount < 10
		? COUNTS[argCount]
		: ("/" + String.valueOf(argCount)).toCharArray(); //$NON-NLS-1$
	return CharOperation.concat(typeName, countChars);
}
static int decodeExtraFlags(int modifiersWithExtraFlags) {
	int extraFlags = 0;
	
	if ((modifiersWithExtraFlags & ASTNode.Bit28) != 0) {
		extraFlags |= ExtraFlags.ParameterTypesStoredAsSignature;
	}
	
	if ((modifiersWithExtraFlags & ASTNode.Bit29) != 0) {
		extraFlags |= ExtraFlags.IsLocalType;
	}
	
	if ((modifiersWithExtraFlags & ASTNode.Bit30) != 0) {
		extraFlags |= ExtraFlags.IsMemberType;
	}
	
	if ((modifiersWithExtraFlags & ASTNode.Bit31) != 0) {
		extraFlags |= ExtraFlags.HasNonPrivateStaticMemberTypes;
	}
	
	return extraFlags;
}
static int decodeModifers(int modifiersWithExtraFlags) {
	return modifiersWithExtraFlags & ~(ASTNode.Bit31 | ASTNode.Bit30 | ASTNode.Bit29 | ASTNode.Bit28);
}
private static int encodeExtraFlags(int extraFlags) {
	int encodedExtraFlags = 0;
	
	if ((extraFlags & ExtraFlags.ParameterTypesStoredAsSignature) != 0) {
		encodedExtraFlags |= ASTNode.Bit28;
	}
	
	if ((extraFlags & ExtraFlags.IsLocalType) != 0) {
		encodedExtraFlags |= ASTNode.Bit29;
	}
	
	if ((extraFlags & ExtraFlags.IsMemberType) != 0) {
		encodedExtraFlags |= ASTNode.Bit30;
	}
	if ((extraFlags & ExtraFlags.HasNonPrivateStaticMemberTypes) != 0) {
		encodedExtraFlags |= ASTNode.Bit31;
	}
	
	return encodedExtraFlags;
}
private static char[] getTypeErasure(char[] typeName) {
	int index;
	if ((index = CharOperation.indexOf('<', typeName)) == -1) return typeName;
	
	int length = typeName.length;
	char[] typeErasurename = new char[length - 2];
	
	System.arraycopy(typeName, 0, typeErasurename, 0, index);
	
	int depth = 1;
	for (int i = index + 1; i < length; i++) {
		switch (typeName[i]) {
			case '<':
				depth++;
				break;
			case '>':
				depth--;
				break;
			default:
				if (depth == 0) {
					typeErasurename[index++] = typeName[i];
				}
				break;
		}
	}
	
	System.arraycopy(typeErasurename, 0, typeErasurename = new char[index], 0, index);
	return typeErasurename;
}
ConstructorPattern(int matchRule) {
	super(CONSTRUCTOR_PATTERN, matchRule);
}
public ConstructorPattern(
	char[] declaringSimpleName,
	char[] declaringQualification,
	char[][] parameterQualifications,
	char[][] parameterSimpleNames,
	int limitTo,
	int matchRule) {

	this(matchRule);

	this.fineGrain = limitTo & FINE_GRAIN_MASK;
    if (this.fineGrain == 0) {
		switch (limitTo) {
			case IJavaSearchConstants.DECLARATIONS :
				this.findReferences = false;
				break;
			case IJavaSearchConstants.REFERENCES :
				this.findDeclarations = false;
				break;
			case IJavaSearchConstants.ALL_OCCURRENCES :
				break;
		}
    } else {
		this.findDeclarations = false;
    }

	this.declaringQualification = this.isCaseSensitive ? declaringQualification : CharOperation.toLowerCase(declaringQualification);
	this.declaringSimpleName = (this.isCaseSensitive || this.isCamelCase) ? declaringSimpleName : CharOperation.toLowerCase(declaringSimpleName);
	if (parameterSimpleNames != null) {
		this.parameterCount = parameterSimpleNames.length;
		boolean synthetic = this.parameterCount>0 && declaringQualification != null && CharOperation.equals(CharOperation.concat(parameterQualifications[0], parameterSimpleNames[0], '.'), declaringQualification);
		int offset = 0;
		if (synthetic) {
			// skip first synthetic parameter
			this.parameterCount--;
			offset++;
		}
		this.parameterQualifications = new char[this.parameterCount][];
		this.parameterSimpleNames = new char[this.parameterCount][];
		for (int i = 0; i < this.parameterCount; i++) {
			this.parameterQualifications[i] = this.isCaseSensitive ? parameterQualifications[i+offset] : CharOperation.toLowerCase(parameterQualifications[i+offset]);
			this.parameterSimpleNames[i] = this.isCaseSensitive ? parameterSimpleNames[i+offset] : CharOperation.toLowerCase(parameterSimpleNames[i+offset]);
		}
	} else {
		this.parameterCount = -1;
	}
	this.mustResolve = mustResolve();
}
/*
 * Instantiate a method pattern with signatures for generics search
 */
public ConstructorPattern(
	char[] declaringSimpleName,
	char[] declaringQualification,
	char[][] parameterQualifications,
	char[][] parameterSimpleNames,
	String[] parameterSignatures,
	IMethod method,
	int limitTo,
	int matchRule) {

	this(declaringSimpleName,
		declaringQualification,
		parameterQualifications,
		parameterSimpleNames,
		limitTo,
		matchRule);

	// Set flags
	try {
		this.varargs = (method.getFlags() & Flags.AccVarargs) != 0;
	} catch (JavaModelException e) {
		// do nothing
	}

	// Get unique key for parameterized constructors
	String genericDeclaringTypeSignature = null;
	if (method.isResolved()) {
		String key = method.getKey();
		BindingKey bindingKey = new BindingKey(key);
		if (bindingKey.isParameterizedType()) {
			genericDeclaringTypeSignature = Util.getDeclaringTypeSignature(key);
			// Store type signature and arguments for declaring type
			if (genericDeclaringTypeSignature != null) {
					this.typeSignatures = Util.splitTypeLevelsSignature(genericDeclaringTypeSignature);
					setTypeArguments(Util.getAllTypeArguments(this.typeSignatures));
			}
		}
	} else {
		this.constructorParameters = true;
		storeTypeSignaturesAndArguments(method.getDeclaringType());
	}

	// store type signatures and arguments for method parameters type
	if (parameterSignatures != null) {
		int length = parameterSignatures.length;
		if (length > 0) {
			this.parametersTypeSignatures = new char[length][][];
			this.parametersTypeArguments = new char[length][][][];
			for (int i=0; i<length; i++) {
				this.parametersTypeSignatures[i] = Util.splitTypeLevelsSignature(parameterSignatures[i]);
				this.parametersTypeArguments[i] = Util.getAllTypeArguments(this.parametersTypeSignatures[i]);
			}
		}
	}

	// Store type signatures and arguments for method
	this.constructorArguments = extractMethodArguments(method);
	if (hasConstructorArguments())  this.mustResolve = true;
}
/*
 * Instantiate a method pattern with signatures for generics search
 */
public ConstructorPattern(
	char[] declaringSimpleName,
	char[] declaringQualification,
	String declaringSignature,
	char[][] parameterQualifications,
	char[][] parameterSimpleNames,
	String[] parameterSignatures,
	char[][] arguments,
	int limitTo,
	int matchRule) {

	this(declaringSimpleName,
		declaringQualification,
		parameterQualifications,
		parameterSimpleNames,
		limitTo,
		matchRule);

	// Store type signature and arguments for declaring type
	if (declaringSignature != null) {
		this.typeSignatures = Util.splitTypeLevelsSignature(declaringSignature);
		setTypeArguments(Util.getAllTypeArguments(this.typeSignatures));
	}

	// Store type signatures and arguments for method parameters type
	if (parameterSignatures != null) {
		int length = parameterSignatures.length;
		if (length > 0) {
			this.parametersTypeSignatures = new char[length][][];
			this.parametersTypeArguments = new char[length][][][];
			for (int i=0; i<length; i++) {
				this.parametersTypeSignatures[i] = Util.splitTypeLevelsSignature(parameterSignatures[i]);
				this.parametersTypeArguments[i] = Util.getAllTypeArguments(this.parametersTypeSignatures[i]);
			}
		}
	}

	// Store type signatures and arguments for method
	this.constructorArguments = arguments;
	if (arguments  == null || arguments.length == 0) {
		if (getTypeArguments() != null && getTypeArguments().length > 0) {
			this.constructorArguments = getTypeArguments()[0];
		}
	}
	if (hasConstructorArguments())  this.mustResolve = true;
}

@Override
public void decodeIndexKey(char[] key) {
	int last = key.length - 1;
	int slash = CharOperation.indexOf(SEPARATOR, key, 0);
	this.declaringSimpleName = CharOperation.subarray(key, 0, slash);
	
	int start = slash + 1;
	slash = CharOperation.indexOf(SEPARATOR, key, start);
	if (slash != -1) {
		last = slash - 1;
	}
	
	boolean isDefaultConstructor = key[last] == '#';
	if (isDefaultConstructor) {
		this.parameterCount = -1;
	} else {
		this.parameterCount = 0;
		int power = 1;
		for (int i = last; i >= start; i--) {
			if (i == last) {
				this.parameterCount = key[i] - '0';
			} else {
				power *= 10;
				this.parameterCount += power * (key[i] - '0');
			}
		}
	}
}
@Override
public SearchPattern getBlankPattern() {
	return new ConstructorPattern(R_EXACT_MATCH | R_CASE_SENSITIVE);
}
@Override
public char[][] getIndexCategories() {
	if (this.findReferences)
		return this.findDeclarations ? REF_AND_DECL_CATEGORIES : REF_CATEGORIES;
	if (this.findDeclarations)
		return DECL_CATEGORIES;
	return CharOperation.NO_CHAR_CHAR;
}
boolean hasConstructorArguments() {
	return this.constructorArguments != null && this.constructorArguments.length > 0;
}
boolean hasConstructorParameters() {
	return this.constructorParameters;
}
@Override
public boolean matchesDecodedKey(SearchPattern decodedPattern) {
	ConstructorPattern pattern = (ConstructorPattern) decodedPattern;

	return pattern.parameterCount != -1
		&& (this.parameterCount == pattern.parameterCount || this.parameterCount == -1 || this.varargs)
		&& matchesName(this.declaringSimpleName, pattern.declaringSimpleName);
}
protected boolean mustResolve() {
	if (this.declaringQualification != null) return true;

	// parameter types
	if (this.parameterSimpleNames != null)
		for (int i = 0, max = this.parameterSimpleNames.length; i < max; i++)
			if (this.parameterQualifications[i] != null) return true;
	return this.findReferences; // need to check resolved default constructors and explicit constructor calls
}
@Override
public EntryResult[] queryIn(Index index) throws IOException {
	char[] key = this.declaringSimpleName; // can be null
	int matchRule = getMatchRule();

	switch(getMatchMode()) {
		case R_EXACT_MATCH :
			if (this.declaringSimpleName != null && this.parameterCount >= 0 && !this.varargs) {
				key = createIndexKey(this.declaringSimpleName, this.parameterCount);
			}
			matchRule &= ~R_EXACT_MATCH;
			matchRule |= R_PREFIX_MATCH;
			break;
		case R_PREFIX_MATCH :
			// do a prefix query with the declaringSimpleName
			break;
		case R_PATTERN_MATCH :
			if (this.parameterCount >= 0 && !this.varargs) {
				key = CharOperation.concat(createIndexKey(this.declaringSimpleName == null ? ONE_STAR : this.declaringSimpleName, this.parameterCount), ONE_STAR);
			} else if (this.declaringSimpleName != null && this.declaringSimpleName[this.declaringSimpleName.length - 1] != '*') {
				key = CharOperation.concat(this.declaringSimpleName, ONE_STAR, SEPARATOR);
			} else if (key != null){
				key = CharOperation.concat(key, ONE_STAR);
			}
			// else do a pattern query with just the declaringSimpleName
			break;
		case R_REGEXP_MATCH :
			// TODO (frederic) implement regular expression match
			break;
		case R_CAMELCASE_MATCH:
		case R_CAMELCASE_SAME_PART_COUNT_MATCH:
			// do a prefix query with the declaringSimpleName
			break;
	}

	return index.query(getIndexCategories(), key, matchRule); // match rule is irrelevant when the key is null
}
@Override
protected StringBuffer print(StringBuffer output) {
	if (this.findDeclarations) {
		output.append(this.findReferences
			? "ConstructorCombinedPattern: " //$NON-NLS-1$
			: "ConstructorDeclarationPattern: "); //$NON-NLS-1$
	} else {
		output.append("ConstructorReferencePattern: "); //$NON-NLS-1$
	}
	if (this.declaringQualification != null)
		output.append(this.declaringQualification).append('.');
	if (this.declaringSimpleName != null)
		output.append(this.declaringSimpleName);
	else if (this.declaringQualification != null)
		output.append("*"); //$NON-NLS-1$

	output.append('(');
	if (this.parameterSimpleNames == null) {
		output.append("..."); //$NON-NLS-1$
	} else {
		for (int i = 0, max = this.parameterSimpleNames.length; i < max; i++) {
			if (i > 0) output.append(", "); //$NON-NLS-1$
			if (this.parameterQualifications[i] != null) output.append(this.parameterQualifications[i]).append('.');
			if (this.parameterSimpleNames[i] == null) output.append('*'); else output.append(this.parameterSimpleNames[i]);
		}
	}
	output.append(')');
	return super.print(output);
}
}
