/*******************************************************************************
 * Copyright (c) 2005, 2008 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
 *******************************************************************************/
package org.eclipse.ltk.internal.ui.refactoring.history;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

import org.osgi.service.prefs.BackingStoreException;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;

import org.eclipse.core.runtime.Assert;
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.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IScopeContext;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;

import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.preference.IPreferencePageContainer;
import org.eclipse.jface.preference.IPreferenceStore;

import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PropertyPage;
import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;
import org.eclipse.ui.preferences.IWorkingCopyManager;
import org.eclipse.ui.preferences.WorkingCopyManager;

import org.eclipse.ltk.core.refactoring.RefactoringCore;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptorProxy;
import org.eclipse.ltk.core.refactoring.history.IRefactoringHistoryService;
import org.eclipse.ltk.core.refactoring.history.RefactoringHistory;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;
import org.eclipse.ltk.internal.core.refactoring.RefactoringPreferenceConstants;
import org.eclipse.ltk.internal.core.refactoring.history.RefactoringHistoryService;
import org.eclipse.ltk.internal.ui.refactoring.BasicElementLabels;
import org.eclipse.ltk.internal.ui.refactoring.IRefactoringHelpContextIds;
import org.eclipse.ltk.internal.ui.refactoring.Messages;
import org.eclipse.ltk.internal.ui.refactoring.RefactoringUIMessages;
import org.eclipse.ltk.internal.ui.refactoring.RefactoringUIPlugin;
import org.eclipse.ltk.internal.ui.refactoring.WorkbenchRunnableAdapter;
import org.eclipse.ltk.ui.refactoring.history.RefactoringHistoryControlConfiguration;

/**
 * Property page for a project's refactoring history.
 *
 * @since 3.2
 */
public final class RefactoringPropertyPage extends PropertyPage {

	/** The dialog settings key */
	private static final String DIALOG_SETTINGS_KEY= "RefactoringPropertyPage"; //$NON-NLS-1$

	/** The empty descriptors constant */
	static final RefactoringDescriptorProxy[] EMPTY_DESCRIPTORS= new RefactoringDescriptorProxy[0];

	/** Preference key for the warn delete all preference */
	private static final String PREFERENCE_DO_NOT_WARN_DELETE_ALL= RefactoringUIPlugin.getPluginId() + ".do.not.warn.delete.history"; //$NON-NLS-1$;

	/** The sort dialog setting */
	private static final String SETTING_SORT= "org.eclipse.ltk.ui.refactoring.sortRefactorings"; //$NON-NLS-1$

	/** The refactoring preference */
	private boolean fHasProjectHistory= false;

	/** The refactoring history control */
	private ShowRefactoringHistoryControl fHistoryControl;

	/** The preferences working copy manager, or <code>null</code> */
	private IWorkingCopyManager fManager= null;

	/** Has the property page new dialog settings? */
	private boolean fNewSettings;

	/** The dialog settings, or <code>null</code> */
	private IDialogSettings fSettings= null;

	/** The share history button, or <code>null</code> */
	private Button fShareHistoryButton= null;

	/**
	 * Creates a new refactoring property page.
	 */
	public RefactoringPropertyPage() {
		noDefaultAndApplyButton();
		final IDialogSettings settings= RefactoringUIPlugin.getDefault().getDialogSettings();
		final IDialogSettings section= settings.getSection(DIALOG_SETTINGS_KEY);
		if (section == null)
			fNewSettings= true;
		else {
			fNewSettings= false;
			fSettings= section;
		}
	}

	@Override
	protected Control createContents(final Composite parent) {
		initializeDialogUnits(parent);

		final IPreferencePageContainer container= getContainer();
		if (container instanceof IWorkbenchPreferenceContainer)
			fManager= ((IWorkbenchPreferenceContainer) container).getWorkingCopyManager();
		else
			fManager= new WorkingCopyManager();

		final Composite composite= new Composite(parent, SWT.NONE);
		final GridLayout layout= new GridLayout();
		layout.marginWidth= 0;
		composite.setLayout(layout);

		fHistoryControl= new ShowRefactoringHistoryControl(composite, new RefactoringHistoryControlConfiguration(getCurrentProject(), true, false) {
			@Override
			public String getProjectPattern() {
				return RefactoringUIMessages.RefactoringPropertyPage_project_pattern;
			}
		});
		fHistoryControl.createControl();
		boolean sortProjects= true;
		final IDialogSettings settings= fSettings;
		if (settings != null)
			sortProjects= settings.getBoolean(SETTING_SORT);
		if (sortProjects)
			fHistoryControl.sortByProjects();
		else
			fHistoryControl.sortByDate();

		fHistoryControl.getDeleteAllButton().addSelectionListener(new SelectionAdapter() {

			@Override
			public final void widgetSelected(final SelectionEvent event) {
				final IProject project= getCurrentProject();
				if (project != null) {
					final IRunnableContext context= new ProgressMonitorDialog(getShell());
					final IPreferenceStore store= RefactoringUIPlugin.getDefault().getPreferenceStore();
					MessageDialogWithToggle dialog= null;
					if (!store.getBoolean(PREFERENCE_DO_NOT_WARN_DELETE_ALL) && !fHistoryControl.getInput().isEmpty()) {
						dialog= MessageDialogWithToggle.openYesNoQuestion(getShell(), RefactoringUIMessages.RefactoringPropertyPage_confirm_delete_all_caption, Messages.format(RefactoringUIMessages.RefactoringPropertyPage_confirm_delete_all_pattern, BasicElementLabels.getResourceName(project)), RefactoringUIMessages.RefactoringHistoryWizard_do_not_show_message, false, null, null);
						store.setValue(PREFERENCE_DO_NOT_WARN_DELETE_ALL, dialog.getToggleState());
					}
					if (dialog == null || dialog.getReturnCode() == IDialogConstants.YES_ID)
						promptDeleteHistory(context, project);
				}
			}
		});
		fHistoryControl.getDeleteButton().addSelectionListener(new SelectionAdapter() {

			@Override
			public final void widgetSelected(final SelectionEvent event) {
				final RefactoringDescriptorProxy[] selection= fHistoryControl.getCheckedDescriptors();
				if (selection.length > 0) {
					final IRunnableContext context= new ProgressMonitorDialog(getShell());
					final IProject project= getCurrentProject();
					if (project != null) {
						final Shell shell= getShell();
						RefactoringHistoryEditHelper.promptRefactoringDelete(shell, context, fHistoryControl, new RefactoringDescriptorDeleteQuery(shell, getCurrentProject(), selection.length), monitor -> RefactoringHistoryService.getInstance().getProjectHistory(project, monitor), selection);
					}
				}
			}
		});
		fShareHistoryButton= new Button(composite, SWT.CHECK);
		fShareHistoryButton.setText(RefactoringUIMessages.RefactoringPropertyPage_share_message);
		fShareHistoryButton.setData(RefactoringPreferenceConstants.PREFERENCE_SHARED_REFACTORING_HISTORY);

		final GridData data= new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		data.verticalIndent= convertHeightInCharsToPixels(1) / 2;
		fShareHistoryButton.setLayoutData(data);
		fShareHistoryButton.setSelection(hasSharedRefactoringHistory());

		new Label(composite, SWT.NONE);

		final IProject project= getCurrentProject();
		if (project != null) {
			IRunnableContext context= PlatformUI.getWorkbench().getActiveWorkbenchWindow();
			if (context == null)
				context= PlatformUI.getWorkbench().getProgressService();
			handleInputEvent(context, project);
		}
		applyDialogFont(composite);
		PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(), IRefactoringHelpContextIds.REFACTORING_PROPERTY_PAGE);

		return composite;
	}

	/**
	 * Returns the project currently associated with this property page.
	 *
	 * @return the currently associated project, or <code>null</code>
	 */
	private IProject getCurrentProject() {
		return getElement().getAdapter(IProject.class);
	}

	/**
	 * Returns the preferences for the specified context.
	 *
	 * @param manager
	 *            the working copy manager
	 * @param context
	 *            the scope context
	 * @return the preferences
	 */
	private IEclipsePreferences getPreferences(final IWorkingCopyManager manager, final IScopeContext context) {
		final IEclipsePreferences preferences= context.getNode(RefactoringCore.ID_PLUGIN);
		if (manager != null)
			return manager.getWorkingCopy(preferences);
		return preferences;
	}

	/**
	 * Handles the input event.
	 *
	 * @param context
	 *            the runnable context to use
	 * @param project
	 *            the project
	 */
	private void handleInputEvent(final IRunnableContext context, final IProject project) {
		Assert.isNotNull(context);
		Assert.isNotNull(project);
		try {
			context.run(false, false, monitor -> {
				final IRefactoringHistoryService service= RefactoringCore.getHistoryService();
				try {
					service.connect();
					fHistoryControl.setInput(service.getProjectHistory(project, monitor));
				} finally {
					service.disconnect();
				}
			});
		} catch (InvocationTargetException exception) {
			RefactoringUIPlugin.log(exception);
		} catch (InterruptedException exception) {
			// Do nothing
		}
	}

	/**
	 * Returns whether a project has an shared refactoring history.
	 *
	 * @return <code>true</code> if the project contains an shared project
	 *         history, <code>false</code> otherwise
	 */
	private boolean hasSharedRefactoringHistory() {
		final IProject project= getCurrentProject();
		if (project != null)
			return RefactoringHistoryService.hasSharedRefactoringHistory(project);
		return false;
	}

	@Override
	protected void performDefaults() {
		super.performDefaults();
		final IProject project= getCurrentProject();
		if (project != null)
			setPreference(fManager, new ProjectScope(project), RefactoringPreferenceConstants.PREFERENCE_SHARED_REFACTORING_HISTORY, null);
	}

	@Override
	public boolean performOk() {
		final IProject project= getCurrentProject();
		if (project == null || fManager == null)
			return true; // not contributed on a project or no control created

		if (fNewSettings) {
			final IDialogSettings settings= RefactoringUIPlugin.getDefault().getDialogSettings();
			IDialogSettings section= settings.getSection(DIALOG_SETTINGS_KEY);
			section= settings.addNewSection(DIALOG_SETTINGS_KEY);
			fSettings= section;
		}
		fSettings.put(SETTING_SORT, fHistoryControl.isSortByProjects());

		setPreference(fManager, new ProjectScope(project), RefactoringPreferenceConstants.PREFERENCE_SHARED_REFACTORING_HISTORY, Boolean.toString(fShareHistoryButton.getSelection()));
		try {
			fManager.applyChanges();
			final boolean history= RefactoringHistoryService.hasSharedRefactoringHistory(project);
			if (history != fHasProjectHistory) {
				final Job job= new Job(history ? RefactoringUIMessages.RefactoringPropertyPage_sharing_refactoring_history : RefactoringUIMessages.RefactoringPropertyPage_unsharing_refactoring_history) {

					@Override
					public final IStatus run(final IProgressMonitor monitor) {
						try {
							RefactoringHistoryService.setSharedRefactoringHistory(project, history, monitor);
						} catch (CoreException exception) {
							RefactoringUIPlugin.log(exception);
							return exception.getStatus();
						}
						return Status.OK_STATUS;
					}
				};
				job.setRule(project);
				job.setPriority(Job.SHORT);
				job.schedule();
			}
		} catch (BackingStoreException exception) {
			RefactoringUIPlugin.log(exception);
		}
		return super.performOk();
	}

	/**
	 * Prompts the user to delete the project refactoring history.
	 *
	 * @param context
	 *            the runnable context to use
	 * @param project
	 *            the project to delete its refactoring history
	 */
	protected void promptDeleteHistory(final IRunnableContext context, final IProject project) {
		Assert.isNotNull(context);
		Assert.isNotNull(project);
		final RefactoringHistoryService service= RefactoringHistoryService.getInstance();
		try {
			service.connect();
			try {
				final Shell shell= getShell();
				context.run(false, true, new WorkbenchRunnableAdapter(monitor -> {
					try {
						monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_deleting_refactorings, 100);
						try {
							service.deleteRefactoringHistory(project, new SubProgressMonitor(monitor, 50, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL));
						} catch (CoreException exception) {
							final Throwable throwable= exception.getStatus().getException();
							if (throwable instanceof IOException) {
								shell.getDisplay().syncExec(() -> MessageDialog.openError(shell, RefactoringUIMessages.ChangeExceptionHandler_refactoring, throwable.getLocalizedMessage()));
							} else
								throw exception;
						}
						final RefactoringHistory history= service.getProjectHistory(project, new SubProgressMonitor(monitor, 50, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL));
						shell.getDisplay().syncExec(() -> {
							fHistoryControl.setInput(history);
							fHistoryControl.setCheckedDescriptors(EMPTY_DESCRIPTORS);
						});
					} finally {
						monitor.done();
					}
				}, project));
			} catch (InvocationTargetException exception) {
				RefactoringUIPlugin.log(exception);
			} catch (InterruptedException exception) {
				// Do nothing
			}
		} finally {
			service.disconnect();
		}
	}

	/**
	 * Sets the preference for a certain context.
	 *
	 * @param manager
	 *            the working copy manager
	 * @param context
	 *            the scope context
	 * @param key
	 *            the key of the preference
	 * @param value
	 *            the value of the preference
	 */
	private void setPreference(final IWorkingCopyManager manager, final IScopeContext context, final String key, final String value) {
		final IEclipsePreferences preferences= getPreferences(manager, context);
		if (value != null)
			preferences.put(key, value);
		else
			preferences.remove(key);
	}

	@Override
	public void setVisible(final boolean visible) {
		fHasProjectHistory= hasSharedRefactoringHistory();
		super.setVisible(visible);
	}
}
