/*******************************************************************************
 * Copyright (c) 2005, 2008 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:
 *     Rational Software - initial implementation
 *     Markus Schorn (Wind River Systems)
 *******************************************************************************/
package org.eclipse.cdt.core.dom.ast;

import java.util.LinkedList;

import org.eclipse.cdt.core.dom.IName;
import org.eclipse.cdt.core.dom.ast.c.ICArrayType;
import org.eclipse.cdt.core.dom.ast.c.ICBasicType;
import org.eclipse.cdt.core.dom.ast.c.ICPointerType;
import org.eclipse.cdt.core.dom.ast.c.ICQualifierType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBasicType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBlockScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespaceScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPReferenceType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateTemplateParameter;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateTypeParameter;
import org.eclipse.cdt.core.dom.ast.gnu.cpp.IGPPBasicType;
import org.eclipse.cdt.core.dom.ast.gnu.cpp.IGPPPointerType;
import org.eclipse.cdt.core.dom.ast.gnu.cpp.IGPPQualifierType;
import org.eclipse.cdt.core.parser.GCCKeywords;
import org.eclipse.cdt.core.parser.Keywords;
import org.eclipse.cdt.core.parser.util.ArrayUtil;
import org.eclipse.cdt.internal.core.dom.parser.ITypeContainer;
import org.eclipse.cdt.internal.core.dom.parser.c.CASTTypeId;
import org.eclipse.cdt.internal.core.dom.parser.c.CVisitor;
import org.eclipse.cdt.internal.core.dom.parser.c.ICInternalBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTTypeId;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;

/**
 * This is a utility class to help convert AST elements to Strings corresponding to the
 * AST element's type.
 * 
 * @author dsteffle
 */
public class ASTTypeUtil {
	private static final String COMMA_SPACE = ", "; //$NON-NLS-1$
	private static final String EMPTY_STRING = ""; //$NON-NLS-1$
	private static final String SPACE = " "; //$NON-NLS-1$
	private static final String[] EMPTY_STRING_ARRAY = new String[0];
	private static final int DEAULT_ITYPE_SIZE = 2;

	/**
	 * Returns a String representation of the parameter type of an IFunctionType.
	 * 
	 * This function calls ASTTypeUtil#getParameterTypeStringArray(IFunctionType) and wraps the
	 * results in "()" with a comma separated list.
	 * 
	 * @param type
	 * @return the representation of the parameter type of an IFunctionType
	 */
	public static String getParameterTypeString(IFunctionType type) {
		StringBuilder result = new StringBuilder();
		String[] parms = getParameterTypeStringArray(type);
		
		result.append(Keywords.cpLPAREN);
		for (int i = 0; i < parms.length; i++) {
			if (parms[i] != null) {
				result.append(parms[i]);
				if (i < parms.length - 1)
					result.append(COMMA_SPACE);
			}
		}
		result.append(Keywords.cpRPAREN);
		return result.toString();
	}
	
	/**
	 * Returns a String representation of the type array as a
	 * comma-separated list.
	 * @param types
	 * @return representation of the type array as a comma-separated list 
	 */
	public static String getTypeListString(IType[] types) {
		StringBuilder result = new StringBuilder();
		for (int i = 0; i < types.length; i++) {
			if (types[i] != null) {
				result.append(getTypeString(types[i]));
				if (i < types.length - 1)
					result.append(COMMA_SPACE);
			}
		}
		return result.toString();
	}

	/**
	 * Returns String[] corresponding to the types of the parameters for the IFunctionType.
	 * 
	 * @param type
	 * @return the types of the parameters for the IFunctionType
	 */
	public static String[] getParameterTypeStringArray(IFunctionType type) {
		IType[] parms = null;
		try {
			parms = type.getParameterTypes();
		} catch (DOMException e) {
			return EMPTY_STRING_ARRAY;
		}
		
		String[] result = new String[parms.length];
		
		for (int i = 0; i < parms.length; i++) {
			if (parms[i] != null) {
				result[i] = getType(parms[i]);
			}
		}
		
		return result;
	}
	
	private static String getTypeString(IType type) {
		StringBuilder result = new StringBuilder();
		boolean needSpace = false;
		
		if (type instanceof IArrayType) {
			result.append(Keywords.cpLBRACKET);
			if (type instanceof ICArrayType) {
				try {
					if (((ICArrayType) type).isConst()) {
						result.append(Keywords.CONST); needSpace = true;
					}
					if (((ICArrayType) type).isRestrict()) {
						if (needSpace) {
							result.append(SPACE); needSpace = false;
						}
						result.append(Keywords.RESTRICT); needSpace = true;
					}
					if (((ICArrayType) type).isStatic()) {
						if (needSpace) {
							result.append(SPACE); needSpace = false;
						}
						result.append(Keywords.STATIC); needSpace = true;
					}
					if (((ICArrayType) type).isVolatile()) {
						if (needSpace) {
							result.append(SPACE); needSpace = false;
						}
						result.append(Keywords.VOLATILE);
					}
				} catch (DOMException e) {
				}
			}
			result.append(Keywords.cpRBRACKET);
		} else if (type instanceof IBasicType) {
			try {
				if (((IBasicType) type).isSigned()) {
					result.append(Keywords.SIGNED); needSpace = true;
				} else if (((IBasicType) type).isUnsigned()) {
					if (needSpace) {
						result.append(SPACE); needSpace = false;
					}
					result.append(Keywords.UNSIGNED); needSpace = true;
				}
				if (((IBasicType) type).isLong()) {
					if (needSpace) {
						result.append(SPACE); needSpace = false;
					}
					result.append(Keywords.LONG); needSpace = true;
				} else if (((IBasicType) type).isShort()) {
					if (needSpace) {
						result.append(SPACE); needSpace = false;
					}
					result.append(Keywords.SHORT); needSpace = true;
				}
			} catch (DOMException e) {
			}
			
			if (type instanceof IGPPBasicType) {
				try {
					if (((IGPPBasicType) type).isLongLong()) {
						if (needSpace) {
							result.append(SPACE); needSpace = false;
						}
						result.append(Keywords.LONG_LONG); needSpace = true;
					}
					if (((IGPPBasicType) type).isComplex()) {
						if (needSpace) {
							result.append(SPACE); needSpace = false;
						}
						result.append(Keywords.c_COMPLEX); needSpace = true;
					}
					if (((IGPPBasicType) type).isImaginary()) {
						if (needSpace) {
							result.append(SPACE); needSpace = false;
						}
						result.append(Keywords.c_IMAGINARY); needSpace = true;
					}

					switch (((IGPPBasicType) type).getType()) {
						case IGPPBasicType.t_typeof:
							result.append(GCCKeywords.TYPEOF);
							break;						
					}
				} catch (DOMException e) {
				}
			} else if (type instanceof ICPPBasicType) {
				try {
					switch (((ICPPBasicType) type).getType()) {
						case ICPPBasicType.t_bool:
							result.append(Keywords.BOOL);
							break;
						case ICPPBasicType.t_wchar_t:
							result.append(Keywords.WCHAR_T);
							break;
					}
				} catch (DOMException e) {
				}
			} else if (type instanceof ICBasicType) {
				try {
					if (((ICBasicType) type).isComplex()) {
						if (needSpace) {
							result.append(SPACE); needSpace = false;
						}
						result.append(Keywords.c_COMPLEX); needSpace = true;
					}
					if (((ICBasicType) type).isImaginary()) {
						if (needSpace) {
							result.append(SPACE); needSpace = false;
						}
						result.append(Keywords.c_IMAGINARY); needSpace = true;
					}
					
					switch (((ICBasicType) type).getType()) {
						case ICBasicType.t_Bool:
							result.append(Keywords.c_BOOL);
							break;
					}
				} catch (DOMException e) {
				}
			}
			
			try {
				switch (((IBasicType) type).getType()) {
					case IBasicType.t_char:
						if (needSpace) result.append(SPACE);
						result.append(Keywords.CHAR);
						break;
					case IBasicType.t_double:
						if (needSpace) result.append(SPACE);
						result.append(Keywords.DOUBLE);
						break;
					case IBasicType.t_float:
						if (needSpace) result.append(SPACE);
						result.append(Keywords.FLOAT);
						break;
					case IBasicType.t_int:
						if (needSpace) result.append(SPACE);
						result.append(Keywords.INT);
						break;
					case IBasicType.t_void:
						if (needSpace) result.append(SPACE);
						result.append(Keywords.VOID);
						break;
				}
			} catch (DOMException e) {
			}
		} else if (type instanceof ICompositeType) {
//			101114 fix, do not display class, and for consistency don't display struct/union as well
//			if (type instanceof ICPPClassType) {
//				try {
//					switch(((ICPPClassType) type).getKey()) {
//						case ICPPClassType.k_class:
//							result.append(Keywords.CLASS);
//							break;
//					}
//				} catch (DOMException e) {}
//			}
//			
//			try {
//				switch(((ICompositeType) type).getKey()) {
//					case ICompositeType.k_struct:
//						result.append(Keywords.STRUCT);
//						break;
//					case ICompositeType.k_union:
//						result.append(Keywords.UNION);
//						break;
//				}
//			} catch (DOMException e) {}
//			result.append(SPACE);
			if (type instanceof ICPPClassType) {
				try {
					String qn = CPPVisitor.renderQualifiedName(getQualifiedNameForAnonymous((ICPPClassType) type));
					result.append(qn);
				} catch (DOMException e) {
					result.append(getNameForAnonymous((ICompositeType) type));
				}
			} else {
				result.append(getNameForAnonymous((ICompositeType) type));
			}
		} else if (type instanceof ICPPReferenceType) {
			result.append(Keywords.cpAMPER);
		} else if (type instanceof ICPPTemplateTypeParameter) {
			result.append(((ICPPTemplateTypeParameter) type).getName());
		} else if (type instanceof ICPPTemplateTemplateParameter) {
			result.append(((ICPPTemplateTemplateParameter) type).getName());
		} else if (type instanceof IEnumeration) {
			result.append(Keywords.ENUM);
			result.append(SPACE);
			result.append(getNameForAnonymous((IEnumeration) type));
		} else if (type instanceof IFunctionType) {
			try {
				String temp = getType(((IFunctionType) type).getReturnType());
				if (temp != null && !temp.equals(EMPTY_STRING)) {
					result.append(temp); needSpace = true;
				}
				if (needSpace) {
					result.append(SPACE); needSpace = false;
				}
				temp = getParameterTypeString((IFunctionType) type);
				if (temp != null && !temp.equals(EMPTY_STRING)) {
					result.append(temp); needSpace = false;
				}
			} catch (DOMException e) {
			}
		} else if (type instanceof IPointerType) {
			result.append(Keywords.cpSTAR); needSpace = true;
			
			if (type instanceof IGPPPointerType) {
				if (((IGPPPointerType) type).isRestrict()) {
					if (needSpace) {
						result.append(SPACE); needSpace = false;
					}
					result.append(Keywords.RESTRICT); needSpace = true;
				}
			} else if (type instanceof ICPointerType) {
				if (((ICPointerType) type).isRestrict()) {
					if (needSpace) {
						result.append(SPACE); needSpace = false;
					}
					result.append(Keywords.RESTRICT); needSpace = true;
				}
			}
			
			if (((IPointerType) type).isConst()) {
				if (needSpace) {
					result.append(SPACE); needSpace = false;
				}
				result.append(Keywords.CONST); needSpace = true;
			}
			if (((IPointerType) type).isVolatile()) {
				if (needSpace) {
					result.append(SPACE); needSpace = false;
				}
				result.append(Keywords.VOLATILE); needSpace = true;
			}
		} else if (type instanceof IQualifierType) {
			if (type instanceof ICQualifierType) {
				if (((ICQualifierType) type).isRestrict()) {
					result.append(Keywords.RESTRICT); needSpace = true;
				}
			} else if (type instanceof IGPPQualifierType) {
				if (((IGPPQualifierType) type).isRestrict()) {
					result.append(Keywords.RESTRICT); needSpace = true;
				}
			}
			
			try {
				if (((IQualifierType) type).isConst()) {
					if (needSpace) {
						result.append(SPACE); needSpace = false;
					}
					result.append(Keywords.CONST); needSpace = true;
				}
				if (((IQualifierType) type).isVolatile()) {
					if (needSpace) {
						result.append(SPACE); needSpace = false;
					}
					result.append(Keywords.VOLATILE); needSpace = true;
				}
			} catch (DOMException e) {
			}
		} else if (type instanceof ITypedef) {
			result.append(((ITypedef) type).getNameCharArray());
		}
		
		return result.toString();
	}

	/**
	 * Returns the type representation of the IType as a String.  This function uses the IType interfaces to build the 
	 * String representation of the IType. Resolves typedefs.
	 * @param type
	 * @return the type representation of the IType
	 */
	public static String getType(IType type) {
		return getType(type, true);
	}
		
	/**
	 * Returns the type representation of the IType as a String.  This function uses the IType interfaces to build the 
	 * String representation of the IType.
	 * @param type
	 * @param resolveTypedefs whether or not typedefs shall be resolved to their real types
	 * @return the type representation of the IType
	 */
	public static String getType(IType type, boolean resolveTypedefs) {
		StringBuilder result = new StringBuilder();
		IType[] types = new IType[DEAULT_ITYPE_SIZE];
		
		// push all of the types onto the stack
		int i = 0;
		while (type != null && ++i < 100) {
			final boolean isTypedef= type instanceof ITypedef;
			if (!resolveTypedefs || !isTypedef) { 
			    types = (IType[]) ArrayUtil.append(IType.class, types, type);
			}
			if (!resolveTypedefs && isTypedef) {
				type= null;	// stop here
			} else if (type instanceof ITypeContainer) {
				try {
					type = ((ITypeContainer) type).getType();
				} catch (DOMException e) {
					type= null;
				}
			} else {
				type= null;
			}
		}
		
		// pop all of the types off of the stack, and build the string representation while doing so
		for (int j = types.length - 1; j >= 0; j--) {
			if (types[j] != null && result.length() > 0)
				result.append(SPACE); // only add a space if this is not the first type being added

			if (types[j] != null) {
                if (j > 0 && types[j - 1] instanceof IQualifierType) {
                    result.append(getTypeString(types[j - 1]));
                    result.append(SPACE);
                    result.append(getTypeString(types[j]));
                    --j;
                } else {
                    result.append(getTypeString(types[j]));
                }
            }
		}

		return result.toString();
	}

	/**
	 * Returns the type representation of the declarator (including parameters) as a String.
	 * 
	 * @param decltor
	 * @return the type representation of the IASTDeclarator (including parameters)
	 */
	public static String getType(IASTDeclarator decltor) {
		// get the most nested declarator
		while(decltor.getNestedDeclarator() != null)
			decltor = decltor.getNestedDeclarator();
		
		IBinding binding = decltor.getName().resolveBinding();
		IType type = null;
		
		try {
			if (binding instanceof IEnumerator) {
				type = ((IEnumerator)binding).getType();
			} else if (binding instanceof IFunction) {
				type = ((IFunction)binding).getType();
			} else if (binding instanceof ITypedef) {
				type = ((ITypedef)binding).getType();
			} else if (binding instanceof IVariable) {
				type = ((IVariable)binding).getType();
			}
		} catch (DOMException e) {
			return EMPTY_STRING;
		}
		
		if (type != null) {
			return getType(type);
		}
		
		return EMPTY_STRING;
	}

	/**
	 * Return's the String representation of a node's type (if available).  This is
	 * currently only being used for testing.
	 * 
	 * TODO Remove this function when done testing if it is no longer needed
	 * 
	 * @param node
	 * @return the String representation of a node's type (if available)
	 */
	public static String getNodeType(IASTNode node) {
		try {
			if (node instanceof IASTDeclarator)
				return getType((IASTDeclarator) node);
			if (node instanceof IASTName && ((IASTName) node).resolveBinding() instanceof IVariable)
				return getType(((IVariable)((IASTName) node).resolveBinding()).getType());
			if (node instanceof IASTName && ((IASTName) node).resolveBinding() instanceof IFunction)
				return getType(((IFunction)((IASTName) node).resolveBinding()).getType());
			if (node instanceof IASTName && ((IASTName) node).resolveBinding() instanceof IType)
				return getType((IType)((IASTName) node).resolveBinding());
			if (node instanceof IASTTypeId)
				return getType((IASTTypeId) node);
		} catch (DOMException e) { return EMPTY_STRING; }
		
		return EMPTY_STRING;
	}
	
	/**
	 * Retuns the type representation of the IASTTypeId as a String.
	 * 
	 * @param typeId
	 * @return the type representation of the IASTTypeId as a String
	 */
	public static String getType(IASTTypeId typeId) {
		if (typeId instanceof CASTTypeId)
			return createCType(typeId.getAbstractDeclarator());
		else if (typeId instanceof CPPASTTypeId)
			return createCPPType(typeId.getAbstractDeclarator());
		
		return EMPTY_STRING;
	}
	
	private static String createCType(IASTDeclarator declarator) {
		IType type = CVisitor.createType(declarator);
		return getType(type);
	}
	
	private static String createCPPType(IASTDeclarator declarator) {
		IType type = CPPVisitor.createType(declarator);
		return getType(type);
	}
	
	/**
	 * This can be used to invoke the IType's isConst() if it has an isConst() method.
     * This returns the result of that invoked isConst() method.
     * It is a convenience function so that the structure of IType does not need
	 * to be known to determine if the IType is const or not.
     *
     * Note:  false is returned if no isConst() method is found
     * 
	 * @param type
	 */
	public static boolean isConst(IType type) {
		if (type instanceof IQualifierType) {
			try {
				return ((IQualifierType) type).isConst();
			} catch (DOMException e) {
				return false;
			}
		} else if (type instanceof ITypeContainer) {
			try {
				return isConst(((ITypeContainer) type).getType());
			} catch (DOMException e) {
				return false;
			}
		} else if (type instanceof IArrayType) {
			try {
				return isConst(((IArrayType) type).getType());
			} catch (DOMException e) {
				return false;
			}
		} else if (type instanceof ICPPReferenceType) {
			try {
				return isConst(((ICPPReferenceType) type).getType());
			} catch (DOMException e) {
				return false;
			}
		} else if (type instanceof IFunctionType) {
			try {
				return isConst(((IFunctionType) type).getReturnType());
			} catch (DOMException e) {
				return false;
			}
		} else if (type instanceof IPointerType) {
			try {
				return isConst(((IPointerType) type).getType());
			} catch (DOMException e) {
				return false;
			}
		} else if (type instanceof ITypedef) {
			try {
				return isConst(((ITypedef) type).getType());
			} catch (DOMException e) {
				return false;
			}
		} else {
			return false;
		}
	}
	
	
	private static String[] getQualifiedNameForAnonymous(ICPPBinding binding) throws DOMException {
		LinkedList<String> result= new LinkedList<String>();
		result.addFirst(getNameForAnonymous(binding));
		ICPPScope scope = (ICPPScope) binding.getScope();
		while (scope != null) {
			if (scope instanceof ICPPBlockScope || scope instanceof ICPPFunctionScope) { 
				break;
			}
			if (!(scope instanceof ICPPTemplateScope)) {
				IName n = scope.getScopeName();
				if (n == null) {
					break;
				}
				char[] name= n.toCharArray();
				if (name.length == 0) {
					if (!(scope instanceof ICPPNamespaceScope)) {
						name= createNameForAnonymous(scope);
						if (name == null) {
							break;
						}
						result.addFirst(new String(name));
					}
				} else {
					result.addFirst(new String(name));
				}
			}
			scope = (ICPPScope) scope.getParent();
		}
	    return result.toArray(new String[result.size()]);
	}
	
	private static String getNameForAnonymous(IBinding binding) {
		char[] name= binding.getNameCharArray();
		if (name == null || name.length == 0) {
			char[] altname= createNameForAnonymous(binding);
			if (altname != null) {
				return new String(altname);
			}
		}
		return new String(name);
	}

	private static char[] createNameForAnonymous(ICPPScope scope) {
		if (scope instanceof ICPPClassScope) {
			return createNameForAnonymous(((ICPPClassScope) scope).getClassType());
		}
		return null;
	}

	public static char[] createNameForAnonymous(IBinding binding) {
		IASTNode node= null;
		if (binding instanceof ICInternalBinding) {
			node= ((ICInternalBinding) binding).getPhysicalNode();
		}
		else if (binding instanceof ICPPInternalBinding) {
			node= ((ICPPInternalBinding) binding).getDefinition();
		}
		if (node != null) {
			IASTFileLocation loc= node.getFileLocation();
			if (loc == null) {
				node= node.getParent();
				if (node != null) {
					loc= node.getFileLocation();
				}
			}
			if (loc != null) {
				char[] fname= loc.getFileName().toCharArray();
				int fnamestart= findFileNameStart(fname);
				StringBuilder buf= new StringBuilder();
				buf.append('{');
				buf.append(fname, fnamestart, fname.length-fnamestart);
				buf.append(':');
				buf.append(loc.getNodeOffset());
				buf.append('}');
				return buf.toString().toCharArray();
			}
		}
		return null;
	}

	
	private static int findFileNameStart(char[] fname) {
		for (int i= fname.length - 2; i >= 0; i--) {
			switch (fname[i]) {
			case '/':
			case '\\':
				return i+1;
			}
		}
		return 0;
	}
}
