/*******************************************************************************
 * Copyright (c) 2000, 2006 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
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.*;

/**
 * Syntactic representation of a reference to a generic type.
 * Note that it might also have a dimension.
 */
public class ParameterizedQualifiedTypeReference extends ArrayQualifiedTypeReference {

	public TypeReference[][] typeArguments;
	private boolean didResolve = false;

	/**
	 * @param tokens
	 * @param dim
	 * @param positions
	 */
	public ParameterizedQualifiedTypeReference(char[][] tokens, TypeReference[][] typeArguments, int dim, long[] positions) {
	    
		super(tokens, dim, positions);
		this.typeArguments = typeArguments;
	}
	public void checkBounds(Scope scope) {
		if (this.resolvedType == null) return;

		checkBounds(
			(ReferenceBinding) this.resolvedType.leafComponentType(),
			scope,
			this.typeArguments.length - 1);
	}
	public void checkBounds(ReferenceBinding type, Scope scope, int index) {
		// recurse on enclosing type if any, and assuming explictly  part of the reference (index>0)
		if (index > 0 &&  type.enclosingType() != null) {
			checkBounds(type.enclosingType(), scope, index - 1);
		}
		if (type.isParameterizedType()) {
			ParameterizedTypeBinding parameterizedType = (ParameterizedTypeBinding) type;
			ReferenceBinding currentType = parameterizedType.type;
			TypeVariableBinding[] typeVariables = currentType.typeVariables();
			TypeBinding[] argTypes = parameterizedType.arguments;
			if (argTypes != null && typeVariables != null) { // argTypes may be null in error cases
				parameterizedType.boundCheck(scope, this.typeArguments[index]);
			}
		}
	}
	public TypeReference copyDims(int dim){
		return new ParameterizedQualifiedTypeReference(this.tokens, this.typeArguments, dim, this.sourcePositions);
	}	
	
	/**
	 * @return char[][]
	 */
	public char [][] getParameterizedTypeName(){
		int length = this.tokens.length;
		char[][] qParamName = new char[length][];
		for (int i = 0; i < length; i++) {
			TypeReference[] arguments = this.typeArguments[i];
			if (arguments == null) {
				qParamName[i] = this.tokens[i];
			} else {
				StringBuffer buffer = new StringBuffer(5);
				buffer.append(this.tokens[i]);
				buffer.append('<');
				for (int j = 0, argLength =arguments.length; j < argLength; j++) {
					if (j > 0) buffer.append(',');
					buffer.append(CharOperation.concatWith(arguments[j].getParameterizedTypeName(), '.'));
				}
				buffer.append('>');
				int nameLength = buffer.length();
				qParamName[i] = new char[nameLength];
				buffer.getChars(0, nameLength, qParamName[i], 0);		
			}
		}
		int dim = this.dimensions;
		if (dim > 0) {
			char[] dimChars = new char[dim*2];
			for (int i = 0; i < dim; i++) {
				int index = i*2;
				dimChars[index] = '[';
				dimChars[index+1] = ']';
			}
			qParamName[length-1] = CharOperation.concat(qParamName[length-1], dimChars);
		}
		return qParamName;
	}	
	
	/* (non-Javadoc)
     * @see org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference#getTypeBinding(org.eclipse.jdt.internal.compiler.lookup.Scope)
     */
    protected TypeBinding getTypeBinding(Scope scope) {
        return null; // not supported here - combined with resolveType(...)
    }
    
    /*
     * No need to check for reference to raw type per construction
     */
	private TypeBinding internalResolveType(Scope scope, boolean checkBounds) {

		// handle the error here
		this.constant = Constant.NotAConstant;
		if (this.didResolve) { // is a shared type reference which was already resolved
			if (this.resolvedType != null && !this.resolvedType.isValidBinding())
				return null; // already reported error
			return this.resolvedType;
		} 
	    this.didResolve = true;
	    Binding binding = scope.getPackage(this.tokens);
	    if (binding != null && !binding.isValidBinding()) {
	    	this.resolvedType = (ReferenceBinding) binding;
			reportInvalidType(scope);
			return null;
		}

	    PackageBinding packageBinding = binding == null ? null : (PackageBinding) binding;
	    boolean isClassScope = scope.kind == Scope.CLASS_SCOPE;
		boolean typeIsConsistent = true;
		ReferenceBinding qualifiedType = null;
	    for (int i = packageBinding == null ? 0 : packageBinding.compoundName.length, max = this.tokens.length; i < max; i++) {
			findNextTypeBinding(i, scope, packageBinding);
			if (!(this.resolvedType.isValidBinding())) {
				reportInvalidType(scope);
				return null;
			}
			ReferenceBinding currentType = (ReferenceBinding) this.resolvedType;
			if (qualifiedType == null) {
				qualifiedType = currentType.enclosingType(); // if member type
				if (qualifiedType != null && (qualifiedType.isGenericType() || qualifiedType.isParameterizedType())) {
					qualifiedType = currentType.isStatic()
						? (ReferenceBinding) scope.environment().convertToRawType(qualifiedType)
						: scope.environment().convertToParameterizedType(qualifiedType);
				}
			}				
			if (typeIsConsistent && currentType.isStatic() && qualifiedType != null && (qualifiedType.isParameterizedType() || qualifiedType.isGenericType())) {
				scope.problemReporter().staticMemberOfParameterizedType(this, scope.environment().createParameterizedType((ReferenceBinding)currentType.erasure(), null, qualifiedType));
				typeIsConsistent = false;
			}			
			// check generic and arity
		    TypeReference[] args = this.typeArguments[i];
		    if (args != null) {
			    TypeReference keep = null;
			    if (isClassScope) {
			    	keep = ((ClassScope) scope).superTypeReference;
			    	((ClassScope) scope).superTypeReference = null;
			    }
				int argLength = args.length;
				TypeBinding[] argTypes = new TypeBinding[argLength];
				boolean argHasError = false;
				for (int j = 0; j < argLength; j++) {
				    TypeReference arg = args[j];
				    TypeBinding argType = isClassScope
						? arg.resolveTypeArgument((ClassScope) scope, currentType, j)
						: arg.resolveTypeArgument((BlockScope) scope, currentType, j);
					if (argType == null) {
						argHasError = true;
					} else {
						argTypes[j] = argType;
					}			    
				}				
				if (argHasError) {
					return null;
				}
				if (isClassScope) {
					((ClassScope) scope).superTypeReference = keep;
					if (((ClassScope) scope).detectHierarchyCycle(currentType, this, argTypes))
						return null;
				}

			    TypeVariableBinding[] typeVariables = currentType.typeVariables();
				if (typeVariables == Binding.NO_TYPE_VARIABLES) { // check generic
					scope.problemReporter().nonGenericTypeCannotBeParameterized(i, this, currentType, argTypes);
					return null;
				} else if (argLength != typeVariables.length) { // check arity
					scope.problemReporter().incorrectArityForParameterizedType(this, currentType, argTypes);
					return null;
				}
				// check parameterizing non-static member type of raw type
				if (typeIsConsistent && !currentType.isStatic() && qualifiedType != null && qualifiedType.isRawType()) {
					scope.problemReporter().rawMemberTypeCannotBeParameterized(
							this, scope.environment().createRawType((ReferenceBinding)currentType.erasure(), qualifiedType), argTypes);
					typeIsConsistent = false;				
				}
				ParameterizedTypeBinding parameterizedType = scope.environment().createParameterizedType((ReferenceBinding)currentType.erasure(), argTypes, qualifiedType);
				// check argument type compatibility
				if (checkBounds) // otherwise will do it in Scope.connectTypeVariables() or generic method resolution
					parameterizedType.boundCheck(scope, args);
				qualifiedType = parameterizedType;
		    } else {
				if (isClassScope)
					if (((ClassScope) scope).detectHierarchyCycle(currentType, this, null))
						return null;
				ReferenceBinding currentErasure = (ReferenceBinding)currentType.erasure();
				if (currentErasure.isGenericType()) {
	   			    if (typeIsConsistent && qualifiedType != null && qualifiedType.isParameterizedType()) {
						scope.problemReporter().parameterizedMemberTypeMissingArguments(this, scope.environment().createParameterizedType(currentErasure, null, qualifiedType));
						typeIsConsistent = false;
					}
	   			    qualifiedType = scope.environment().createRawType(currentErasure, qualifiedType); // raw type
				} else {
					qualifiedType = (qualifiedType != null && qualifiedType.isParameterizedType())
													? scope.environment().createParameterizedType(currentErasure, null, qualifiedType)
													: currentType;
				}
			}
		}
		this.resolvedType = qualifiedType;
		if (isTypeUseDeprecated(this.resolvedType, scope))
			reportDeprecatedType(scope);
		// array type ?
		if (this.dimensions > 0) {
			if (dimensions > 255)
				scope.problemReporter().tooManyDimensions(this);
			this.resolvedType = scope.createArrayType(this.resolvedType, dimensions);
		}
		return this.resolvedType;
	}
	
	public StringBuffer printExpression(int indent, StringBuffer output) {
		int length = tokens.length;
		for (int i = 0; i < length - 1; i++) {
			output.append(tokens[i]);
			TypeReference[] typeArgument = typeArguments[i];
			if (typeArgument != null) {
				output.append('<');
				int max = typeArgument.length - 1;
				for (int j = 0; j < max; j++) {
					typeArgument[j].print(0, output);
					output.append(", ");//$NON-NLS-1$
				}
				typeArgument[max].print(0, output);
				output.append('>');
			}
			output.append('.');
		}
		output.append(tokens[length - 1]);
		TypeReference[] typeArgument = typeArguments[length - 1];
		if (typeArgument != null) {
			output.append('<');
			int max = typeArgument.length - 1;
			for (int j = 0; j < max; j++) {
				typeArgument[j].print(0, output);
				output.append(", ");//$NON-NLS-1$
			}
			typeArgument[max].print(0, output);
			output.append('>');
		}
		if ((this.bits & IsVarArgs) != 0) {
			for (int i= 0 ; i < dimensions - 1; i++) {
				output.append("[]"); //$NON-NLS-1$
			}
			output.append("..."); //$NON-NLS-1$
		} else {
			for (int i= 0 ; i < dimensions; i++) {
				output.append("[]"); //$NON-NLS-1$
			}
		}
		return output;
	}	
	
	public TypeBinding resolveType(BlockScope scope, boolean checkBounds) {
	    return internalResolveType(scope, checkBounds);
	}	
	public TypeBinding resolveType(ClassScope scope) {
	    return internalResolveType(scope, false);
	}
	public void traverse(ASTVisitor visitor, BlockScope scope) {
		if (visitor.visit(this, scope)) {
			for (int i = 0, max = this.typeArguments.length; i < max; i++) {
				if (this.typeArguments[i] != null) {
					for (int j = 0, max2 = this.typeArguments[i].length; j < max2; j++) {
						this.typeArguments[i][j].traverse(visitor, scope);
					}
				}
			}
		}
		visitor.endVisit(this, scope);
	}
	
	public void traverse(ASTVisitor visitor, ClassScope scope) {
		if (visitor.visit(this, scope)) {
			for (int i = 0, max = this.typeArguments.length; i < max; i++) {
				if (this.typeArguments[i] != null) {
					for (int j = 0, max2 = this.typeArguments[i].length; j < max2; j++) {
						this.typeArguments[i][j].traverse(visitor, scope);
					}
				}
			}
		}
		visitor.endVisit(this, scope);
	}

}
