/*******************************************************************************
 * Copyright (c) 2005, 2007 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
 *     IBM Corporation
 *     Markus Schorn (Wind River Systems)
 *     Warren Paul (Nokia) - 174238
 *******************************************************************************/
package org.eclipse.cdt.internal.ui.wizards.classwizard;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.views.contentoutline.ContentOutline;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.browser.AllTypesCache;
import org.eclipse.cdt.core.browser.IQualifiedTypeName;
import org.eclipse.cdt.core.browser.ITypeInfo;
import org.eclipse.cdt.core.browser.ITypeReference;
import org.eclipse.cdt.core.browser.TypeSearchScope;
import org.eclipse.cdt.core.dom.ILinkage;
import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IEnumeration;
import org.eclipse.cdt.core.dom.ast.ITypedef;
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.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespace;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IndexFilter;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICContainer;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.ISourceRoot;
import org.eclipse.cdt.core.parser.IScannerInfo;
import org.eclipse.cdt.core.parser.IScannerInfoProvider;
import org.eclipse.cdt.ui.CUIPlugin;

import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;

import org.eclipse.cdt.internal.ui.editor.CEditor;
import org.eclipse.cdt.internal.ui.viewsupport.IViewPartInputProvider;
import org.eclipse.cdt.internal.ui.wizards.filewizard.NewSourceFileGenerator;

public class NewClassWizardUtil {

    /**
     * Returns the parent source folder of the given element. If the given
     * element is already a source folder, the element itself is returned.
     * 
     * @param element the C Element
     * @return the source folder
     */
    public static ICContainer getSourceFolder(ICElement element) {
        ICContainer folder = null;
        boolean foundSourceRoot = false;
        ICElement curr = element;
        while (curr != null && !foundSourceRoot) {
            if (curr instanceof ICContainer && folder == null) {
                folder = (ICContainer)curr;
            }
            foundSourceRoot = (curr instanceof ISourceRoot);
            curr = curr.getParent();
        }
        if (folder == null) {
            ICProject cproject = element.getCProject();
            folder = cproject.findSourceRoot(cproject.getProject());
        }
        return folder;
    }
    
    /**
     * Returns the parent source folder for the given path. If the given
     * path is already a source folder, the corresponding C element is returned.
     * 
     * @param path the path
     * @return the source folder
     */
    public static ICContainer getSourceFolder(IPath path) {
        if (path == null)
            return null;
        while (path.segmentCount() > 0) {
            IResource res = getWorkspaceRoot().findMember(path);
            if (res != null && res.exists()) {
                int resType = res.getType();
                if (resType == IResource.PROJECT || resType == IResource.FOLDER) {
                    ICElement elem = CoreModel.getDefault().create(res.getFullPath());
                    if (elem != null) {
                        ICContainer sourceFolder = getSourceFolder(elem);
                        if (sourceFolder != null)
                            return sourceFolder;
                        if (resType == IResource.PROJECT) {
                        	if (elem instanceof ICContainer) {
                                return (ICContainer)elem;
                        	}
                        }
                    }
                }
            }
            path = path.removeLastSegments(1);
        }
        return null;
    }
    
    /**
     * Returns the parent source folder for the given resource. If the given
     * resource is already a source folder, the corresponding C element is returned.
     * 
     * @param resource the resource
     * @return the source folder
     */
    public static ICContainer getSourceFolder(IResource resource) {
        if (resource != null && resource.exists()) {
            int resType = resource.getType();
            if (resType == IResource.PROJECT || resType == IResource.FOLDER) {
                ICElement elem = CoreModel.getDefault().create(resource.getFullPath());
                if (elem != null) {
                    ICContainer sourceFolder = getSourceFolder(elem);
                    if (sourceFolder != null)
                        return sourceFolder;
                }
            } else {
                return getSourceFolder(resource.getParent());
            }
        }
        return null;
    }
    
    /**
     * Returns the first source root in the given project. If the project has
     * no source roots as children, the project itself is returned.
     * 
     * @param cproject
     * @return the source root
     */
    public static ISourceRoot getFirstSourceRoot(ICProject cproject) {
        ISourceRoot folder = null;
        try {
            if (cproject.exists()) {
                ISourceRoot[] roots = cproject.getSourceRoots();
                if (roots != null && roots.length > 0)
                    folder = roots[0];
            }
        } catch (CModelException e) {
            CUIPlugin.log(e);
        }
        if (folder == null) {
            folder = cproject.findSourceRoot(cproject.getResource());
        }
        return folder;
    }

    /**
     * Returns the C Element which corresponds to the given selection.
     * 
     * @param selection the selection to be inspected
     * @return a C element matching the selection, or <code>null</code>
     * if no C element exists in the given selection
     */
    public static ICElement getCElementFromSelection(IStructuredSelection selection) {
        ICElement celem = null;
        if (selection != null && !selection.isEmpty()) {
            Object selectedElement = selection.getFirstElement();
            if (selectedElement instanceof IAdaptable) {
                IAdaptable adaptable = (IAdaptable) selectedElement;            
                
                celem = (ICElement) adaptable.getAdapter(ICElement.class);
                if (celem == null) {
                    IResource resource = (IResource) adaptable.getAdapter(IResource.class);
                    if (resource != null && resource.getType() != IResource.ROOT) {
                        while (celem == null && resource.getType() != IResource.PROJECT) {
                            celem = (ICElement) resource.getAdapter(ICElement.class);
                            resource = resource.getParent();
                        }
                        if (celem == null) {
                            celem = CoreModel.getDefault().create(resource); // c project
                        }
                    }
                }
            }
        }
        return celem;
    }
    
    /**
     * Returns the C Element which corresponds to the active editor.
     * 
     * @return a C element matching the active editor, or <code>null</code>
     * if no C element can be found
     */
    public static ICElement getCElementFromEditor() {
        ICElement celem = null;
        IWorkbenchPart part = CUIPlugin.getActivePage().getActivePart();
        if (part instanceof ContentOutline) {
            part = CUIPlugin.getActivePage().getActiveEditor();
        }
        if (part instanceof IViewPartInputProvider) {
            Object elem = ((IViewPartInputProvider)part).getViewPartInput();
            if (elem instanceof ICElement) {
                celem = (ICElement) elem;
            }
        }
        if (celem == null && part instanceof CEditor) {
            IEditorInput input = ((IEditorPart)part).getEditorInput();
            if (input != null) {
                final IResource res = (IResource) input.getAdapter(IResource.class);
                if (res != null && res instanceof IFile) {
                    celem = CoreModel.getDefault().create((IFile)res);
                }
            }
        }
        return celem;
    }
    
    /**
     * Returns the parent namespace for the given element. If the given element is
     * already a namespace, the element itself is returned.
     *
     * @param element the given C Element
     * @return the C Element for the namespace, or <code>null</code> if not found
     */
    public static ICElement getNamespace(ICElement element) {
        ICElement curr = element;
        while (curr != null) {
            int type = curr.getElementType();
            if (type == ICElement.C_UNIT) {
                break;
            }
            if (type == ICElement.C_NAMESPACE) {
                return curr;
            }
            curr = curr.getParent();
        }
        return null;
    }
    
    /**
     * Creates a header file name from the given class name. This is the file name
     * to be used when the class is created. eg. "MyClass" -> "MyClass.h"
     * 
     * @param className the class name
     * @return the header file name for the given class
     */
    public static String createHeaderFileName(String className) {
        return NewSourceFileGenerator.generateHeaderFileNameFromClass(className);
    }
    
    /**
     * Creates a source file name from the given class name. This is the file name
     * to be used when the class is created. eg. "MyClass" -> "MyClass.cpp"
     * 
     * @param className the class name
     * @return the source file name for the given class
     */
    public static String createSourceFileName(String className) {
        return NewSourceFileGenerator.generateSourceFileNameFromClass(className);
    }
    
    /**
     * Returns the workspace root.
     * 
     * @return the workspace root
     */ 
    public static IWorkspaceRoot getWorkspaceRoot() {
        return ResourcesPlugin.getWorkspace().getRoot();
    }
    
    /**
     * Resolve the location of the given class.
     * 
     * @param type the class to resolve
     * @param context the runnable context
     * @return the class location, or <code>null</code> if not found
     */
    public static ITypeReference resolveClassLocation(ITypeInfo type, IRunnableContext context) {
        return type.getResolvedReference();
    }

    private static final int[] CLASS_TYPES = { ICElement.C_CLASS, ICElement.C_STRUCT };
    
    /**
     * Returns all classes/structs which are accessible from the include
     * paths of the given project.
     * 
     * @param cProject the given project
     * @return array of classes/structs
     */
    public static ITypeInfo[] getReachableClasses(ICProject cProject) {
        ITypeInfo[] elements = AllTypesCache.getTypes(new TypeSearchScope(true), CLASS_TYPES);
        if (elements != null && elements.length > 0) {
            if (cProject != null) {
            	IProject project = cProject.getProject();
                IScannerInfoProvider provider = CCorePlugin.getDefault().getScannerInfoProvider(project);
                if (provider != null) {
                    //TODO get the scanner info for the actual source folder
                    IScannerInfo info = provider.getScannerInformation(project);
                    if (info != null) {
                        String[] includePaths = info.getIncludePaths();
                        List<ITypeInfo> filteredTypes = new ArrayList<ITypeInfo>();
                        for (int i = 0; i < elements.length; ++i) {
                            ITypeInfo baseType = elements[i];
                            if (isTypeReachable(baseType, cProject, includePaths)) {
                                filteredTypes.add(baseType);
                            }
                        }
                        return filteredTypes.toArray(new ITypeInfo[filteredTypes.size()]);
                    }
                }
            }
        }
        return elements;
    }
    
    /**
     * Checks whether the given type can be found in the given project or the
     * given include paths.
     * 
     * @param type the type
     * @param project the project
     * @param includePaths the include paths
     * @return <code>true</code> if the given type is found
     */
    public static boolean isTypeReachable(ITypeInfo type, ICProject project, String[] includePaths) {
        ICProject baseProject = type.getEnclosingProject();
        if (baseProject != null && baseProject.equals(project)) {
            return true;
        }
        
        // check the include paths
        ITypeReference ref = type.getResolvedReference();
        for (int i = 0; i < includePaths.length; ++i) {
            IPath includePath = new Path(includePaths[i]);
            if (ref != null) {
                if (includePath.isPrefixOf(ref.getLocation()))
                    return true;
            } else {
                // we don't have the real location, so just check the project path
                if (baseProject != null && baseProject.getProject().getLocation().isPrefixOf(includePath))
                    return true;
            }
        }

        return false;
    }

	public static final int SEARCH_MATCH_ERROR = -1;  // some error, search failed
	public static final int SEARCH_MATCH_NOTFOUND = 0;  // no match found
	public static final int SEARCH_MATCH_FOUND_EXACT = 1;   // exact match
	public static final int SEARCH_MATCH_FOUND_EXACT_ANOTHER_TYPE = 2; // same name found, by different type
	public static final int SEARCH_MATCH_FOUND_ANOTHER_NAMESPACE = 3; // same type name exits in different scope
	public static final int SEARCH_MATCH_FOUND_ANOTHER_TYPE = 4; // same name used by another type in different scope

	/**
	 * Search for the given qualified name of the give
	 * @param typeName  qualified name of the type to search
	 * @param project  
	 * @param queryType Class of interface type to search for (e.g. ICPPClassType.class)
	 * @return one of {@link #SEARCH_MATCH_ERROR}, 
	 * {@link #SEARCH_MATCH_FOUND_ANOTHER_NAMESPACE},
	 * {@link #SEARCH_MATCH_FOUND_ANOTHER_TYPE}, 
	 * {@link #SEARCH_MATCH_FOUND_EXACT_ANOTHER_TYPE},
	 * {@link #SEARCH_MATCH_FOUND_EXACT} or
	 * {@link #SEARCH_MATCH_NOTFOUND}.	 
	 */
	public static int searchForCppType(IQualifiedTypeName typeName, ICProject project, Class<?> queryType) {
		IIndex index= null;
		try {
			if (project != null) {
				index = CCorePlugin.getIndexManager().getIndex(project);
				index.acquireReadLock();
			}
		} catch (CoreException e) {
			CUIPlugin.log(e);
			return SEARCH_MATCH_ERROR;
		} catch (InterruptedException e) {
		}
		if (index == null) {
			return SEARCH_MATCH_ERROR;
		}
		try {
			String fullyQualifiedTypeName = typeName.getFullyQualifiedName();
			try {
				IndexFilter filter= IndexFilter.getFilter(ILinkage.CPP_LINKAGE_ID);
				String[] nameStrs= fullyQualifiedTypeName.split("::"); //$NON-NLS-1$
				char[][] names= new char[nameStrs.length][];
				for (int i = 0; i < names.length; i++) {
					names[i]= nameStrs[i].toCharArray();
				}
				//bug 165636: findBindings(char[][]...) does not find nested nodes (classes)
				//therefore switching back to findBindings(Pattern...)
				IBinding[] bindings = index.findBindings(Pattern.compile(typeName.getName()), false, filter, new NullProgressMonitor());
				boolean sameTypeNameExists = false; 
				boolean sameNameDifferentTypeExists = false;

				for (int i = 0; i < bindings.length; ++i) {
					ICPPBinding binding = (ICPPBinding)bindings[i];

					//get the fully qualified name of this binding
					String bindingFullName = CPPVisitor.renderQualifiedName(binding.getQualifiedName());
					Class<? extends ICPPBinding> currentNodeType = binding.getClass();
					// full binding				
					if (queryType.isAssignableFrom(currentNodeType)) {						
						if (bindingFullName.equals(fullyQualifiedTypeName)) {
							//bug 165636: there is a match only if there is a definition for the binding
							//otherwise, users can create a new class definition for this binding
							if (index.findDefinitions(binding).length > 0)	{
								return SEARCH_MATCH_FOUND_EXACT;
							}							
						} else {
							// same type , same name , but different name space
							// see if there is an exact match;
							sameTypeNameExists = true;
						}
					} else if(ICPPClassType.class.isAssignableFrom(currentNodeType) || 
							IEnumeration.class.isAssignableFrom(currentNodeType) || // TODO - this should maybe be ICPPEnumeration
							ICPPNamespace.class.isAssignableFrom(currentNodeType) ||
							ITypedef.class.isAssignableFrom(currentNodeType) ||
							ICPPBasicType.class.isAssignableFrom(currentNodeType))
					{						
						if (bindingFullName.equals(fullyQualifiedTypeName))	{
							return SEARCH_MATCH_FOUND_EXACT_ANOTHER_TYPE;
						}
						// different type , same name , but different name space
						sameNameDifferentTypeExists = true;
					}
				}
				if(sameTypeNameExists){
					return SEARCH_MATCH_FOUND_ANOTHER_NAMESPACE;
				}

				if(sameNameDifferentTypeExists){
					return SEARCH_MATCH_FOUND_ANOTHER_TYPE;
				}
			} catch (CoreException e) {
				return SEARCH_MATCH_ERROR;
			} catch (DOMException e) {
				return SEARCH_MATCH_ERROR;
			}
			return SEARCH_MATCH_NOTFOUND;
		}
		finally {
			index.releaseReadLock();
		}
	}
}
