/*******************************************************************************
 * Copyright (c) 2000, 2017 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.team.internal.ui.synchronize;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.team.internal.ui.Policy;
import org.eclipse.team.internal.ui.Utils;
import org.eclipse.team.internal.ui.synchronize.actions.DirectionFilterActionGroup;
import org.eclipse.team.ui.synchronize.ISynchronizePage;
import org.eclipse.team.ui.synchronize.ISynchronizePageConfiguration;
import org.eclipse.team.ui.synchronize.ISynchronizePageSite;
import org.eclipse.team.ui.synchronize.SynchronizePageActionGroup;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.IShowInTargetList;
import org.eclipse.ui.part.Page;
import org.eclipse.ui.part.ShowInContext;

/**
 * Abstract synchronize page that can filter changes by mode (incoming, outgoing,
 * both or conflicting). It also uses forms to indicate when a model is empty and
 * provide a link to a non-empty mode.
 */
public abstract class AbstractSynchronizePage extends Page implements ISynchronizePage, IAdaptable {

	private ISynchronizePageConfiguration configuration;
	private ISynchronizePageSite site;

	// Parent composite of this view. It is remembered so that we can dispose of its children when
	// the viewer type is switched.
	private Composite composite;
	private ChangesSection changesSection;
	private Viewer changesViewer;

	private AbstractViewerAdvisor viewerAdvisor;

	/*
	 * Contribute actions for changing modes to the page.
	 */
	class ModeFilterActions extends SynchronizePageActionGroup {
		private DirectionFilterActionGroup modes;
		@Override
		public void initialize(ISynchronizePageConfiguration configuration) {
			super.initialize(configuration);
			if (isThreeWay()) {
				modes = new DirectionFilterActionGroup(configuration);
			}
		}
		@Override
		public void fillActionBars(IActionBars actionBars) {
			super.fillActionBars(actionBars);
			if (modes == null) return;
			IToolBarManager manager = actionBars.getToolBarManager();
			IContributionItem group = findGroup(manager, ISynchronizePageConfiguration.MODE_GROUP);
			if (manager != null && group != null) {
				modes.fillToolBar(group.getId(), manager);
			}
			IMenuManager viewMenu = actionBars.getMenuManager();
			group = findGroup(manager, ISynchronizePageConfiguration.MODE_GROUP);
			if (viewMenu != null && group != null) {
				IContributionItem layoutGroup = findGroup(manager, ISynchronizePageConfiguration.LAYOUT_GROUP);
				if (layoutGroup != null) {
					// Put the modes in the layout group to save space
					group = layoutGroup;
				}
				MenuManager modesItem = new MenuManager(Utils.getString("action.modes.label", Policy.getActionBundle())); //$NON-NLS-1$
				viewMenu.appendToGroup(group.getId(), modesItem);
				modes.fillMenu(modesItem);
			}
		}
		private boolean isThreeWay() {
			return ISynchronizePageConfiguration.THREE_WAY.equals(configuration.getComparisonType());
		}
	}

	/**
	 * Create a new instance of the page
	 * @param configuration a synchronize page configuration
	 */
	protected AbstractSynchronizePage(ISynchronizePageConfiguration configuration) {
		this.configuration = configuration;
		configuration.setPage(this);
		configuration.addActionContribution(new ModeFilterActions());
	}

	@Override
	public void createControl(Composite parent) {
		composite = new Composite(parent, SWT.NONE);
		//sc.setContent(composite);
		GridLayout gridLayout= new GridLayout();
		gridLayout.makeColumnsEqualWidth= false;
		gridLayout.marginWidth= 0;
		gridLayout.marginHeight = 0;
		gridLayout.verticalSpacing = 0;
		composite.setLayout(gridLayout);
		GridData data = new GridData(GridData.FILL_BOTH);
		data.grabExcessVerticalSpace = true;
		composite.setLayoutData(data);

		// Create the changes section which, in turn, creates the changes viewer and its configuration
		this.changesSection = createChangesSection(composite);
		createChangesViewer(changesSection.getContainer());
		changesSection.setViewer(changesViewer);
	}

	/**
	 * Create the changes section that will contain the changes viewer.
	 * @return the changes section that will contain the changes viewer
	 */
	protected abstract ChangesSection createChangesSection(Composite parent);

	/**
	 * Return the viewer that will display the changes associated
	 * with the page.
	 *
	 * @param parent the parent of the viewer
	 */
	private void createChangesViewer(Composite parent) {
		viewerAdvisor = createViewerAdvisor(parent);
		changesViewer = viewerAdvisor.getViewer();
		viewerAdvisor.setInitialInput();
	}

	protected abstract AbstractViewerAdvisor createViewerAdvisor(Composite parent);

	public AbstractViewerAdvisor getViewerAdvisor() {
		return viewerAdvisor;
	}

	@Override
	public void setActionBars(IActionBars actionBars) {
		// Delegate menu creation to the advisor
		viewerAdvisor.setActionBars(actionBars);
	}

	@Override
	public Control getControl() {
		return composite;
	}

	@Override
	public void setFocus() {
		changesSection.setFocus();
	}

	@Override
	public void init(ISynchronizePageSite site) {
		this.site = site;
		IDialogSettings settings = getSettings();
		if (settings != null) {
			try {
				int mode = settings.getInt(ISynchronizePageConfiguration.P_MODE);
				if (mode != 0) {
					configuration.setMode(mode);
				}
			} catch (NumberFormatException e) {
				// The mode settings does not exist.
				// Leave the mode as is (assuming the
				// participant initialized it to an
				// appropriate value
			}
		}
	}

	@Override
	public void dispose() {
		changesSection.dispose();
		composite.dispose();
		super.dispose();
	}

	@Override
	public Viewer getViewer() {
		return changesViewer;
	}

	@Override
	public boolean aboutToChangeProperty(
			ISynchronizePageConfiguration configuration, String key,
			Object newValue) {
		if (key.equals(ISynchronizePageConfiguration.P_MODE)) {
			return (internalSetMode(configuration.getMode(), ((Integer)newValue).intValue()));
		}
		return true;
	}

	private boolean internalSetMode(int oldMode, int mode) {
		if(oldMode == mode) return false;
		updateMode(mode);
		IDialogSettings settings = getSettings();
		if (settings != null) {
			settings.put(ISynchronizePageConfiguration.P_MODE, mode);
		}
		return true;
	}

	/*
	 * This method enables "Show In" support for this view
	 *
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public <T> T getAdapter(Class<T> key) {
		if (key.equals(ISelectionProvider.class))
			return (T) changesViewer;
		if (key == IShowInSource.class) {
			return (T) (IShowInSource) () -> {
				StructuredViewer v = (StructuredViewer)changesViewer;
				if (v == null) return null;
				ISelection s = v.getSelection();
				if (s instanceof IStructuredSelection) {
					Object[] resources = Utils.getResources(((IStructuredSelection)s).toArray());
					return new ShowInContext(null, new StructuredSelection(resources));
				}
				return null;
			};
		}
		if (key == IShowInTargetList.class) {
			return (T) (IShowInTargetList) () -> new String[] { IPageLayout.ID_RES_NAV };
		}
		return null;
	}

	/**
	 * Return the page site that was assigned to this page.
	 * @return the page site that was assigned to this page
	 */
	public ISynchronizePageSite getSynchronizePageSite() {
		return site;
	}

	/**
	 * Return the synchronize page configuration that was used to create
	 * this page.
	 * @return Returns the configuration.
	 */
	public ISynchronizePageConfiguration getConfiguration() {
		return configuration;
	}

	/**
	 * Return the settings for the page from the configuration
	 * os <code>null</code> if settings can not be persisted
	 * for the page
	 * @return the persisted page settings
	 */
	protected IDialogSettings getSettings() {
		return configuration.getSite().getPageSettings();
	}

	/**
	 * Callback from the changes section that indicates that the
	 * user has chosen to reset the view contents after an error
	 * has occurred
	 */
	public abstract void reset();

	/**
	 * Change the mode to the given mode. This method is invoked
	 * when the mode in the configuration is changed by a client.
	 * @param mode the mode to be used
	 */
	protected abstract void updateMode(int mode);

	public ChangesSection getChangesSection() {
		return changesSection;
	}
}
