/*******************************************************************************
 * Copyright (c) 2006, 2012 Wind River Systems, Inc. and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Markus Schorn - initial API and implementation
 *******************************************************************************/

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

import org.eclipse.cdt.core.CCorePlugin;
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.internal.ui.util.ExceptionHandler;
import org.eclipse.cdt.internal.ui.util.StatusLineHandler;
import org.eclipse.cdt.internal.ui.viewsupport.IndexUI;
import org.eclipse.cdt.ui.CUIPlugin;
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;

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(() -> 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) {
		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();
		}
		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;
	}
}
