/*******************************************************************************
 * Copyright (c) 2004, 2009 QNX Software Systems 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:
 *     QNX Software Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.core.browser;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.IMember;
import org.eclipse.cdt.core.model.IMethodDeclaration;
import org.eclipse.cdt.core.model.IParent;
import org.eclipse.cdt.core.model.IStructure;
import org.eclipse.cdt.core.model.ITranslationUnit;

/**
 * @noextend This class is not intended to be subclassed by clients.
 * @noinstantiate This class is not intended to be instantiated by clients.
 */
public class TypeUtil {

    public static boolean isDeclaringType(ICElement elem) {
        int type = elem.getElementType();
        return (type == ICElement.C_CLASS
            || type == ICElement.C_STRUCT
            || type == ICElement.C_ENUMERATION
            || type == ICElement.C_UNION
            || type == ICElement.C_TYPEDEF
            || type == ICElement.C_NAMESPACE);
    }

    public static boolean isMemberType(ICElement elem) {
        int type = elem.getElementType();
        if (type == ICElement.C_CLASS
            || type == ICElement.C_STRUCT
            || type == ICElement.C_ENUMERATION
            || type == ICElement.C_UNION
            || type == ICElement.C_TYPEDEF
            || type == ICElement.C_NAMESPACE)
        	return true;
        return elem instanceof IMember;
    }

    /**
     * Returns the type in which this member is declared, or <code>null</code>
     * if this member is not declared in a type (for example, a top-level type).
     * This is a handle-only method.
     * 
     * @return the type in which this member is declared, or <code>null</code>
     * if this member is not declared in a type (for example, a top-level type)
     */
    public static ICElement getDeclaringType(ICElement elem) {
        if (!isMemberType(elem))
            return null;
        ICElement parent = elem.getParent();
        while (parent != null && !(parent instanceof ITranslationUnit)) {
            if (isDeclaringType(parent))
                return parent;
            parent = parent.getParent();
        }
        return null;
    }
    
    public static ICElement getDeclaringClass(ICElement type) {
    	ICElement parentElement = type.getParent();
    	if (parentElement != null && isClassOrStruct(parentElement)) {
    	    return parentElement;
    	}

    	if (isClassOrStruct(type)) {
        	while (parentElement != null) {
        	    if (isClassOrStruct(parentElement)) {
        			return parentElement;
        		} else if (parentElement instanceof IMember) {
       				parentElement = parentElement.getParent();
       			} else {
       				return null;
        		}
        	}
        }

    	return null;
    }
    
    public static boolean isClassOrStruct(ICElement type) {
        int kind = type.getElementType();
		// case ICElement.C_TEMPLATE_CLASS:
		// case ICElement.C_TEMPLATE_STRUCT:
        return (kind == ICElement.C_CLASS || kind == ICElement.C_STRUCT);
    }

    public static boolean isClass(ICElement type) {
        return (type.getElementType() == ICElement.C_CLASS);
    }

    public static boolean isNamespace(ICElement type) {
        return (type.getElementType() == ICElement.C_NAMESPACE);
    }

    /**
     * Returns the top-level types declared in the given translation unit
     * in the order in which they appear in the source.
     *
     * @param tu the translation unit
     * @return the top-level types declared in the given translation unit
     * @throws CModelException if this element does not exist or if an
     *		exception occurs while accessing its corresponding resource
     */
    public static ICElement[] getTypes(ITranslationUnit tu) throws CModelException {
        List<ICElement> typeList = new ArrayList<ICElement>();
        ICElement[] children = tu.getChildren();
        for (int i = 0; i < children.length; ++i) {
            if (isDeclaringType(children[i]))
                typeList.add(children[i]);
        }
        return typeList.toArray(new ICElement[typeList.size()]);
	}
    
    /**
     * Returns all types declared in the given translation unit in the order
     * in which they appear in the source. 
     * This includes all top-level types and nested member types.
     * It does NOT include local types (types defined in methods).
     *
     * @return the array of top-level and member types defined in the given translation unit, in declaration order.
     * @throws CModelException if this element does not exist or if an
     *		exception occurs while accessing its corresponding resource
     */
    public static ICElement[] getAllTypes(ITranslationUnit tu) throws CModelException {
    	ICElement[] types = getTypes(tu);
    	ArrayList<ICElement> allTypes = new ArrayList<ICElement>(types.length);
    	ArrayList<ICElement> typesToTraverse = new ArrayList<ICElement>(types.length);
    	for (ICElement type : types) {
    		typesToTraverse.add(type);
    	}
    	while (!typesToTraverse.isEmpty()) {
    		ICElement type = typesToTraverse.get(0);
    		typesToTraverse.remove(type);
    		allTypes.add(type);
    		types = getTypes(type);
    		for (ICElement type2 : types) {
    			typesToTraverse.add(type2);
    		}
    	} 
        return allTypes.toArray(new ICElement[allTypes.size()]);
	}
    

	/**
	 * Returns the immediate member types declared by the given element.
	 * The results are listed in the order in which they appear in the source file.
	 *
	 * @param elem the element
	 * @exception CModelException if this element does not exist or if an
	 *		exception occurs while accessing its corresponding resource.
	 * @return the immediate member types declared by this type
	 */
    public static ICElement[] getTypes(ICElement elem) throws CModelException {
        List<ICElement> typeList = new ArrayList<ICElement>();
        if (isDeclaringType(elem) && elem instanceof IParent) {
            ICElement[] children = ((IParent)elem).getChildren();
            for (int i = 0; i < children.length; ++i) {
                if (isDeclaringType(children[i]))
                    typeList.add(children[i]);
            }
        }
        return typeList.toArray(new ICElement[typeList.size()]);
	}
    
    public static ITranslationUnit getTranslationUnit(ICElement elem) {
        while (elem != null) {
            if (elem instanceof ITranslationUnit)
                return (ITranslationUnit)elem;
            elem = elem.getParent();
        }
        return null;
    }
	
//  TODO move method to CModelUtil
    public static IQualifiedTypeName getFullyQualifiedName(ICElement type) {
		String name = type.getElementName();
		IQualifiedTypeName qualifiedName = new QualifiedTypeName(name);
		ICElement parent = type.getParent();
		while (parent != null && (isNamespace(parent) || isClass(parent))) {
			qualifiedName = new QualifiedTypeName(parent.getElementName()).append(qualifiedName);
		    parent = parent.getParent();
		}
	    return qualifiedName;
	}
    
    public static IMethodDeclaration[] getMethods(ICElement elem) {
	    if (elem instanceof IStructure) {
	        try {
	            List<?> list = ((IParent)elem).getChildrenOfType(ICElement.C_METHOD_DECLARATION);
	            if (list != null && !list.isEmpty()) {
	                return list.toArray(new IMethodDeclaration[list.size()]);
	            }
	        } catch (CModelException e) {
	        }
	    }
	    return null;
	}

    public static ICElement[] getFields(ICElement elem) {
	    if (elem instanceof IStructure) {
	        try {
	            List<?> list = ((IParent)elem).getChildrenOfType(ICElement.C_FIELD);
	            if (list != null && !list.isEmpty()) {
	                return list.toArray(new ICElement[list.size()]);
	            }
	        } catch (CModelException e) {
	        }
	    }
	    return null;
	}
    
    
	/**
	 * Finds a method by name.
	 * This searches for a method with a name and signature. Parameter types are only
	 * compared by the simple name, no resolving for the fully qualified type name is done.
	 * Constructors are only compared by parameters, not the name.
	 * @param name The name of the method to find
	 * @param paramTypes The type signatures of the parameters e.g. <code>{"QString;","I"}</code>
	 * @param isConstructor If the method is a constructor
	 * @param methods The methods to search in
	 * @return The found method or <code>null</code>, if nothing found
	 */
//  TODO move methods to CModelUtil
	public static IMethodDeclaration findMethod(String name, String[] paramTypes, boolean isConstructor, boolean isDestructor, IMethodDeclaration[] methods) throws CModelException {
		for (int i= methods.length - 1; i >= 0; i--) {
			if (isSameMethodSignature(name, paramTypes, isConstructor, isDestructor, methods[i])) {
				return methods[i];
			}
		}
		return null;
	}
   
	/**
	 * Tests if a method equals to the given signature.
	 * Parameter types are only compared by the simple name, no resolving for
	 * the fully qualified type name is done. Constructors are only compared by
	 * parameters, not the name.
	 * @param name Name of the method
	 * @param paramTypes The type signatures of the parameters e.g. <code>{"QString;","I"}</code>
	 * @param isConstructor Specifies if the method is a constructor
	 * @return Returns <code>true</code> if the method has the given name and parameter types and constructor state.
	 */
//TODO move methods to CModelUtil
	public static boolean isSameMethodSignature(String name, String[] paramTypes, boolean isConstructor, boolean isDestructor, IMethodDeclaration curr) throws CModelException {
		if (isConstructor || isDestructor || name.equals(curr.getElementName())) {
			if ((isConstructor == curr.isConstructor()) && (isDestructor == curr.isDestructor())) {
				String[] currParamTypes= curr.getParameterTypes();
				if (paramTypes.length == currParamTypes.length) {
					for (int i= 0; i < paramTypes.length; i++) {
						// TODO should compare signatures
						String t1= paramTypes[i];
						String t2= currParamTypes[i];
						if (!t1.equals(t2)) {
							return false;
						}
					}
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * Finds a method in a type.
	 * This searches for a method with the same name and signature. Parameter types are only
	 * compared by the simple name, no resolving for the fully qualified type name is done.
	 * Constructors are only compared by parameters, not the name.
	 * @param name The name of the method to find
	 * @param paramTypes The type signatures of the parameters e.g. <code>{"QString;","I"}</code>
	 * @param isConstructor If the method is a constructor
	 * @return The first found method or <code>null</code>, if nothing found
	 */
//	TODO move methods to CModelUtil
	public static IMethodDeclaration findMethod(String name, String[] paramTypes, boolean isConstructor, boolean isDestructor, ICElement type) throws CModelException {
		return findMethod(name, paramTypes, isConstructor, isDestructor, getMethods(type));
	}

}
