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

import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.wizard.IWizardNode;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
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.swt.widgets.Label;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchWizard;
import org.eclipse.ui.activities.ITriggerPoint;
import org.eclipse.ui.activities.WorkbenchActivityHelper;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.model.AdaptableList;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.wizards.IWizardCategory;
import org.eclipse.ui.wizards.IWizardDescriptor;

/**
 * Abstract wizard page class from which an import or export wizard can be chosen.
 *
 * @since 3.2
 *
 */
public abstract class ImportExportPage extends WorkbenchWizardSelectionPage{
    protected static final String DIALOG_SETTING_SECTION_NAME = "ImportExportPage."; //$NON-NLS-1$

    // tree viewer of wizard selections
    private TreeViewer treeViewer;

	/*
	 * Class to create a control that shows a categorized tree of wizard types.
	 */
	protected static class CategorizedWizardSelectionTree {
		private static final int SIZING_LISTS_HEIGHT = 200;

		private IWizardCategory wizardCategories;
		private String message;
		private TreeViewer viewer;

		/**
		 * Constructor for CategorizedWizardSelectionTree
		 *
		 * @param categories root wizard category for the wizard type
		 * @param msg message describing what the user should choose from the tree.
		 */
		protected CategorizedWizardSelectionTree(IWizardCategory categories, String msg){
			this.wizardCategories = categories;
			this.message = msg;
		}

		/**
		 * Create the tree viewer and a message describing what the user should choose
		 * from the tree.
		 *
		 * @param parent Composite on which the tree viewer is to be created
		 * @return Comoposite with all widgets
		 */
		protected Composite createControl(Composite parent){
	        Font font = parent.getFont();

	        // create composite for page.
	        Composite outerContainer = new Composite(parent, SWT.NONE);
	        outerContainer.setLayout(new GridLayout());
	        outerContainer.setLayoutData(new GridData(GridData.FILL_BOTH));
	        outerContainer.setFont(font);

	        Label messageLabel = new Label(outerContainer, SWT.NONE);
	        if (message != null) {
				messageLabel.setText(message);
			}
	        messageLabel.setFont(font);

	        createFilteredTree(outerContainer);
	        layoutTopControl(viewer.getControl());

	        return outerContainer;
		}

		/**
		 * Create the categorized tree viewer.
		 *
		 * @param parent
		 */
		private void createFilteredTree(Composite parent){
			// Create a FilteredTree for the categories and wizards
			FilteredTree filteredTree = new FilteredTree(parent, SWT.SINGLE | SWT.H_SCROLL
	                | SWT.V_SCROLL | SWT.BORDER, new WizardPatternFilter(), true);
	        viewer = filteredTree.getViewer();
	        filteredTree.setFont(parent.getFont());
			filteredTree.setQuickSelectionMode(true);

	        viewer.setContentProvider(new WizardContentProvider());
			viewer.setLabelProvider(new WorkbenchLabelProvider());
	        viewer.setComparator(DataTransferWizardCollectionComparator.INSTANCE);

	        ArrayList inputArray = new ArrayList();
	        boolean expandTop = false;

	        if (wizardCategories != null) {
	            if (wizardCategories.getParent() == null) {
					for (IWizardCategory wizardCategory : wizardCategories.getCategories()) {
						inputArray.add(wizardCategory);
	                }
	            } else {
	                expandTop = true;
	                inputArray.add(wizardCategories);
	            }
	        }

	        // ensure the category is expanded.  If there is a remembered expansion it will be set later.
	        if (expandTop) {
				viewer.setAutoExpandLevel(2);
			}

	        AdaptableList input = new AdaptableList(inputArray);

	        // filter wizard list according to capabilities that are enabled
	        viewer.addFilter(new WizardActivityFilter());

	        viewer.setInput(input);
		}

		/**
		 *
		 * @return the categorized tree viewer
		 */
		protected TreeViewer getViewer(){
			return viewer;
		}

		/**
		 * Layout for the given control.
		 *
		 * @param control
		 */
	    private void layoutTopControl(Control control) {
	        GridData data = new GridData(GridData.FILL_BOTH);

	        int availableRows = DialogUtil.availableRows(control.getParent());

	        //Only give a height hint if the dialog is going to be too small
	        if (availableRows > 50) {
	            data.heightHint = SIZING_LISTS_HEIGHT;
	        } else {
	            data.heightHint = availableRows * 3;
	        }

	        control.setLayoutData(data);
	    }
	}

	/**
	 * Constructor for import/export wizard page.
	 *
	 * @param aWorkbench current workbench
	 * @param currentSelection current selection
	 */
	protected ImportExportPage(IWorkbench aWorkbench, IStructuredSelection currentSelection){
		super("importExportPage", aWorkbench, currentSelection, null, null);	//$NON-NLS-1$
		setTitle(WorkbenchMessages.Select);
	}

	@Override
	public void createControl(Composite parent) {
	    Font font = parent.getFont();

	    // create composite for page.
	    Composite outerContainer = new Composite(parent, SWT.NONE);
	    outerContainer.setLayout(new GridLayout());
	    outerContainer.setLayoutData(new GridData(GridData.FILL_BOTH));
	    outerContainer.setFont(font);

	    Composite comp = createTreeViewer(outerContainer);

		Dialog.applyDialogFont(comp);

	    restoreWidgetValues();

	    setControl(outerContainer);

	   initialize();
	}

	/**
	 * Create the tree viewer from which a wizard is selected.
	 */
    protected abstract Composite createTreeViewer(Composite parent);

    /**
     * Method to call when an item in one of the lists is double-clicked.
     * Shows the first page of the selected wizard or expands a collapsed
     * tree.
     * @param event
     */
    protected void treeDoubleClicked(DoubleClickEvent event){
    	ISelection selection = event.getViewer().getSelection();
	    IStructuredSelection ss = (IStructuredSelection) selection;
    	listSelectionChanged(ss);

		Object element = ss.getFirstElement();
		TreeViewer v = (TreeViewer)event.getViewer();
		if (v.isExpandable(element)) {
		    v.setExpandedState(element, !v.getExpandedState(element));
		} else if (element instanceof WorkbenchWizardElement) {
			if (canFlipToNextPage()) {
				getContainer().showPage(getNextPage());
				return;
			}
		}
        getContainer().showPage(getNextPage());
    }

    /*
     * Update the wizard's message based on the given (selected) wizard element.
     */
    private void updateSelectedNode(WorkbenchWizardElement wizardElement){
        setErrorMessage(null);
        if (wizardElement == null) {
        	updateMessage();
            setSelectedNode(null);
            return;
        }

        setSelectedNode(createWizardNode(wizardElement));
        setMessage(wizardElement.getDescription());
    }

    /*
     * Update the wizard's message based on the currently selected tab
     * and the selected wizard on that tab.
     */
    protected void updateMessage(){
    	TreeViewer viewer = getTreeViewer();
    	if (viewer != null){
    		ISelection selection = viewer.getSelection();
            IStructuredSelection ss = (IStructuredSelection) selection;
            Object sel = ss.getFirstElement();
            if (sel instanceof WorkbenchWizardElement){
               	updateSelectedNode((WorkbenchWizardElement)sel);
            }
            else{
            	setSelectedNode(null);
            }
    	} else {
			setMessage(null);
		}
    }

    /*
     * Method to call whenever the selection in one of the lists has changed.
     * Updates the wizard's message to relect the description of the currently
     * selected wizard.
     */
    protected void listSelectionChanged(ISelection selection){
        setErrorMessage(null);
        IStructuredSelection ss = (IStructuredSelection) selection;
        Object sel = ss.getFirstElement();
        if (sel instanceof WorkbenchWizardElement){
	        WorkbenchWizardElement currentWizardSelection = (WorkbenchWizardElement) sel;
	        updateSelectedNode(currentWizardSelection);
        } else {
			updateSelectedNode(null);
		}
    }

    /*
     * Create a wizard node given a wizard's descriptor.
     */
	private IWizardNode createWizardNode(IWizardDescriptor element) {
        return new WorkbenchWizardNode(this, element) {
            @Override
			public IWorkbenchWizard createWizard() throws CoreException {
                return wizardElement.createWizard();
            }
        };
    }

    /**
     * Uses the dialog store to restore widget values to the values that they
     * held last time this wizard was used to completion.
     */
    protected void restoreWidgetValues() {
        updateMessage();
    }

    /**
     * Expands the wizard categories in this page's category viewer that were
     * expanded last time this page was used. If a category that was previously
     * expanded no longer exists then it is ignored.
     */
    protected void expandPreviouslyExpandedCategories(String setting, IWizardCategory wizardCategories, TreeViewer viewer) {
        String[] expandedCategoryPaths =  getDialogSettings()
                .getArray(setting);
        if (expandedCategoryPaths == null || expandedCategoryPaths.length == 0) {
			return;
		}

        List categoriesToExpand = new ArrayList(expandedCategoryPaths.length);

        if (wizardCategories != null) {
            for (String expandedCategoryPath : expandedCategoryPaths) {
                IWizardCategory category = wizardCategories
                        .findCategory(new Path(expandedCategoryPath));
                if (category != null) {
					categoriesToExpand.add(category);
				}
            }
        }

        if (!categoriesToExpand.isEmpty()) {
			viewer.setExpandedElements(categoriesToExpand.toArray());
		}

    }

    /**
     * Selects the wizard category and wizard in this page that were selected
     * last time this page was used. If a category or wizard that was
     * previously selected no longer exists then it is ignored.
     */
    protected void selectPreviouslySelected(String setting, IWizardCategory wizardCategories, final TreeViewer viewer) {
        String selectedId = getDialogSettings().get(setting);
        if (selectedId == null) {
			return;
		}

        if (wizardCategories == null) {
			return;
		}

        Object selected = wizardCategories.findCategory(new Path(
                selectedId));

        if (selected == null) {
            selected = wizardCategories.findWizard(selectedId);

            if (selected == null) {
				// if we cant find either a category or a wizard, abort.
                return;
			}
        }

        viewer.setSelection(new StructuredSelection(selected), true);
    }

    /**
     * Stores the collection of currently-expanded categories in this page's
     * dialog store, in order to recreate this page's state in the next
     * instance of this page.
     */
    protected void storeExpandedCategories(String setting, TreeViewer viewer) {
        Object[] expandedElements = viewer.getExpandedElements();
        List expandedElementPaths = new ArrayList(expandedElements.length);
        for (Object expandedElement : expandedElements) {
            if (expandedElement instanceof IWizardCategory) {
				expandedElementPaths
                        .add(((IWizardCategory) expandedElement)
                                .getPath().toString());
			}
        }
        getDialogSettings().put(setting,
                (String[]) expandedElementPaths
                        .toArray(new String[expandedElementPaths.size()]));
    }

    /**
     * Stores the currently-selected element in this page's dialog store, in
     * order to recreate this page's state in the next instance of this page.
     */
    protected void storeSelectedCategoryAndWizard(String setting, TreeViewer viewer) {
		Object selected = viewer.getStructuredSelection().getFirstElement();

        if (selected != null) {
            if (selected instanceof IWizardCategory) {
				getDialogSettings().put(setting,
                        ((IWizardCategory) selected).getPath()
                                .toString());
			} else {
				// else its a wizard
            	getDialogSettings().put(setting,
                        ((IWizardDescriptor) selected).getId());
			}
        }
    }

    /**
     * When Finish is pressed, write widget values to the dialog store so
     * that they will persist into the next invocation of the wizard page.
     *
     */
    public void saveWidgetValues(){
    	// do nothing by default - subclasses should override
    }

    @Override
	public IWizardPage getNextPage() {
    	ITriggerPoint triggerPoint = getTriggerPoint();

        if (triggerPoint == null || WorkbenchActivityHelper.allowUseOf(triggerPoint, getSelectedNode())) {
			return super.getNextPage();
		}
        return null;
    }

    /**
     * Get the trigger point for the wizard type, if one exists.
     *
     * @return the wizard's trigger point
     */
    protected ITriggerPoint getTriggerPoint(){
    	return null;	// default implementation
    }

    /**
     * Set the tree viewer that is used for this wizard selection page.
     *
     * @param viewer
     */
    protected void setTreeViewer(TreeViewer viewer){
    	treeViewer = viewer;
    }

    /**
     * Get the tree viewer that is used for this wizard selection page.
     *
     * @return tree viewer used for this wizard's selection page
     */
    protected TreeViewer getTreeViewer(){
    	return treeViewer;
    }

    /**
     * Perform any initialization of the wizard page that needs to be done
     * after widgets are created and main control is set.
     */
    protected void initialize(){
    	// do nothing by default
    }
}
