/*******************************************************************************
 * Copyright (c) 2007, 2018 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
 *     Manumitting Technologies Inc - bug 324310
 *     Simon Scholz <simon.scholz@vogella.com> - Bug 484445
 *******************************************************************************/
package org.eclipse.pde.api.tools.ui.internal.wizards;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.pde.api.tools.internal.model.ApiModelFactory;
import org.eclipse.pde.api.tools.internal.model.SystemLibraryApiComponent;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
import org.eclipse.pde.api.tools.ui.internal.ApiToolsLabelProvider;
import org.eclipse.pde.api.tools.ui.internal.IApiToolsHelpContextIds;
import org.eclipse.pde.api.tools.ui.internal.SWTFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.PlatformUI;

/**
 * The wizard page allowing a new directory-based API profiles to be created or
 * an existing one to be edited.
 *
 * @since 1.0.0
 */
public class DirectoryBasedApiBaselineWizardPage extends ApiBaselineWizardPage {
	public static boolean isApplicable(IApiBaseline profile) {
		String loc = profile.getLocation();
		return loc != null && IPath.fromOSString(loc).toFile().exists();
	}

	/**
	 * Resets the baseline contents based on current settings and a location
	 * from which to read plug-ins.
	 */
	class ReloadOperation implements IRunnableWithProgress {
		private final String location, name;

		/**
		 * Constructor
		 */
		public ReloadOperation(String name, String location) {
			this.location = location;
			this.name = name;
		}

		@Override
		public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
			monitor.beginTask(WizardMessages.ApiProfileWizardPage_0, 10);
			try {
				fProfile = ApiModelFactory.newApiBaseline(name, location);
				ApiModelFactory.addComponents(fProfile, location, monitor);
				DirectoryBasedApiBaselineWizardPage.this.contentchange = true;
			} catch (CoreException e) {
				ApiPlugin.log(e);
			} finally {
				monitor.done();
			}
		}

	}


	/**
	 * widgets
	 */
	private Text nametext = null;
	private TreeViewer treeviewer = null;
	Combo locationcombo = null;
	private Button browsebutton = null, reloadbutton = null;

	/**
	 * We need to know if we are initializing the page to not respond to changed
	 * events causing validation when the wizard opens.
	 *
	 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=266597
	 */
	private boolean initializing = false;

	/**
	 * Constructor
	 */
	protected DirectoryBasedApiBaselineWizardPage(IApiBaseline profile) {
		super(profile);
	}

	@Override
	public void createControl(Composite parent) {
		Composite comp = SWTFactory.createComposite(parent, 4, 1, GridData.FILL_HORIZONTAL);
		SWTFactory.createWrapLabel(comp, WizardMessages.ApiProfileWizardPage_5, 1);
		nametext = SWTFactory.createText(comp, SWT.BORDER | SWT.SINGLE, 3, GridData.GRAB_HORIZONTAL | GridData.FILL_HORIZONTAL | GridData.BEGINNING);
		nametext.addModifyListener(e -> setPageComplete(pageValid()));

		SWTFactory.createVerticalSpacer(comp, 1);

		SWTFactory.createWrapLabel(comp, WizardMessages.ApiProfileWizardPage_9, 1);
		locationcombo = SWTFactory.createCombo(comp, SWT.BORDER | SWT.SINGLE, 1, GridData.GRAB_HORIZONTAL | GridData.FILL_HORIZONTAL | GridData.BEGINNING, null);
		locationcombo.addModifyListener(e -> {
			setPageComplete(pageValid());
			updateButtons();
		});
		browsebutton = SWTFactory.createPushButton(comp, WizardMessages.ApiProfileWizardPage_10, null);
		browsebutton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
			DirectoryDialog dialog = new DirectoryDialog(getShell());
			dialog.setMessage(WizardMessages.ApiProfileWizardPage_11);
			String loctext = locationcombo.getText().trim();
			if (loctext.length() > 0) {
				dialog.setFilterPath(loctext);
			}
			String newPath = dialog.open();
			if (newPath != null
					&& (!IPath.fromOSString(loctext).equals(IPath.fromOSString(newPath)) || getCurrentComponents().length == 0)) {
				/*
				 * If the path is identical, but there is no component loaded, we still want to
				 * reload. This might be the case if the combo is initialized by copy/paste with
				 * a path that points to a plugin directory
				 */
				String updatedLocation = getUpdatedLocationForMacOS(newPath);
				locationcombo.setText(updatedLocation);
				// If current name matches with the last segment of old location then
				// update the current name with last segment of new location
				if (nametext.getText().equals(IPath.fromOSString(loctext).lastSegment())) {
					nametext.setText(IPath.fromOSString(updatedLocation).lastSegment() == null ? "" //$NON-NLS-1$
							: IPath.fromOSString(updatedLocation).lastSegment());
					nametext.setFocus();
					nametext.selectAll();
				}

				setErrorMessage(null);
				doReload();
			}
		}));

		reloadbutton = SWTFactory.createPushButton(comp, WizardMessages.ApiProfileWizardPage_12, null);
		reloadbutton.setEnabled(locationcombo.getText().trim().length() > 0);
		reloadbutton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> doReload()));

		SWTFactory.createWrapLabel(comp, WizardMessages.ApiProfileWizardPage_13, 4);
		Tree tree = new Tree(comp, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
		GridData gd = new GridData(GridData.FILL_BOTH);
		gd.heightHint = 250;
		gd.horizontalSpan = 4;
		tree.setLayoutData(gd);
		treeviewer = new TreeViewer(tree);
		treeviewer.setLabelProvider(new ApiToolsLabelProvider());
		treeviewer.setContentProvider(new ContentProvider());
		treeviewer.setComparator(new ViewerComparator());
		treeviewer.setInput(getCurrentComponents());
		treeviewer.addSelectionChangedListener(event -> updateButtons());
		treeviewer.addFilter(new ViewerFilter() {
			@Override
			public boolean select(Viewer viewer, Object parentElement, Object element) {
				if (element instanceof IApiComponent) {
					IApiComponent component = (IApiComponent) element;
					try {
						if (component.isSourceComponent() || component.isSystemComponent()) {
							return false;
						}
					} catch (CoreException e) {
						ApiPlugin.log(e);
					}
					return true;
				}
				return !(element instanceof SystemLibraryApiComponent);
			}
		});

		setControl(comp);
		setPageComplete(fProfile != null);
		initialize();
		PlatformUI.getWorkbench().getHelpSystem().setHelp(comp, IApiToolsHelpContextIds.APIPROFILES_WIZARD_PAGE);
		Dialog.applyDialogFont(comp);
	}

	private String getUpdatedLocationForMacOS(String newPath) {
		String newLoc = newPath;
		// On Mac OS, if user selects *.app, then location
		// should be reset to *.app/Contents/Eclipse if that exists
		if (System.getProperty("os.name").startsWith("Mac")) {//$NON-NLS-1$ //$NON-NLS-2$
			IPath nPath = IPath.fromOSString(newPath);
			if (nPath.lastSegment() == null) {
				return newLoc;
			}
			if (nPath.lastSegment().endsWith(".app")) { //$NON-NLS-1$
				IPath appendedPath = nPath.append("Contents").append("Eclipse");//$NON-NLS-1$ //$NON-NLS-2$
				if (appendedPath.toFile().exists()) {
					newLoc = appendedPath.toOSString();
				}
			}
		}
		return newLoc;
	}
	/**
	 * Initializes the controls of the page if the profile is not
	 * <code>null</code>
	 */
	@Override
	protected void initialize() {
		initializing = true;
		try {
			super.initialize();
			if (fProfile != null) {
				nametext.setText(fProfile.getName());
				IApiComponent[] components = fProfile.getApiComponents();
				Set<String> locations = new HashSet<>();
				String loc = fProfile.getLocation();
				if (loc != null) {
					IPath location = IPath.fromOSString(loc);
					// check if the location is a file
					if (location.toFile().isDirectory()) {
						locations.add(location.removeTrailingSeparator().toOSString());
					}
				} else {
					for (IApiComponent component : components) {
						if (!component.isSystemComponent()) {
							IPath location = IPath.fromOSString(component.getLocation()).removeLastSegments(1);
							if (location.toFile().isDirectory()) {
								locations.add(location.removeTrailingSeparator().toOSString());
							}
						}
					}
				}
				if (!locations.isEmpty()) {
					locationcombo.setItems(locations.toArray(new String[locations.size()]));
					locationcombo.select(0);
				}
			} else {
				// try to set the default location to be the current install
				// directory
				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=258969
				Location location = Platform.getInstallLocation();
				if (location != null) {
					URL url = location.getURL();
					IPath path = IPath.fromOSString(url.getFile()).removeTrailingSeparator();
					if (path.toFile().exists()) {
						locationcombo.add(path.toOSString());
						locationcombo.select(0);
					}
				}
			}
		} finally {
			initializing = false;
		}
	}

	/**
	 * Reloads all of the plugins from the location specified in the location
	 * text field.
	 */
	protected void doReload() {
		String location = locationcombo.getText().trim();
		IRunnableWithProgress op = new ReloadOperation(nametext.getText().trim(), location);
		try {
			getContainer().run(true, true, op);
			// If no name has been set, the directories' name is taken as name.
			if (nametext.getText().isEmpty()) {
				nametext.setText(Paths.get(location).getFileName() == null ? "" //$NON-NLS-1$
						: String.valueOf(Paths.get(location).getFileName()));
			}
			treeviewer.setInput(getCurrentComponents());
			treeviewer.refresh();
			setPageComplete(pageValid());
			nametext.setFocus();
			nametext.selectAll();
		} catch (InvocationTargetException | InterruptedException ie) {
		}
	}

	@Override
	public IApiBaseline finish() throws IOException, CoreException {
		if (fProfile != null) {
			fProfile.setName(nametext.getText().trim());
		}
		return fProfile;
	}

	/**
	 * @return if the page is valid, such that it is considered complete and can
	 *         be 'finished'
	 */
	protected boolean pageValid() {
		if (initializing) {
			return false;
		}
		setErrorMessage(null);
		if (!isNameValid(nametext.getText().trim())) {
			return false;
		}
		String text = locationcombo.getText().trim();
		String updatedText = getUpdatedLocationForMacOS(text);
		if (!updatedText.equals(text)) {
			locationcombo.setText(updatedText);
		}
		if (text.length() < 1) {
			setErrorMessage(WizardMessages.ApiProfileWizardPage_23);
			reloadbutton.setEnabled(false);
			return false;
		}
		if (!IPath.fromOSString(text).toFile().exists()) {
			setErrorMessage(WizardMessages.ApiProfileWizardPage_24);
			reloadbutton.setEnabled(false);
			return false;
		}
		if (fProfile != null) {
			if (fProfile.getApiComponents().length == 0) {
				setErrorMessage(WizardMessages.ApiProfileWizardPage_2);
				return false;
			}
			IStatus status = fProfile.getExecutionEnvironmentStatus();
			if (status.getSeverity() == IStatus.ERROR) {
				setErrorMessage(status.getMessage());
				return false;
			}
			if (fProfile.getLocation() != null && !fProfile.getLocation().equals(locationcombo.getText())) {
				setErrorMessage(WizardMessages.ApiProfileWizardPage_location_needs_reset);
				return false;
			}
		} else {
			setErrorMessage(WizardMessages.ApiProfileWizardPage_location_needs_reset);
			return false;
		}
		return true;
	}

	/**
	 * Updates the state of a variety of buttons on this page
	 */
	protected void updateButtons() {
		String loctext = locationcombo.getText().trim();
		reloadbutton.setEnabled(loctext.length() > 0);
	}

}
