/*******************************************************************************
 *  Copyright (c) 2006, 2016 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *     QNX Software Systems - Mikhail Khodjaiants - Registers View (Bug 53640)
 *     Wind River Systems -  adopted to use with Modules view
 *     Ericsson AB		  -  Modules view for DSF implementation
 *******************************************************************************/
package org.eclipse.cdt.dsf.debug.ui.viewmodel.modules.detail;

import java.util.concurrent.ExecutionException;

import org.eclipse.cdt.debug.ui.ICDebugUIConstants;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.internal.ui.IDsfDebugHelpContextIds;
import org.eclipse.cdt.dsf.debug.internal.ui.IInternalDsfDebugUIConstants;
import org.eclipse.cdt.dsf.debug.internal.ui.viewmodel.detailsupport.MessagesForDetailPane;
import org.eclipse.cdt.dsf.debug.internal.ui.viewmodel.detailsupport.TextViewerAction;
import org.eclipse.cdt.dsf.debug.service.IModules;
import org.eclipse.cdt.dsf.debug.service.IModules.IModuleDMContext;
import org.eclipse.cdt.dsf.debug.service.IModules.IModuleDMData;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.core.runtime.IAdaptable;
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.debug.ui.IDebugUIConstants;
import org.eclipse.debug.ui.IDebugView;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.progress.WorkbenchJob;
import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds;

/**
 *
 */
public class ModuleDetailPane extends ModulesAbstractDetailPane implements IAdaptable, IPropertyChangeListener {

	/**
	 * These are the IDs for the actions in the context menu
	 */
	protected static final String DETAIL_COPY_ACTION = ActionFactory.COPY.getId() + ".SourceDetailPane"; //$NON-NLS-1$
	protected static final String DETAIL_SELECT_ALL_ACTION = IDebugView.SELECT_ALL_ACTION + ".SourceDetailPane"; //$NON-NLS-1$

	/**
	 * The ID, name and description of this pane are stored in constants so that the class
	 * does not have to be instantiated to access them.
	 */
	public static final String ID = "ModuleDetailPane"; //$NON-NLS-1$
	public static final String NAME = "Module Viewer"; //$NON-NLS-1$
	public static final String DESCRIPTION = "A detail pane that is based on a source viewer.  Displays as text and has actions for assigning values, content assist and text modifications."; //$NON-NLS-1$

	/**
	 * The source viewer in which the computed string detail
	 * of selected modules will be displayed.
	 */
	private SourceViewer fSourceViewer;

	@Override
	public Control createControl(Composite parent) {
		createSourceViewer(parent);

		if (isInView()) {
			createViewSpecificComponents();
			createActions();
			DsfUIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
			JFaceResources.getFontRegistry().addListener(this);
		}
		return fSourceViewer.getControl();
	}

	private DetailJob fDetailJob = null;

	@Override
	public void display(IStructuredSelection selection) {
		if (selection == null) {
			clearSourceViewer();
			return;
		}

		if (isInView()) {
			fSourceViewer.setEditable(true);
		}

		if (selection.isEmpty()) {
			clearSourceViewer();
			return;
		}

		synchronized (this) {
			if (fDetailJob != null) {
				fDetailJob.cancel();
			}
			fDetailJob = new DetailJob(selection.getFirstElement());
			fDetailJob.schedule();
		}

	}

	/**
	 * Clears the source viewer, removes all text.
	 */
	protected void clearSourceViewer() {
		if (fDetailJob != null) {
			fDetailJob.cancel();
		}
		fDetailDocument.set(""); //$NON-NLS-1$
		fSourceViewer.setEditable(false);
	}

	@Override
	public void dispose() {
		super.dispose();
		if (fDetailJob != null)
			fDetailJob.cancel();
		if (fSourceViewer != null && fSourceViewer.getControl() != null)
			fSourceViewer.getControl().dispose();

		if (isInView()) {
			DsfUIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);
			JFaceResources.getFontRegistry().removeListener(this);
		}

	}

	@Override
	public String getDescription() {
		return DESCRIPTION;
	}

	@Override
	public String getID() {
		return ID;
	}

	@Override
	public String getName() {
		return NAME;
	}

	@Override
	public boolean setFocus() {
		if (fSourceViewer != null) {
			fSourceViewer.getTextWidget().setFocus();
			return true;
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T getAdapter(Class<T> adapter) {
		if (ITextViewer.class.equals(adapter)) {
			return (T) fSourceViewer;
		}
		return null;
	}

	@Override
	public void propertyChange(PropertyChangeEvent event) {
		String propertyName = event.getProperty();
		if (propertyName.equals(IDebugUIConstants.PREF_DETAIL_PANE_FONT)) {
			fSourceViewer.getTextWidget().setFont(JFaceResources.getFont(IDebugUIConstants.PREF_DETAIL_PANE_FONT));
		}
	}

	/**
	 * Creates the source viewer in the given parent composite
	 *
	 * @param parent Parent composite to create the source viewer in
	 */
	private void createSourceViewer(Composite parent) {

		// Create & configure a SourceViewer
		fSourceViewer = new SourceViewer(parent, null, SWT.V_SCROLL | SWT.H_SCROLL);
		fSourceViewer.setDocument(getDetailDocument());
		fSourceViewer.getTextWidget().setFont(JFaceResources.getFont(IDebugUIConstants.PREF_DETAIL_PANE_FONT));
		fSourceViewer.getTextWidget().setWordWrap(DsfUIPlugin.getDefault().getPreferenceStore()
				.getBoolean(IInternalDsfDebugUIConstants.PREF_DETAIL_PANE_WORD_WRAP));
		fSourceViewer.setEditable(false);
		PlatformUI.getWorkbench().getHelpSystem().setHelp(fSourceViewer.getTextWidget(),
				IDsfDebugHelpContextIds.DETAIL_PANE);
		Control control = fSourceViewer.getControl();
		GridData gd = new GridData(GridData.FILL_BOTH);
		control.setLayoutData(gd);
	}

	/**
	 * Variables used to create the detailed information for a selection
	 */
	private IDocument fDetailDocument;

	/**
	 * Lazily instantiate and return a Document for the detail pane text viewer.
	 */
	protected IDocument getDetailDocument() {
		if (fDetailDocument == null) {
			fDetailDocument = new Document();
		}
		return fDetailDocument;
	}

	/**
	 * Creates listeners and other components that should only be added to the
	 * source viewer when this detail pane is inside a view.
	 */
	private void createViewSpecificComponents() {

		// Add a document listener so actions get updated when the document changes
		getDetailDocument().addDocumentListener(new IDocumentListener() {
			@Override
			public void documentAboutToBeChanged(DocumentEvent event) {
			}

			@Override
			public void documentChanged(DocumentEvent event) {
				updateSelectionDependentActions();
			}
		});

		// Add the selection listener so selection dependent actions get updated.
		fSourceViewer.getSelectionProvider().addSelectionChangedListener(new ISelectionChangedListener() {
			@Override
			public void selectionChanged(SelectionChangedEvent event) {
				updateSelectionDependentActions();
			}
		});

		// Add a focus listener to update actions when details area gains focus
		fSourceViewer.getControl().addFocusListener(new FocusAdapter() {
			@Override
			public void focusGained(FocusEvent e) {

				getViewSite().setSelectionProvider(fSourceViewer.getSelectionProvider());

				setGlobalAction(IDebugView.SELECT_ALL_ACTION, getAction(DETAIL_SELECT_ALL_ACTION));
				setGlobalAction(IDebugView.COPY_ACTION, getAction(DETAIL_COPY_ACTION));

				getViewSite().getActionBars().updateActionBars();
			}

			@Override
			public void focusLost(FocusEvent e) {

				getViewSite().setSelectionProvider(null);

				setGlobalAction(IDebugView.SELECT_ALL_ACTION, null);
				setGlobalAction(IDebugView.COPY_ACTION, null);
				getViewSite().getActionBars().updateActionBars();

			}
		});

		// Add a context menu to the detail area
		createDetailContextMenu(fSourceViewer.getTextWidget());
	}

	/**
	 * Create the context menu particular to the detail pane.  Note that anyone
	 * wishing to contribute an action to this menu must use
	 * <code>ICDebugUIConstants.MODULES_VIEW_DETAIL_ID</code> as the
	 * <code>targetID</code> in the extension XML.
	 */
	protected void createDetailContextMenu(Control menuControl) {
		MenuManager menuMgr = new MenuManager();
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			@Override
			public void menuAboutToShow(IMenuManager mgr) {
				fillDetailContextMenu(mgr);
			}
		});
		Menu menu = menuMgr.createContextMenu(menuControl);
		menuControl.setMenu(menu);

		getViewSite().registerContextMenu(ICDebugUIConstants.MODULES_VIEW_DETAIL_ID, menuMgr,
				fSourceViewer.getSelectionProvider());

	}

	/**
	 * Adds items to the detail pane's context menu including any extension defined
	 * actions.
	 *
	 * @param menu The menu to add the item to.
	 */
	protected void fillDetailContextMenu(IMenuManager menu) {

		menu.add(new Separator(ICDebugUIConstants.MODULES_GROUP));
		menu.add(new Separator());
		menu.add(getAction(DETAIL_COPY_ACTION));
		menu.add(getAction(DETAIL_SELECT_ALL_ACTION));
		menu.add(new Separator());
		menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));

	}

	/**
	 * Creates the actions to add to the context menu
	 */
	private void createActions() {
		TextViewerAction textAction = new TextViewerAction(fSourceViewer, ITextOperationTarget.SELECT_ALL);
		textAction.configureAction(MessagesForDetailPane.DetailPane_Select_All, "", ""); //$NON-NLS-1$ //$NON-NLS-2$
		textAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.SELECT_ALL);
		PlatformUI.getWorkbench().getHelpSystem().setHelp(textAction,
				IDsfDebugHelpContextIds.DETAIL_PANE_SELECT_ALL_ACTION);
		setAction(DETAIL_SELECT_ALL_ACTION, textAction);

		textAction = new TextViewerAction(fSourceViewer, ITextOperationTarget.COPY);
		textAction.configureAction(MessagesForDetailPane.DetailPane_Copy, "", ""); //$NON-NLS-1$ //$NON-NLS-2$
		textAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.COPY);
		PlatformUI.getWorkbench().getHelpSystem().setHelp(textAction, IDsfDebugHelpContextIds.DETAIL_PANE_COPY_ACTION);
		setAction(DETAIL_COPY_ACTION, textAction);

		setSelectionDependantAction(DETAIL_COPY_ACTION);

		updateSelectionDependentActions();
	}

	/**
	 * Job to compute the details for a selection
	 */
	class DetailJob extends Job {

		private Object fElement;
		// whether a result was collected
		private IProgressMonitor fMonitor;

		public DetailJob(Object element) {
			super("compute module details"); //$NON-NLS-1$
			setSystem(true);
			fElement = element;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
		 */
		@Override
		protected IStatus run(IProgressMonitor monitor) {
			fMonitor = monitor;
			/*
			 *  Make sure this is an element we want to deal with.
			 */
			IModuleDMContext dmc = null;
			if (fElement instanceof IDMVMContext) {
				IDMContext vmcdmc = ((IDMVMContext) fElement).getDMContext();
				dmc = DMContexts.getAncestorOfType(vmcdmc, IModuleDMContext.class);
			}

			if (dmc == null)
				return Status.OK_STATUS;
			DsfSession session = DsfSession.getSession(dmc.getSessionId());
			if (session == null)
				return Status.OK_STATUS;

			/*
			 * Create the query to write the value to the service. Note: no need to
			 * guard against RejectedExecutionException, because
			 * DsfSession.getSession() above would only return an active session.
			 */
			GetModuleDetailsQuery query = new GetModuleDetailsQuery(dmc);
			session.getExecutor().execute(query);

			/*
			 * Now we have the data, go and get it. Since the call is completed now
			 * the ".get()" will not suspend it will immediately return with the
			 * data.
			 */
			try {
				detailComputed(getModuleDetail((IModuleDMData) query.get()));
			} catch (InterruptedException e) {
				assert false;
				return Status.OK_STATUS;
			} catch (ExecutionException e) {
				return Status.OK_STATUS;
			}
			return Status.OK_STATUS;
		}

		/**
		 * Set the module details in the detail pane view
		 * @param result
		 */
		private void detailComputed(final String result) {
			if (!fMonitor.isCanceled()) {
				WorkbenchJob setDetail = new WorkbenchJob("set details") { //$NON-NLS-1$
					@Override
					public IStatus runInUIThread(IProgressMonitor monitor) {
						if (!fMonitor.isCanceled()) {
							getDetailDocument().set(result);
						}
						return Status.OK_STATUS;
					}
				};
				setDetail.setSystem(true);
				setDetail.schedule();
			}
		}

	}

	/**
	 * To get the details of the given module selected in Modules View
	 * @param module
	 * @return
	 */
	private String getModuleDetail(IModuleDMData module) {
		StringBuilder sb = new StringBuilder();

		// Type
		String type = null;
		//        switch( module.getType() ) {
		//            case ICModule.EXECUTABLE:
		//                type = ModulesMessages.getString( "ModulesView.Executable" ); //$NON-NLS-1$
		//                break;
		//            case ICModule.SHARED_LIBRARY:
		//                type = ModulesMessages.getString( "ModulesView.SharedLibrary" ); //$NON-NLS-1$
		//                break;
		//        }
		type = ModulesMessages.getString("ModulesView.SharedLibrary"); //$NON-NLS-1$
		if (type != null) {
			sb.append(ModulesMessages.getString("ModulesView.Type")); //$NON-NLS-1$
			sb.append(type);
			sb.append('\n');
		}

		// Symbols flag
		sb.append(ModulesMessages.getString("ModulesView.Symbols")); //$NON-NLS-1$
		sb.append((module.isSymbolsLoaded()) ? ModulesMessages.getString("ModulesView.Loaded") //$NON-NLS-1$
				: ModulesMessages.getString("ModulesView.NotLoaded")); //$NON-NLS-1$
		sb.append('\n');

		// Symbols file
		sb.append(ModulesMessages.getString("ModulesView.SymbolsFile")); //$NON-NLS-1$
		sb.append(module.getFile());
		sb.append('\n');

		// Base address
		String baseAddress = module.getBaseAddress();
		sb.append(ModulesMessages.getString("ModulesView.BaseAddress")); //$NON-NLS-1$
		sb.append(baseAddress);
		sb.append('\n');

		// Size
		long size = module.getSize();
		if (size > 0) {
			sb.append(ModulesMessages.getString("ModulesView.Size")); //$NON-NLS-1$
			sb.append(size);
			sb.append('\n');
		}

		return sb.toString();
	}

	public class GetModuleDetailsQuery extends Query<Object> {

		private IModuleDMContext fDmc;

		public GetModuleDetailsQuery(IModuleDMContext dmc) {
			super();
			fDmc = dmc;
		}

		@Override
		protected void execute(final DataRequestMonitor<Object> rm) {
			/*
			 * We're in another dispatch, so we must guard against executor
			 * shutdown again.
			 */
			final DsfSession session = DsfSession.getSession(fDmc.getSessionId());
			if (session == null) {
				rm.setStatus(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE,
						"Debug session already shut down.", null)); //$NON-NLS-1$
				rm.done();
				return;
			}

			/*
			 * Guard against a disposed service
			 */
			DsfServicesTracker tracker = new DsfServicesTracker(DsfUIPlugin.getBundleContext(), fDmc.getSessionId());
			IModules service = tracker.getService(IModules.class);
			tracker.dispose();
			if (service == null) {
				rm.setStatus(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE,
						"Service unavailable", null)); //$NON-NLS-1$
				rm.done();
				return;
			}

			service.getModuleData(fDmc, new DataRequestMonitor<IModuleDMData>(session.getExecutor(), rm) {
				@Override
				protected void handleCompleted() {
					/*
					 * We're in another dispatch, so we must guard against executor shutdown again.
					 */
					if (!DsfSession.isSessionActive(session.getId())) {
						rm.setStatus(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE,
								"Debug session already shut down.", null)); //$NON-NLS-1$
						rm.done();
						return;
					}
					super.handleCompleted();
				}

				@Override
				protected void handleSuccess() {
					/*
					 * All good set return value.
					 */
					rm.setData(getData());
					rm.done();
				}
			});
		}
	}
}
