/*******************************************************************************
 * Copyright (c) 2000, 2016 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.jdt.internal.ui.dialogs;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import org.eclipse.swt.SWT;
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.IStatus;

import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ILabelProvider;

import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog;
import org.eclipse.ui.dialogs.FilteredList;

import org.eclipse.jdt.internal.corext.util.Messages;

import org.eclipse.jdt.internal.ui.JavaUIMessages;

/**
 * A class to select elements out of a list of elements, organized on multiple
 * pages.
 */
public class MultiElementListSelectionDialog extends AbstractElementListSelectionDialog {

	private static class Page {
		private Object[] elements;
		public String filter;
		public boolean okState= false;

		public Page(Object[] elements) {
			this.elements= elements;
		}
	}

	private Page[] fPages;
	private int fCurrentPage;
	private int fNumberOfPages;

	private Button fFinishButton;
	private Button fBackButton;
	private Button fNextButton;
	private Button fSkipButton;

	private Label fPageInfoLabel;
	private String fPageInfoMessage= JavaUIMessages.MultiElementListSelectionDialog_pageInfoMessage;
	private Comparator<?> fComparator;

	/**
	 * Constructs a multi-page list selection dialog.
	 * @param parent The parent shell
	 * @param renderer the label renderer.
	 */
	public MultiElementListSelectionDialog(Shell parent, ILabelProvider renderer) {
		super(parent, renderer);
	}

	/**
	 * Sets message shown in the right top corner. Use {0} and {1} as placeholders
	 * for the current and the total number of pages.
	 * @param message the message.
	 */
	public void setPageInfoMessage(String message) {
		fPageInfoMessage= message;
	}

	/**
	 * Sets the elements to be displayed in the dialog.
	 * @param elements an array of pages holding arrays of elements
	 */
	public void setElements(Object[][] elements) {
		fNumberOfPages= elements.length;
		fPages= new Page[fNumberOfPages];
		for (int i= 0; i != fNumberOfPages; i++)
			fPages[i]= new Page(elements[i]);

		initializeResult(fNumberOfPages);
	}

	/*
	 * @see Window#open()
	 */
	@Override
	public int open() {
		List<Object[]> selection= getInitialElementSelections();
		if (selection == null || selection.size() != fNumberOfPages) {
			setInitialSelections(new Object[fNumberOfPages]);
			selection= getInitialElementSelections();
		}

		Assert.isTrue(selection.size() == fNumberOfPages);

		return super.open();
	}

	/*
	 * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(Composite)
	 */
	@Override
	protected Control createDialogArea(Composite parent) {
		Composite contents= (Composite) super.createDialogArea(parent);

		createMessageArea(contents);
		createFilterText(contents);
		createFilteredList(contents);

		fCurrentPage= 0;
		setPageData();

		applyDialogFont(contents);
		return contents;
	}

	/*
	 * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(Composite)
	 */
	@Override
	protected void createButtonsForButtonBar(Composite parent) {
		fSkipButton= createButton(parent, IDialogConstants.SKIP_ID, IDialogConstants.SKIP_LABEL, false);
		fBackButton= createButton(parent, IDialogConstants.BACK_ID, IDialogConstants.BACK_LABEL, false);

		// XXX: Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=279425
		boolean HAS_BUG_279425= true;
		fNextButton= createButton(parent, IDialogConstants.NEXT_ID, IDialogConstants.NEXT_LABEL, !HAS_BUG_279425);
		fFinishButton= createButton(parent, IDialogConstants.OK_ID, IDialogConstants.FINISH_LABEL, HAS_BUG_279425);

		createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
	}

	/*
	 * XXX: Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=279425
	 * 		The whole method can be removed once that bug is fixed.
	 * @see org.eclipse.jface.dialogs.Dialog#initializeBounds()
	 * @since 3.5.1
	 */
	@Override
	protected void initializeBounds() {
		super.initializeBounds();
		fNextButton.getShell().setDefaultButton(fNextButton);
	}

	/*
	 * @see org.eclipse.ui.dialogs.SelectionDialog#createMessageArea(Composite)
	 */
	@Override
	protected Label createMessageArea(Composite parent) {
		Composite composite= new Composite(parent, SWT.NONE);

		GridLayout layout= new GridLayout();
		layout.marginHeight= 0;
		layout.marginWidth= 0;
		layout.horizontalSpacing= 5;
		layout.numColumns= 2;
		composite.setLayout(layout);

		GridData data= new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		composite.setLayoutData(data);

		Label messageLabel= super.createMessageArea(composite);

		fPageInfoLabel= new Label(composite, SWT.NULL);
		fPageInfoLabel.setText(getPageInfoMessage());

		data= new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		data.horizontalAlignment= GridData.END;
		fPageInfoLabel.setLayoutData(data);
		applyDialogFont(messageLabel);
		return messageLabel;
	}

	/*
	 * @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult()
	 */
	@Override
	protected void computeResult() {
		setResult(fCurrentPage, getSelectedElements());
	}

	/*
	 * @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int)
	 */
	@Override
	protected void buttonPressed(int buttonId) {
		if (buttonId == IDialogConstants.SKIP_ID) {
			boolean isLastPage= fCurrentPage == fNumberOfPages - 1 ? true : false;
			turnPage(true, true);
			if (isLastPage) {
				buttonPressed(IDialogConstants.OK_ID);
			}
		} else if (buttonId == IDialogConstants.BACK_ID) {
			turnPage(false, false);
		} else if (buttonId == IDialogConstants.NEXT_ID) {
			turnPage(true, false);
		} else {
			super.buttonPressed(buttonId);
		}
	}

	/**
	 * @see AbstractElementListSelectionDialog#handleDefaultSelected()
	 */
	@Override
	protected void handleDefaultSelected() {
		if (validateCurrentSelection()) {
			if (fCurrentPage == fNumberOfPages - 1) {
				buttonPressed(IDialogConstants.OK_ID);
			} else {
				buttonPressed(IDialogConstants.NEXT_ID);
			}
		}
	}

	/**
	 * @see AbstractElementListSelectionDialog#updateButtonsEnableState(IStatus)
	 */
	@Override
	protected void updateButtonsEnableState(IStatus status) {
		boolean isOK= !status.matches(IStatus.ERROR);
		fPages[fCurrentPage].okState= isOK;

		boolean isAllOK= isOK;
		for (int i= 0; i != fNumberOfPages; i++)
			isAllOK = isAllOK && fPages[i].okState;

		fFinishButton.setEnabled(isAllOK);

		boolean nextButtonEnabled= isOK && (fCurrentPage < fNumberOfPages - 1);

		fNextButton.setEnabled(nextButtonEnabled);
		fBackButton.setEnabled(fCurrentPage != 0);

		// since there will always be a default selection - see FilteredList.TableUpdateJob.defaultSelect()
		fSkipButton.setEnabled(true);

		if (nextButtonEnabled) {
			getShell().setDefaultButton(fNextButton);
		} else if (isAllOK) {
			getShell().setDefaultButton(fFinishButton);
		}
	}

	private void turnPage(boolean toNextPage, boolean skipSelection) {
		Page page= fPages[fCurrentPage];

		// store filter
		String filter= getFilter();
		if (filter == null)
			filter= ""; //$NON-NLS-1$
		page.filter= filter;

		if (skipSelection) {
			setSelection(null);
		}

		// store selection
		Object[] selectedElements= getSelectedElements();
		List<Object[]> list= getInitialElementSelections();
		list.set(fCurrentPage, selectedElements);

		// store result
		setResult(fCurrentPage, selectedElements);

		if (toNextPage) {
			if (fCurrentPage + 1 >= fNumberOfPages)
				return;

			fCurrentPage++;
		} else {
			if (fCurrentPage - 1 < 0)
				return;

			fCurrentPage--;
		}

		if (fPageInfoLabel != null && !fPageInfoLabel.isDisposed())
			fPageInfoLabel.setText(getPageInfoMessage());

		setPageData();

		validateCurrentSelection();
	}

	private void setPageData() {
		Page page= fPages[fCurrentPage];

		// 1. set elements
		setListElements(page.elements);

		// 2. apply filter
		String filter= page.filter;
		if (filter == null)
			filter= ""; //$NON-NLS-1$
		setFilter(filter);

		// 3. select elements
		Object[] selectedElements= (Object[]) getInitialElementSelections().get(fCurrentPage);
		setSelection(selectedElements);
		fFilteredList.setFocus();
	}

	private String getPageInfoMessage() {
		if (fPageInfoMessage == null)
			return ""; //$NON-NLS-1$

		String[] args= new String[] { Integer.toString(fCurrentPage + 1), Integer.toString(fNumberOfPages) };
		return Messages.format(fPageInfoMessage, args);
	}

	private void initializeResult(int length) {
		List<Object> result= new ArrayList<>(length);
		for (int i= 0; i != length; i++)
			result.add(null);

		setResult(result);
	}

	/**
	 * Gets the current Page.
	 * @return Returns a int
	 */
	public int getCurrentPage() {
		return fCurrentPage;
	}

	/**
	 * Set the <code>Comparator</code> used to sort
	 * the elements in the List.
	 *
	 * @param comparator the comparator to use, not null.
	 */
	public void setComparator(Comparator<?> comparator) {
		fComparator= comparator;
		if (fFilteredList != null)
			fFilteredList.setComparator(fComparator);
	}

	@Override
	protected FilteredList createFilteredList(Composite parent) {
		FilteredList filteredList= super.createFilteredList(parent);
		if (fComparator != null) {
			filteredList.setComparator(fComparator);
		}
		return filteredList;
	}

}
