/*******************************************************************************
 * Copyright (c) 2006, 2012 Wind River Systems, Inc. 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:
 *    Markus Schorn - initial API and implementation
 *******************************************************************************/ 

package org.eclipse.cdt.internal.ui.typehierarchy;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.Region;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.texteditor.ITextEditor;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.ICompositeType;
import org.eclipse.cdt.core.dom.ast.IEnumeration;
import org.eclipse.cdt.core.dom.ast.IEnumerator;
import org.eclipse.cdt.core.dom.ast.IField;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.ITypedef;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMember;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexManager;
import org.eclipse.cdt.core.index.IIndexName;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IFunctionDeclaration;
import org.eclipse.cdt.ui.CUIPlugin;

import org.eclipse.cdt.internal.ui.util.ExceptionHandler;
import org.eclipse.cdt.internal.ui.util.StatusLineHandler;
import org.eclipse.cdt.internal.ui.viewsupport.IndexUI;

public class TypeHierarchyUI {
	private static final int INDEX_SEARCH_OPTION = IIndexManager.ADD_DEPENDENCIES
			| IIndexManager.ADD_DEPENDENT | IIndexManager.ADD_EXTENSION_FRAGMENTS_TYPE_HIERARCHY;

	public static THViewPart open(ICElement input, IWorkbenchWindow window) {
        if (!isValidInput(input)) {
        	return null;
        }
        ICElement memberInput= null;
        if (!isValidTypeInput(input)) {
        	memberInput= input;
        	input= memberInput.getParent();
        	if (!isValidTypeInput(input)) {
        		ICElement[] inputs= findInput(memberInput);
        		if (inputs != null) {
        			input= inputs[0];
        			memberInput= inputs[1];
        		}
        	}
        }
        		
        if (isValidTypeInput(input)) {
        	return openInViewPart(window, input, memberInput);
        }
        return null;
    }

    private static THViewPart openInViewPart(IWorkbenchWindow window, ICElement input, ICElement member) {
        IWorkbenchPage page= window.getActivePage();
        try {
            THViewPart result= (THViewPart)page.showView(CUIPlugin.ID_TYPE_HIERARCHY);
            result.setInput(input, member);
            return result;
        } catch (CoreException e) {
            ExceptionHandler.handle(e, window.getShell(), Messages.TypeHierarchyUI_OpenTypeHierarchy, null); 
        }
        return null;        
    }

	public static ICElement[] getInput(final ITextEditor editor, IRegion region) {
		if (editor != null) {
			final IEditorInput editorInput = editor.getEditorInput();
			ICElement inputCElement = CUIPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editorInput);
			if (inputCElement != null) {
				final ICProject project= inputCElement.getCProject();
				try {
					return findInput(project, editorInput, region);
				} catch (CoreException e) {
					CUIPlugin.log(e);
				}
			}
		}
		return null;
	}

    public static void open(final ITextEditor editor, final ITextSelection sel) {
		if (editor != null) {
			ICElement inputCElement = CUIPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editor.getEditorInput());
			if (inputCElement != null) {
				final ICProject project= inputCElement.getCProject();
				final IEditorInput editorInput = editor.getEditorInput();
				final Display display= Display.getCurrent();

				Job job= new Job(Messages.TypeHierarchyUI_OpenTypeHierarchy) {
					@Override
					protected IStatus run(IProgressMonitor monitor) {
						try {
							StatusLineHandler.clearStatusLine(editor.getSite());
							IRegion reg= new Region(sel.getOffset(), sel.getLength());
							final ICElement[] elems= findInput(project, editorInput, reg);
							if (elems != null && elems.length == 2) {
								display.asyncExec(new Runnable() {
									@Override
									public void run() {
										openInViewPart(editor.getSite().getWorkbenchWindow(), elems[0], elems[1]);
									}});
							} else {
								StatusLineHandler.showStatusLineMessage(editor.getSite(), 
										Messages.TypeHierarchyUI_OpenFailure_message);
							}
							return Status.OK_STATUS;
						} 
						catch (CoreException e) {
							return e.getStatus();
						}
					}
				};
				job.setUser(true);
				job.schedule();
			}
		}
    }
    
	private static ICElement[] findInput(ICProject project, IEditorInput editorInput, IRegion sel) throws CoreException {
		try {
			IIndex index= CCorePlugin.getIndexManager().getIndex(project, INDEX_SEARCH_OPTION);

			index.acquireReadLock();
			try {
				IASTName name= IndexUI.getSelectedName(editorInput, sel);
				if (name != null) {
					IBinding binding= name.resolveBinding();
					if (!isValidInput(binding)) {
						return null;
					}
					ICElement member= null;
					if (!isValidTypeInput(binding)) {
						member= findDeclaration(project, index, name, binding);
						name= null;
						binding= findTypeBinding(binding);
					}
					if (isValidTypeInput(binding)) {
						ICElement input= findDefinition(project, index, name, binding);
						if (input != null) {
							return new ICElement[] {input, member};
						}
					}
				}
			}
			finally {
				index.releaseReadLock();
			}
		}
		catch (CoreException e) {
			CUIPlugin.log(e);
		} 
		catch (InterruptedException e) {
		}
		return null;
	}

	private static ICElement[] findInput(ICElement member)  {
		ICProject project= member.getCProject();
		try {
			IIndex index= CCorePlugin.getIndexManager().getIndex(project, INDEX_SEARCH_OPTION);
			index.acquireReadLock();
			try {
				IIndexName name= IndexUI.elementToName(index, member);
				if (name != null) {
					member= IndexUI.getCElementForName(project, index, name);
					IBinding binding= index.findBinding(name);
					binding= findTypeBinding(binding);
					if (isValidTypeInput(binding)) {
						ICElement input= findDefinition(project, index, null, binding);
						if (input != null) {
							return new ICElement[] {input, member};
						}
					}
				}
			}
			finally {
				index.releaseReadLock();
			}
		}
		catch (CoreException e) {
			CUIPlugin.log(e);
		} 
		catch (InterruptedException e) {
		}
		return null;
	}

	private static IBinding findTypeBinding(IBinding memberBinding) {
		try {
			if (memberBinding instanceof IEnumerator) {
				IType type= ((IEnumerator) memberBinding).getType();
				if (type instanceof IBinding) {
					return (IBinding) type;
				}
			}
			else if (memberBinding instanceof ICPPMember) {
				return ((ICPPMember) memberBinding).getClassOwner();
			}
			else if (memberBinding instanceof IField) {
				return ((IField) memberBinding).getCompositeTypeOwner();
			}
		} catch (DOMException e) {
			// don't log problem bindings
		}
		return null;
	}

	private static ICElement findDefinition(ICProject project, IIndex index, IASTName name, IBinding binding) 
			throws CoreException {
		if (name != null && name.isDefinition()) {
			return IndexUI.getCElementForName(project, index, name);
		}

		ICElement[] elems= IndexUI.findAllDefinitions(index, binding);
		if (elems.length > 0) {
			return elems[0];
		}
		return IndexUI.findAnyDeclaration(index, project, binding);
	}

	private static ICElement findDeclaration(ICProject project, IIndex index, IASTName name, IBinding binding) 
			throws CoreException {
		if (name != null && name.isDefinition()) {
			return IndexUI.getCElementForName(project, index, name);
		}

		ICElement[] elems= IndexUI.findAllDefinitions(index, binding);
		if (elems.length > 0) {
			return elems[0];
		}
		return IndexUI.findAnyDeclaration(index, project, binding);
	}

	public static boolean isValidInput(IBinding binding) {
		if (isValidTypeInput(binding)
				|| binding instanceof ICPPMember
				|| binding instanceof IEnumerator
				|| binding instanceof IField) {
			return true;
		}
		return false;
	}

	public static boolean isValidTypeInput(IBinding binding) {
		if (binding instanceof ICompositeType
				|| binding instanceof IEnumeration 
				|| binding instanceof ITypedef) {
			return true;
		}
		return false;
	}

	public static boolean isValidInput(ICElement elem) {
		if (elem == null) {
			return false;
		}
		if (isValidTypeInput(elem)) {
			return true;
		}
		switch (elem.getElementType()) {
		case ICElement.C_FIELD:
		case ICElement.C_METHOD:
		case ICElement.C_METHOD_DECLARATION:
		case ICElement.C_TEMPLATE_METHOD:
		case ICElement.C_TEMPLATE_METHOD_DECLARATION:
		case ICElement.C_ENUMERATOR:
			return true;
		}
		return false;
	}

	public static boolean isValidTypeInput(ICElement elem) {
		if (elem == null) {
			return false;
		}
		switch (elem.getElementType()) {
		case ICElement.C_CLASS:
		case ICElement.C_STRUCT:
		case ICElement.C_UNION:
		case ICElement.C_CLASS_DECLARATION:
		case ICElement.C_STRUCT_DECLARATION:
		case ICElement.C_UNION_DECLARATION:
		case ICElement.C_ENUMERATION:
		case ICElement.C_TYPEDEF:
//		case ICElement.C_TEMPLATE_CLASS:
//		case ICElement.C_TEMPLATE_CLASS_DECLARATION:
//		case ICElement.C_TEMPLATE_STRUCT:
//		case ICElement.C_TEMPLATE_STRUCT_DECLARATION:
//		case ICElement.C_TEMPLATE_UNION:
//		case ICElement.C_TEMPLATE_UNION_DECLARATION:
			return true;
		}
		return false;
	}

	static String getLocalElementSignature(ICElement element) {
		if (element != null) {
			try {
				switch (element.getElementType()) {
				case ICElement.C_METHOD:
				case ICElement.C_METHOD_DECLARATION:
					return ((IFunctionDeclaration) element).getSignature();
				case ICElement.C_FIELD:
					return element.getElementName();
				}
			} catch (CModelException e) {
				CUIPlugin.log(e);
			}
		}
		return null;
	}
}
