/*******************************************************************************
 * 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.refactoring;

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

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
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.Table;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;

import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;

import org.eclipse.ui.PlatformUI;

import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;

import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
import org.eclipse.jdt.internal.corext.refactoring.ExceptionInfo;

import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.JavaUIStatus;
import org.eclipse.jdt.internal.ui.dialogs.FilteredTypesSelectionDialog;
import org.eclipse.jdt.internal.ui.dialogs.StatusInfo;
import org.eclipse.jdt.internal.ui.util.SWTUtil;

/**
 * A special control to add and remove thrown exceptions.
 */
public class ChangeExceptionsControl extends Composite {
//TODO: cleanup, adapt NLS strings

	private static class ExceptionInfoContentProvider implements IStructuredContentProvider {
		@Override
		@SuppressWarnings("unchecked")
		public Object[] getElements(Object inputElement) {
			return removeMarkedAsDeleted((List<ExceptionInfo>) inputElement);
		}
		private ExceptionInfo[] removeMarkedAsDeleted(List<ExceptionInfo> exceptionInfos){
			List<ExceptionInfo> result= new ArrayList<>(exceptionInfos.size());
			for (ExceptionInfo info : exceptionInfos) {
				if (! info.isDeleted())
					result.add(info);
			}
			return result.toArray(new ExceptionInfo[result.size()]);
		}
		@Override
		public void dispose() {
			// do nothing
		}
		@Override
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			// do nothing
		}
	}

	private static class ExceptionInfoLabelProvider extends LabelProvider implements ITableLabelProvider {
		private Image fInterfaceImage;

		public ExceptionInfoLabelProvider() {
			super();
			fInterfaceImage= JavaPluginImages.get(JavaPluginImages.IMG_OBJS_CLASS);
		}

		@Override
		public Image getColumnImage(Object element, int columnIndex) {
			return fInterfaceImage;
		}
		@Override
		public String getColumnText(Object element, int columnIndex) {
			ExceptionInfo info= (ExceptionInfo) element;
			return BasicElementLabels.getJavaElementName(info.getFullyQualifiedName());
		}
	}

	private final IExceptionListChangeListener fListener;
	private final IJavaProject fProject;

	private TableViewer fTableViewer;
	private Button fRemoveButton;
	private List<ExceptionInfo> fExceptionInfos;

	public ChangeExceptionsControl(Composite parent, int style, IExceptionListChangeListener listener, IJavaProject project) {
		super(parent, style);
		Assert.isNotNull(listener);
		fListener= listener;
		Assert.isNotNull(project);
		fProject= project;
		GridLayout layout= new GridLayout();
		layout.numColumns= 2;
		layout.marginWidth= 0;
		layout.marginHeight= 0;
		setLayout(layout);

		createExceptionList(this);
		createButtonComposite(this);
	}

	public void setInput(List<ExceptionInfo> exceptionInfos) {
		Assert.isNotNull(exceptionInfos);
		fExceptionInfos= exceptionInfos;
		fTableViewer.setInput(fExceptionInfos);
		if (fExceptionInfos.size() > 0)
			fTableViewer.setSelection(new StructuredSelection(fExceptionInfos.get(0)));
	}

	private void createExceptionList(Composite parent) {
		final Table table= new Table(parent, SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
		table.setLayoutData(new GridData(GridData.FILL_BOTH));

		fTableViewer= new TableViewer(table);
		fTableViewer.setUseHashlookup(true);
		fTableViewer.setContentProvider(new ExceptionInfoContentProvider());
		fTableViewer.setLabelProvider(new ExceptionInfoLabelProvider());
		fTableViewer.addSelectionChangedListener(event -> updateButtonsEnabledState());
	}

	private ExceptionInfo[] getSelectedItems() {
		ISelection selection= fTableViewer.getSelection();
		if (selection == null)
			return new ExceptionInfo[0];

		if (!(selection instanceof IStructuredSelection))
			return new ExceptionInfo[0];

		List<?> selected= ((IStructuredSelection) selection).toList();
		return selected.toArray(new ExceptionInfo[selected.size()]);
	}

	// ---- Button bar --------------------------------------------------------------------------------------

	private void createButtonComposite(Composite parent) {
		Composite buttonComposite= new Composite(parent, SWT.NONE);
		buttonComposite.setLayoutData(new GridData(GridData.FILL_VERTICAL));
		GridLayout gl= new GridLayout();
		gl.marginHeight= 0;
		gl.marginWidth= 0;
		buttonComposite.setLayout(gl);

		createAddButton(buttonComposite);
		fRemoveButton= createRemoveButton(buttonComposite);
		updateButtonsEnabledState();
	}

	private void updateButtonsEnabledState() {
		if (fRemoveButton != null)
			fRemoveButton.setEnabled(getTableSelectionCount() != 0);
	}

	private int getTableSelectionCount() {
		return getTable().getSelectionCount();
	}

	private int getTableItemCount() {
		return getTable().getItemCount();
	}

	private Table getTable() {
		return fTableViewer.getTable();
	}

	private Button createAddButton(Composite buttonComposite) {
		Button button= new Button(buttonComposite, SWT.PUSH);
		button.setText(RefactoringMessages.ChangeExceptionsControl_buttons_add);
		button.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		SWTUtil.setButtonDimensionHint(button);
		button.setEnabled(true);
		button.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				doAddException();
			}
		});
		return button;
	}

	private void doAddException() {
		IType newException= chooseException();
		if (newException == null)
			return;

		ExceptionInfo info= findExceptionInfo(newException);
		if (info != null) {
			if (info.isDeleted()) {
				info.markAsOld();
				fTableViewer.refresh();
			}
			fListener.exceptionListChanged();
			fTableViewer.getControl().setFocus();
			fTableViewer.setSelection(new StructuredSelection(info), true);
			return;
		}

		info= ExceptionInfo.createInfoForAddedException(newException);
		fExceptionInfos.add(info);
		fListener.exceptionListChanged();
		fTableViewer.refresh();
		fTableViewer.getControl().setFocus();
		int row= getTableItemCount() - 1;
		getTable().setSelection(row);
		updateButtonsEnabledState();

	}

	private IType chooseException() {
		IJavaElement[] elements= new IJavaElement[] { fProject.getJavaProject() };
		final IJavaSearchScope scope= SearchEngine.createJavaSearchScope(elements);

		FilteredTypesSelectionDialog dialog= new FilteredTypesSelectionDialog(getShell(), false,
				PlatformUI.getWorkbench().getProgressService(), scope, IJavaSearchConstants.CLASS);
		dialog.setTitle(RefactoringMessages.ChangeExceptionsControl_choose_title);
		dialog.setMessage(RefactoringMessages.ChangeExceptionsControl_choose_message);
		dialog.setInitialPattern("*Exception*"); //$NON-NLS-1$
		dialog.setValidator(selection -> {
			if (selection.length == 0)
				return new StatusInfo(IStatus.ERROR, ""); //$NON-NLS-1$
			try {
				return checkException((IType)selection[0]);
			} catch (JavaModelException e) {
				JavaPlugin.log(e);
				return StatusInfo.OK_STATUS;
			}
		});

		if (dialog.open() == Window.OK) {
			return (IType) dialog.getFirstResult();
		}
		return null;
	}

	private IStatus checkException(final IType type) throws JavaModelException {
		ITypeHierarchy hierarchy= type.newSupertypeHierarchy(new NullProgressMonitor());
		IType curr= type;
		while (curr != null) {
			String name= curr.getFullyQualifiedName();
			if ("java.lang.Throwable".equals(name)) //$NON-NLS-1$
				return StatusInfo.OK_STATUS;
			curr= hierarchy.getSuperclass(curr);
		}
		return JavaUIStatus.createError(IStatus.ERROR,
				RefactoringMessages.ChangeExceptionsControl_not_exception, null);
	}

	private ExceptionInfo findExceptionInfo(IType exception) {
		for (ExceptionInfo info : fExceptionInfos) {
			if (info.getElement().equals(exception))
				return info;
		}
		return null;
	}

	private Button createRemoveButton(Composite buttonComposite) {
		final Button button= new Button(buttonComposite, SWT.PUSH);
		button.setText(RefactoringMessages.ChangeExceptionsControl_buttons_remove);
		button.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		SWTUtil.setButtonDimensionHint(button);
		button.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				int index= getTable().getSelectionIndices()[0];
				for (ExceptionInfo s : getSelectedItems()) {
					if (s.isAdded()) {
						fExceptionInfos.remove(s);
					} else {
						s.markAsDeleted();
					}
				}
				restoreSelection(index);
			}
			private void restoreSelection(int index) {
				fTableViewer.refresh();
				fTableViewer.getControl().setFocus();
				int itemCount= getTableItemCount();
				if (itemCount != 0) {
					if (index >= itemCount)
						index= itemCount - 1;
					getTable().setSelection(index);
				}
				fListener.exceptionListChanged();
				updateButtonsEnabledState();
			}
		});
		return button;
	}

}
