/*******************************************************************************
 * Copyright (c) 2000, 2011 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.eclipse.jdt.internal.compiler.lookup;

/**
 * Context used during type inference for a generic method invocation
 */
public class InferenceContext {

	private TypeBinding[][][] collectedSubstitutes;
	MethodBinding genericMethod;
	int depth;
	int status;
	TypeBinding expectedType;
	boolean hasExplicitExpectedType; // indicates whether the expectedType (if set) was explicit in code, or set by default
    public boolean isUnchecked;
	TypeBinding[] substitutes;
	final static int FAILED = 1;

public InferenceContext(MethodBinding genericMethod) {
	this.genericMethod = genericMethod;
	TypeVariableBinding[] typeVariables = genericMethod.typeVariables;
	int varLength = typeVariables.length;
	this.collectedSubstitutes = new TypeBinding[varLength][3][];
	this.substitutes = new TypeBinding[varLength];
}

public TypeBinding[] getSubstitutes(TypeVariableBinding typeVariable, int constraint) {
	return this.collectedSubstitutes[typeVariable.rank][constraint];
}

/**
 * Returns true if any unresolved variable is detected, i.e. any variable is substituted with itself
 */
public boolean hasUnresolvedTypeArgument() {
	for (int i = 0, varLength = this.substitutes.length; i <varLength; i++) {
		if (this.substitutes[i] == null) {
			return true;
		}
	}
	return false;
}

public void recordSubstitute(TypeVariableBinding typeVariable, TypeBinding actualType, int constraint) {
    TypeBinding[][] variableSubstitutes = this.collectedSubstitutes[typeVariable.rank];
    insertLoop: {
    	TypeBinding[] constraintSubstitutes = variableSubstitutes[constraint];
    	int length;
    	if (constraintSubstitutes == null) {
    		length = 0;
    		constraintSubstitutes = new TypeBinding[1];
    	} else {
    		length = constraintSubstitutes.length;
	        for (int i = 0; i < length; i++) {
	        	TypeBinding substitute = constraintSubstitutes[i];
	            if (substitute == actualType) return; // already there //$IDENTITY-COMPARISON$
	            if (substitute == null) {
	                constraintSubstitutes[i] = actualType;
	                break insertLoop;
	            }
	        }
	        // no free spot found, need to grow by one
	        System.arraycopy(constraintSubstitutes, 0, constraintSubstitutes = new TypeBinding[length+1], 0, length);
    	}
        constraintSubstitutes[length] = actualType;
        variableSubstitutes[constraint] = constraintSubstitutes;
    }
}
@Override
public String toString() {
	StringBuilder buffer = new StringBuilder(20);
	buffer.append("InferenceContex for ");//$NON-NLS-1$
	for (int i = 0, length = this.genericMethod.typeVariables.length; i < length; i++) {
		buffer.append(this.genericMethod.typeVariables[i]);
	}
	buffer.append(this.genericMethod);
	buffer.append("\n\t[status=");//$NON-NLS-1$
	switch(this.status) {
		case 0 :
			buffer.append("ok]");//$NON-NLS-1$
			break;
		case FAILED :
			buffer.append("failed]");//$NON-NLS-1$
			break;
	}
	if (this.expectedType == null) {
		buffer.append(" [expectedType=null]"); //$NON-NLS-1$
	} else {
		buffer.append(" [expectedType=").append(this.expectedType.shortReadableName()).append(']'); //$NON-NLS-1$
	}
	buffer.append(" [depth=").append(this.depth).append(']'); //$NON-NLS-1$
	buffer.append("\n\t[collected={");//$NON-NLS-1$
	for (int i = 0, length = this.collectedSubstitutes == null ? 0 : this.collectedSubstitutes.length; i < length; i++) {
		TypeBinding[][] collected = this.collectedSubstitutes[i];
		for (int j = TypeConstants.CONSTRAINT_EQUAL; j <= TypeConstants.CONSTRAINT_SUPER; j++) {
			TypeBinding[] constraintCollected = collected[j];
			if (constraintCollected != null) {
				for (int k = 0, clength = constraintCollected.length; k < clength; k++) {
					buffer.append("\n\t\t").append(this.genericMethod.typeVariables[i].sourceName); //$NON-NLS-1$
					switch (j) {
						case TypeConstants.CONSTRAINT_EQUAL :
							buffer.append("="); //$NON-NLS-1$
							break;
						case TypeConstants.CONSTRAINT_EXTENDS :
							buffer.append("<:"); //$NON-NLS-1$
							break;
						case TypeConstants.CONSTRAINT_SUPER :
							buffer.append(">:"); //$NON-NLS-1$
							break;
					}
					if (constraintCollected[k] != null) {
						buffer.append(constraintCollected[k].shortReadableName());
					}
				}
			}
		}
	}
	buffer.append("}]");//$NON-NLS-1$
	buffer.append("\n\t[inferred=");//$NON-NLS-1$
	int count = 0;
	for (int i = 0, length = this.substitutes == null ? 0 : this.substitutes.length; i < length; i++) {
		if (this.substitutes[i] == null) continue;
		count++;
		buffer.append('{').append(this.genericMethod.typeVariables[i].sourceName);
		buffer.append("=").append(this.substitutes[i].shortReadableName()).append('}'); //$NON-NLS-1$
	}
	if (count == 0) buffer.append("{}"); //$NON-NLS-1$
	buffer.append(']');
	return buffer.toString();
}
}
