/*******************************************************************************
 * Copyright (c) 2000, 2016 QNX Software Systems 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:
 * QNX Software Systems - Initial API and implementation
 * ARM Limited - https://bugs.eclipse.org/bugs/show_bug.cgi?id=186981
 *******************************************************************************/
package org.eclipse.cdt.debug.internal.ui;

import java.net.URI;
import java.util.Iterator;

import org.eclipse.cdt.debug.core.model.ICBreakpoint;
import org.eclipse.cdt.debug.core.model.ICDebugElementStatus;
import org.eclipse.cdt.debug.core.model.ICStackFrame;
import org.eclipse.cdt.debug.core.model.ICType;
import org.eclipse.cdt.debug.core.model.ICValue;
import org.eclipse.cdt.debug.core.model.IEnableDisableTarget;
import org.eclipse.cdt.debug.ui.CDebugUIPlugin;
import org.eclipse.cdt.debug.ui.breakpoints.CBreakpointPropertyDialogAction;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.contexts.IDebugContextListener;
import org.eclipse.debug.ui.contexts.IDebugContextProvider;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IVerticalRulerInfo;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.IStorageEditorInput;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.SimpleMarkerAnnotation;

import com.ibm.icu.text.MessageFormat;

/**
 * Utility methods for C/C++ Debug UI.
 */
public class CDebugUIUtils {

	static public IRegion findWord(IDocument document, int offset) {
		int start = -1;
		int end = -1;
		try {
			int pos = offset;
			char c;
			while (pos >= 0) {
				c = document.getChar(pos);
				if (!Character.isJavaIdentifierPart(c))
					break;
				--pos;
			}
			start = pos;
			pos = offset;
			int length = document.getLength();
			while (pos < length) {
				c = document.getChar(pos);
				if (!Character.isJavaIdentifierPart(c))
					break;
				++pos;
			}
			end = pos;
		} catch (BadLocationException x) {
		}
		if (start > -1 && end > -1) {
			if (start == offset && end == offset)
				return new Region(offset, 0);
			else if (start == offset)
				return new Region(start, end - start);
			else
				return new Region(start + 1, end - start - 1);
		}
		return null;
	}

	/**
	 * Returns the currently selected stack frame or the topmost frame
	 * in the currently selected thread in the Debug view
	 * of the current workbench page. Returns <code>null</code>
	 * if no stack frame or thread is selected, or if not called from the UI thread.
	 *
	 * @return the currently selected stack frame or the topmost frame
	 * 		   in the currently selected thread
	 */
	static public ICStackFrame getCurrentStackFrame() {
		IAdaptable context = DebugUITools.getDebugContext();
		return (context != null) ? (ICStackFrame) context.getAdapter(ICStackFrame.class) : null;
	}

	/**
	 * Moved from CDebugModelPresentation because it is also used by CVariableLabelProvider.
	 */
	static public String getValueText(IValue value) {
		StringBuilder label = new StringBuilder();
		if (value instanceof ICDebugElementStatus && !((ICDebugElementStatus) value).isOK()) {
			label.append(MessageFormat.format(CDebugUIMessages.getString("CDTDebugModelPresentation.4"), //$NON-NLS-1$
					(Object[]) new String[] { ((ICDebugElementStatus) value).getMessage() }));
		} else if (value instanceof ICValue) {
			ICType type = null;
			try {
				type = ((ICValue) value).getType();
			} catch (DebugException e) {
			}
			try {
				String valueString = value.getValueString();
				if (valueString != null) {
					valueString = valueString.trim();
					if (type != null && type.isCharacter()) {
						if (valueString.length() == 0)
							valueString = "."; //$NON-NLS-1$
						label.append(valueString);
					} else if (valueString.length() > 0) {
						label.append(valueString);
					}
				}
			} catch (DebugException e1) {
			}
		}
		return label.toString();
	}

	/**
	 * Moved from CDebugModelPresentation because it is also used by CVariableLabelProvider.
	 */
	public static String getVariableTypeName(ICType type) {
		StringBuilder result = new StringBuilder();
		if (type != null) {
			String typeName = type.getName();
			if (typeName != null)
				typeName = typeName.trim();
			if (type.isArray() && typeName != null) {
				int index = typeName.indexOf('[');
				if (index != -1)
					typeName = typeName.substring(0, index).trim();
			}
			if (typeName != null && typeName.length() > 0) {
				result.append(typeName);
				if (type.isArray()) {
					int[] dims = type.getArrayDimensions();
					for (int i = 0; i < dims.length; ++i) {
						result.append('[');
						result.append(dims[i]);
						result.append(']');
					}
				}
			}
		}
		return result.toString();
	}

	public static String getVariableName(IVariable variable) throws DebugException {
		return decorateText(variable, variable.getName());
	}

	public static String getEditorFilePath(IEditorInput input) throws CoreException {
		if (input instanceof IFileEditorInput) {
			IPath location = ((IFileEditorInput) input).getFile().getLocation();
			if (location != null) {
				return location.toOSString();
			}
			URI locationURI = ((IFileEditorInput) input).getFile().getLocationURI();
			if (locationURI != null) {
				IPath uriPath = URIUtil.toPath(locationURI);
				if (uriPath != null) {
					return uriPath.toOSString();
				}
			}
			return ""; //$NON-NLS-1$
		}
		if (input instanceof IStorageEditorInput) {
			return ((IStorageEditorInput) input).getStorage().getFullPath().toOSString();
		}
		if (input instanceof IPathEditorInput) {
			return ((IPathEditorInput) input).getPath().toOSString();
		}
		if (input instanceof IURIEditorInput) {
			IPath uriPath = URIUtil.toPath(((IURIEditorInput) input).getURI());
			if (uriPath != null)
				return uriPath.toOSString();
		}
		return ""; //$NON-NLS-1$
	}

	public static String decorateText(Object element, String text) {
		if (text == null)
			return null;
		StringBuilder baseText = new StringBuilder(text);
		if (element instanceof ICDebugElementStatus && !((ICDebugElementStatus) element).isOK()) {
			baseText.append(
					MessageFormat.format(" <{0}>", new Object[] { ((ICDebugElementStatus) element).getMessage() })); //$NON-NLS-1$
		}
		if (element instanceof IAdaptable) {
			IEnableDisableTarget target = ((IAdaptable) element).getAdapter(IEnableDisableTarget.class);
			if (target != null) {
				if (!target.isEnabled()) {
					baseText.append(' ');
					baseText.append(CDebugUIMessages.getString("CDTDebugModelPresentation.25")); //$NON-NLS-1$
				}
			}
		}
		return baseText.toString();
	}

	/**
	 * Helper function to open an error dialog.
	 * @param title
	 * @param message
	 * @param e
	 */
	static public void openError(final String title, final String message, final Exception e) {
		UIJob uiJob = new UIJob("open error") { //$NON-NLS-1$

			@Override
			public IStatus runInUIThread(IProgressMonitor monitor) {
				// open error for the exception
				String detail = ""; //$NON-NLS-1$
				if (e != null)
					detail = e.getMessage();

				Shell shell = CDebugUIPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell();

				MessageDialog.openError(shell, title, message + "\n" + detail); //$NON-NLS-1$
				return Status.OK_STATUS;
			}
		};
		uiJob.setSystem(true);
		uiJob.schedule();
	}

	/**
	 * Resolves the {@link IBreakpoint} from the given editor and ruler information. Returns <code>null</code>
	 * if no breakpoint exists or the operation fails.
	 *
	 * @param editor the editor
	 * @param info the current ruler information
	 * @return the {@link IBreakpoint} from the current editor position or <code>null</code>
	 */
	public static IBreakpoint getBreakpointFromEditor(ITextEditor editor, IVerticalRulerInfo info) {
		IAnnotationModel annotationModel = editor.getDocumentProvider().getAnnotationModel(editor.getEditorInput());
		IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput());
		if (annotationModel != null) {
			Iterator<Annotation> iterator = annotationModel.getAnnotationIterator();
			while (iterator.hasNext()) {
				Object object = iterator.next();
				if (object instanceof SimpleMarkerAnnotation) {
					SimpleMarkerAnnotation markerAnnotation = (SimpleMarkerAnnotation) object;
					IMarker marker = markerAnnotation.getMarker();
					try {
						if (marker.isSubtypeOf(IBreakpoint.BREAKPOINT_MARKER)) {
							Position position = annotationModel.getPosition(markerAnnotation);
							int line = document.getLineOfOffset(position.getOffset());
							if (line == info.getLineOfLastMouseButtonActivity()) {
								IBreakpoint breakpoint = DebugPlugin.getDefault().getBreakpointManager()
										.getBreakpoint(marker);
								if (breakpoint != null) {
									return breakpoint;
								}
							}
						}
					} catch (CoreException e) {
					} catch (BadLocationException e) {
					}
				}
			}
		}
		return null;
	}

	public static void editBreakpointProperties(IWorkbenchPart part, final ICBreakpoint bp) {
		final ISelection debugContext = DebugUITools.getDebugContextForPart(part);
		CBreakpointPropertyDialogAction propertiesAction = new CBreakpointPropertyDialogAction(part.getSite(),
				new ISelectionProvider() {
					@Override
					public ISelection getSelection() {
						return new StructuredSelection(bp);
					}

					@Override
					public void addSelectionChangedListener(ISelectionChangedListener listener) {
					}

					@Override
					public void removeSelectionChangedListener(ISelectionChangedListener listener) {
					}

					@Override
					public void setSelection(ISelection selection) {
					}
				}, new IDebugContextProvider() {
					@Override
					public ISelection getActiveContext() {
						return debugContext;
					}

					@Override
					public void addDebugContextListener(IDebugContextListener listener) {
					}

					@Override
					public void removeDebugContextListener(IDebugContextListener listener) {
					}

					@Override
					public IWorkbenchPart getPart() {
						return null;
					}

				});
		propertiesAction.run();
		propertiesAction.dispose();
	}

	/**
	 * Formats the given key stroke or click name and the modifier keys
	 * to a key binding string that can be used in action texts.
	 *
	 * @param modifierKeys the modifier keys
	 * @param keyOrClick a key stroke or click, e.g. "Double Click"
	 * @return the formatted keyboard shortcut string, e.g. "Shift+Double Click"
	 *
	 * @since 8.1
	 */
	public static final String formatKeyBindingString(int modifierKeys, String keyOrClick) {
		// this should actually all be delegated to KeyStroke class
		return KeyStroke.getInstance(modifierKeys, KeyStroke.NO_KEY).format() + keyOrClick;
	}
}
