/*******************************************************************************
 * 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
 *******************************************************************************/
package org.eclipse.pde.api.tools.ui.internal.wizards;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;
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.pde.core.target.ITargetDefinition;
import org.eclipse.pde.core.target.ITargetHandle;
import org.eclipse.pde.core.target.ITargetPlatformService;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.PlatformUI;

/**
 * Define a baseline from a Target Definition
 *
 * @since 1.0.600
 */
public class TargetBasedApiBaselineWizardPage extends ApiBaselineWizardPage {
	public static boolean isApplicable(IApiBaseline profile) {
		return ApiModelFactory.isDerivedFromTarget(profile);
	}

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

		/**
		 * Constructor
		 */
		public ReloadTargetOperation(ITargetDefinition definition, String name) {
			this.definition = definition;
			this.name = name;
		}

		@Override
		public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
			try {
				fProfile = ApiModelFactory.newApiBaselineFromTarget(name, definition, monitor);
				TargetBasedApiBaselineWizardPage.this.contentchange = true;
			} catch (CoreException e) {
				// error reported via the definition's status in pageValid()
				ApiPlugin.log(e);
			}
		}
	}

	private static class TargetLabelProvider extends StyledCellLabelProvider {
		@SuppressWarnings("restriction")
		private final org.eclipse.pde.internal.ui.PDELabelProvider pdeLabelProvider = org.eclipse.pde.internal.ui.PDEPlugin
				.getDefault().getLabelProvider();

		public TargetLabelProvider() {
			pdeLabelProvider.connect(this);
		}

		@Override
		@SuppressWarnings("restriction")
		public void update(ViewerCell cell) {
			final Object element = cell.getElement();

			ITargetDefinition targetDef = (ITargetDefinition) element;
			ITargetHandle targetHandle = targetDef.getHandle();
			String name = targetDef.getName();
			if (name == null || name.length() == 0) {
				name = targetHandle.toString();
			}

			StyledString styledString = new StyledString(name);
			if (targetHandle instanceof org.eclipse.pde.internal.core.target.WorkspaceFileTargetHandle) {
				IFile file = ((org.eclipse.pde.internal.core.target.WorkspaceFileTargetHandle) targetHandle)
						.getTargetFile();
				String location = " - " + file.getFullPath(); //$NON-NLS-1$
				styledString.append(location, StyledString.DECORATIONS_STYLER);
			} else if (targetHandle instanceof org.eclipse.pde.internal.core.target.ExternalFileTargetHandle) {
				URI uri = ((org.eclipse.pde.internal.core.target.ExternalFileTargetHandle) targetHandle).getLocation();
				String location = " - " + uri.toASCIIString(); //$NON-NLS-1$
				styledString.append(location, StyledString.DECORATIONS_STYLER);
			}

			cell.setText(styledString.toString());
			cell.setStyleRanges(styledString.getStyleRanges());
			cell.setImage(getImage());
			super.update(cell);
		}

		@SuppressWarnings("restriction")
		public Image getImage() {
			return pdeLabelProvider.get(org.eclipse.pde.internal.ui.PDEPluginImages.DESC_TARGET_DEFINITION);
		}

		@Override
		public void dispose() {
			pdeLabelProvider.disconnect(this);
			super.dispose();
		}
	}

	/**
	 * Initial collection of targets (handles are realized into definitions as
	 * working copies)
	 */
	private final List<ITargetDefinition> fTargets = new ArrayList<>();

	private ITargetDefinition selectedTargetDefinition;

	/**
	 * widgets
	 */
	private Text nametext = null;
	private CheckboxTableViewer targetsViewer;
	private TreeViewer treeviewer = null;
	private Button reloadbutton = null;

	public TargetBasedApiBaselineWizardPage(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.FILL_HORIZONTAL | GridData.BEGINNING);
		nametext.addModifyListener(e -> setPageComplete(pageValid()));

		SWTFactory.createVerticalSpacer(comp, 1);
		targetsViewer = CheckboxTableViewer.newCheckList(comp, SWT.MULTI | SWT.BORDER);
		GridDataFactory.fillDefaults().span(3, 1).grab(true, true).applyTo(targetsViewer.getControl());

		targetsViewer.setLabelProvider(new TargetLabelProvider());
		targetsViewer.setContentProvider(ArrayContentProvider.getInstance());
		targetsViewer.addCheckStateListener(event -> {
			if (event.getChecked()) {
				targetsViewer.setCheckedElements(new Object[] { event.getElement() });
				selectedTargetDefinition = (ITargetDefinition) event.getElement();
			} else {
				selectedTargetDefinition = null;
			}
			updateButtons();
			setPageComplete(pageValid());
		});

		// add the targets
		ITargetPlatformService service = getTargetService();
		if (service != null) {
			for (ITargetHandle handle : service.getTargets(null)) {
				try {
					fTargets.add(handle.getTargetDefinition());
				} catch (CoreException e) {
					PDECore.log(e);
				}
			}
			if (fTargets.isEmpty()) {
				// Ensure we have at least a default TP
				try {
					fTargets.add(service.getWorkspaceTargetDefinition());
				} catch (CoreException e) {
					PDECore.log(e);
				}
			}
			targetsViewer.setInput(fTargets);
		}

		targetsViewer.setComparator(new ViewerComparator() {
			@Override
			public int compare(Viewer viewer, Object e1, Object e2) {
				String name1 = ((ITargetDefinition) e1).getName();
				String name2 = ((ITargetDefinition) e2).getName();
				if (name1 == null) {
					return -1;
				}
				if (name2 == null) {
					return 1;
				}
				return name1.compareToIgnoreCase(name2);
			}
		});

		reloadbutton = SWTFactory.createPushButton(comp, WizardMessages.ApiProfileWizardPage_12, null);
		reloadbutton.setEnabled(targetsViewer.getCheckedElements().length == 1);
		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 = 100;
		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);
		if (fProfile != null) {
			nametext.setText(fProfile.getName());

			ITargetDefinition found = null;
			// Use isDerivedFromTarget as the target may have had its
			// sequence number bumped several times
			for (ITargetDefinition target : fTargets) {
				if (ApiModelFactory.isDerivedFromTarget(fProfile, target)) {
					found = target;
					break;
				}
			}
			if (found != null) {
				selectedTargetDefinition = found;
				targetsViewer.setCheckedElements(new Object[] { found });
			}
		}
		initialize();
		updateButtons();
		setPageComplete(pageValid());
		PlatformUI.getWorkbench().getHelpSystem().setHelp(comp, IApiToolsHelpContextIds.APIPROFILES_WIZARD_PAGE);
		Dialog.applyDialogFont(comp);
	}

	/**
	 * @return if the page is valid, such that it is considered complete and can
	 *         be 'finished'
	 */
	protected boolean pageValid() {
		setErrorMessage(null);
		// If a target is selected then report any resolving problems before
		// any missing-name errors. It's strange to click on 'reset' and see
		// the progress bar and having any resolving errors masked by a
		// missing-name or no-bundles-error
		ITargetDefinition selected = getSelectedTargetDefinition();
		if (selected != null && selected.getStatus() != null && !selected.getStatus().isOK()) {
			setErrorMessage(WizardMessages.TargetBasedApiBaselineWizardPage_loading_error);
			return false;
		}
		if (!isNameValid(nametext.getText().trim())) {
			return false;
		}
		if(targetsViewer.getCheckedElements().length != 1) {
			setErrorMessage(WizardMessages.ApiProfileWizardPage_select_target);
			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 (!ApiModelFactory.isUpToDateWithTarget(fProfile, selected)) {
				setErrorMessage(WizardMessages.ApiProfileWizardPage_location_needs_reset);
				return false;
			}
		} else {
			setErrorMessage(WizardMessages.ApiProfileWizardPage_location_needs_reset);
			return false;
		}
		return true;
	}

	private ITargetDefinition getSelectedTargetDefinition() {
		// don't query the table directly as may be called from non-UI thread
		return selectedTargetDefinition;
	}

	/**
	 * Updates the state of a variety of buttons on this page
	 */
	protected void updateButtons() {
		reloadbutton.setEnabled(getSelectedTargetDefinition() != null);
	}

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

	/**
	 * Reloads all of the plugins from the location specified in the location
	 * text field.
	 */
	protected void doReload() {
		IRunnableWithProgress op = new ReloadTargetOperation(getSelectedTargetDefinition(), nametext.getText().trim());
		try {
			getContainer().run(true, true, op);
			treeviewer.setInput(getCurrentComponents());
			treeviewer.refresh();
			setPageComplete(pageValid());
		} catch (InvocationTargetException | InterruptedException ie) {
		}
	}

	private ITargetPlatformService getTargetService() {
		return ApiPlugin.getDefault().acquireService(ITargetPlatformService.class);
	}
}
