/*******************************************************************************
 * Copyright (c) 2000, 2015 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
 *     Jerome Cambon <jerome.cambon@oracle.com> - [code style] don't generate redundant modifiers "public static final abstract" for interface members - https://bugs.eclipse.org/71627
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.refactoring;

import java.util.Arrays;
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.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.Label;
import org.eclipse.swt.widgets.Text;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.wizard.IWizardPage;

import org.eclipse.ui.PlatformUI;

import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.ui.refactoring.RefactoringWizard;

import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.JavaModelException;

import org.eclipse.jdt.internal.corext.refactoring.structure.ExtractInterfaceProcessor;

import org.eclipse.jdt.ui.JavaElementComparator;
import org.eclipse.jdt.ui.JavaElementLabels;

import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.util.ExceptionHandler;
import org.eclipse.jdt.internal.ui.util.SWTUtil;
import org.eclipse.jdt.internal.ui.viewsupport.AppearanceAwareLabelProvider;
import org.eclipse.jdt.internal.ui.viewsupport.DecoratingJavaLabelProvider;

public class ExtractInterfaceWizard extends RefactoringWizard {

	private final ExtractInterfaceProcessor fProcessor;

	public ExtractInterfaceWizard(ExtractInterfaceProcessor processor, Refactoring refactoring) {
		super(refactoring, DIALOG_BASED_USER_INTERFACE);
		fProcessor= processor;
		setDefaultPageTitle(RefactoringMessages.ExtractInterfaceWizard_Extract_Interface);
	}

	/* non java-doc
	 * @see RefactoringWizard#addUserInputPages
	 */
	@Override
	protected void addUserInputPages(){
		addPage(new ExtractInterfaceInputPage(fProcessor));
	}

	private static class ExtractInterfaceInputPage extends TextInputWizardPage {

		private Button fReplaceAllCheckbox;
		private Button fGenerateAnnotationsCheckbox;
		private Button fGenerateCommentsCheckbox;
		private Button fInstanceofCheckbox;
		private CheckboxTableViewer fTableViewer;
		private static final String DESCRIPTION = RefactoringMessages.ExtractInterfaceInputPage_description;
		private static final String SETTING_ANNOTATIONS= 		"Annotations";//$NON-NLS-1$
		private static final String SETTING_REPLACE= "Replace"; //$NON-NLS-1$
		private static final String SETTING_COMMENTS= "Comments"; //$NON-NLS-1$
		private static final String SETTING_INSTANCEOF= "InstanceOf"; //$NON-NLS-1$
		private Button fSelectAllButton;
		private Button fDeselectAllButton;

		private final ExtractInterfaceProcessor fProcessor;

		public ExtractInterfaceInputPage(ExtractInterfaceProcessor processor) {
			super(DESCRIPTION, true);
			fProcessor= processor;
		}

		@Override
		public void createControl(Composite parent) {
			initializeDialogUnits(parent);
			Composite result= new Composite(parent, SWT.NONE);
			setControl(result);
			GridLayout layout= new GridLayout();
			layout.numColumns= 2;
			result.setLayout(layout);

			Label label= new Label(result, SWT.NONE);
			label.setText(RefactoringMessages.ExtractInterfaceInputPage_Interface_name);

			Text text= createTextInputField(result);
			text.selectAll();
			text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

			addReplaceAllCheckbox(result);
			addInstanceofCheckbox(result, layout.marginWidth);
			fReplaceAllCheckbox.addSelectionListener(new SelectionAdapter() {

				@Override
				public void widgetSelected(SelectionEvent e) {
					fInstanceofCheckbox.setEnabled(fReplaceAllCheckbox.getSelection());
				}
			});
			addGenerateAnnotationsCheckbox(result);

			Label separator= new Label(result, SWT.NONE);
			GridData gd= new GridData();
			gd.horizontalSpan= 2;
			separator.setLayoutData(gd);

			Label tableLabel= new Label(result, SWT.NONE);
			tableLabel.setText(RefactoringMessages.ExtractInterfaceInputPage_Members);
			tableLabel.setEnabled(anyMembersToExtract());
			gd= new GridData();
			gd.horizontalSpan= 2;
			tableLabel.setLayoutData(gd);

			addMemberListComposite(result);
			addGenerateCommentsCheckbox(result);
			Dialog.applyDialogFont(result);
			initializeCheckboxes();
			updateUIElementEnablement();
			PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(), IJavaHelpContextIds.EXTRACT_INTERFACE_WIZARD_PAGE);
		}

		private void addGenerateAnnotationsCheckbox(Composite result) {
			String title= RefactoringMessages.ExtractInterfaceWizard_generate_annotations;
			fGenerateAnnotationsCheckbox= createCheckbox(result,  title, false);
			fProcessor.setAnnotations(fGenerateAnnotationsCheckbox.getSelection());
			fGenerateAnnotationsCheckbox.addSelectionListener(new SelectionAdapter(){
				@Override
				public void widgetSelected(SelectionEvent e) {
					fProcessor.setAnnotations(fGenerateAnnotationsCheckbox.getSelection());
				}
			});
		}

		private void addGenerateCommentsCheckbox(Composite result) {
			String title= RefactoringMessages.ExtractInterfaceWizard_generate_comments;
			fGenerateCommentsCheckbox= createCheckbox(result,  title, false);
			fProcessor.setComments(fGenerateCommentsCheckbox.getSelection());
			fGenerateCommentsCheckbox.addSelectionListener(new SelectionAdapter(){
				@Override
				public void widgetSelected(SelectionEvent e) {
					fProcessor.setComments(fGenerateCommentsCheckbox.getSelection());
				}
			});
		}

		private void addInstanceofCheckbox(Composite result, int margin) {
			String title= RefactoringMessages.ExtractInterfaceWizard_use_supertype;
			fInstanceofCheckbox= new Button(result, SWT.CHECK);
			fInstanceofCheckbox.setSelection(false);
			GridData gd= new GridData();
			gd.horizontalIndent= (margin + fInstanceofCheckbox.computeSize(SWT.DEFAULT, SWT.DEFAULT).x);
			gd.horizontalSpan= 2;
			fInstanceofCheckbox.setLayoutData(gd);
			fInstanceofCheckbox.setText(title);
			fProcessor.setInstanceOf(fInstanceofCheckbox.getSelection());
			fInstanceofCheckbox.addSelectionListener(new SelectionAdapter(){
				@Override
				public void widgetSelected(SelectionEvent e) {
					fProcessor.setInstanceOf(fInstanceofCheckbox.getSelection());
				}
			});
		}

		private void addMemberListComposite(Composite result) {
			Composite composite= new Composite(result, SWT.NONE);
			GridLayout layout= new GridLayout();
			layout.numColumns= 2;
			layout.marginWidth= 0;
			layout.marginHeight= 0;
			composite.setLayout(layout);
			GridData gd= new GridData(GridData.FILL_BOTH);
			gd.heightHint= convertHeightInCharsToPixels(12);
			gd.horizontalSpan= 2;
			composite.setLayoutData(gd);

			fTableViewer= CheckboxTableViewer.newCheckList(composite, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
			fTableViewer.getTable().setLayoutData(new GridData(GridData.FILL_BOTH));
			fTableViewer.setLabelProvider(createLabelProvider());
			fTableViewer.setContentProvider(ArrayContentProvider.getInstance());
			try {
				fTableViewer.setInput(getExtractableMembers());
			} catch (JavaModelException e) {
				ExceptionHandler.handle(e, RefactoringMessages.ExtractInterfaceInputPage_Extract_Interface, RefactoringMessages.ExtractInterfaceInputPage_Internal_Error);
				fTableViewer.setInput(new IMember[0]);
			}
			fTableViewer.addCheckStateListener(event -> ExtractInterfaceInputPage.this.updateUIElementEnablement());
			fTableViewer.setComparator(new JavaElementComparator());
			fTableViewer.getControl().setEnabled(anyMembersToExtract());

			createButtonComposite(composite);
		}

		private IMember[] getExtractableMembers() throws JavaModelException {
			return fProcessor.getExtractableMembers();
		}

		protected void updateUIElementEnablement() {
			final IMember[] checked= getCheckedMembers();
			IMember[] extractable;
			try {
				extractable= getExtractableMembers();
			} catch (JavaModelException exception) {
				extractable= new IMember[0];
				JavaPlugin.log(exception);
			}
			final boolean enabled= containsMethods(checked);
			fGenerateAnnotationsCheckbox.setEnabled(enabled);
			fGenerateCommentsCheckbox.setEnabled(enabled);
			fInstanceofCheckbox.setEnabled(fReplaceAllCheckbox.getSelection());
			fSelectAllButton.setEnabled(checked.length < extractable.length);
			fDeselectAllButton.setEnabled(checked.length > 0);
		}

		private static boolean containsMethods(IMember[] members) {
			for (IMember member : members) {
				if (member.getElementType() == IJavaElement.METHOD) {
					return true;
				}
			}
			return false;
		}

		private ILabelProvider createLabelProvider(){
			AppearanceAwareLabelProvider lprovider= new AppearanceAwareLabelProvider(
				AppearanceAwareLabelProvider.DEFAULT_TEXTFLAGS |  JavaElementLabels.F_APP_TYPE_SIGNATURE,
				AppearanceAwareLabelProvider.DEFAULT_IMAGEFLAGS
			);

			return new DecoratingJavaLabelProvider(lprovider);
		}

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

			fSelectAllButton= new Button(buttonComposite, SWT.PUSH);
			fSelectAllButton.setText(RefactoringMessages.ExtractInterfaceInputPage_Select_All);
			fSelectAllButton.setEnabled(anyMembersToExtract());
			fSelectAllButton.setLayoutData(new GridData());
			SWTUtil.setButtonDimensionHint(fSelectAllButton);
			fSelectAllButton.addSelectionListener(new SelectionAdapter(){
				@Override
				public void widgetSelected(SelectionEvent e) {
					fTableViewer.setAllChecked(true);
					ExtractInterfaceInputPage.this.updateUIElementEnablement();
				}
			});

			fDeselectAllButton= new Button(buttonComposite, SWT.PUSH);
			fDeselectAllButton.setText(RefactoringMessages.ExtractInterfaceInputPage_Deselect_All);
			fDeselectAllButton.setEnabled(anyMembersToExtract());
			fDeselectAllButton.setLayoutData(new GridData());
			SWTUtil.setButtonDimensionHint(fDeselectAllButton);
			fDeselectAllButton.addSelectionListener(new SelectionAdapter(){
				@Override
				public void widgetSelected(SelectionEvent e) {
					fTableViewer.setAllChecked(false);
					ExtractInterfaceInputPage.this.updateUIElementEnablement();
				}
			});
		}

		private boolean anyMembersToExtract() {
			try {
				return getExtractableMembers().length > 0;
			} catch (JavaModelException e) {
				return false;
			}
		}

		private void addReplaceAllCheckbox(Composite result) {
			String title= RefactoringMessages.ExtractInterfaceInputPage_change_references;
			boolean defaultValue= fProcessor.isReplace();
			fReplaceAllCheckbox= createCheckbox(result,  title, defaultValue);
			fProcessor.setReplace(fReplaceAllCheckbox.getSelection());
			fReplaceAllCheckbox.addSelectionListener(new SelectionAdapter(){
				@Override
				public void widgetSelected(SelectionEvent e) {
					fProcessor.setReplace(fReplaceAllCheckbox.getSelection());
				}
			});
		}

		private static Button createCheckbox(Composite parent, String title, boolean value){
			Button checkBox= new Button(parent, SWT.CHECK);
			checkBox.setText(title);
			checkBox.setSelection(value);
			GridData layoutData= new GridData();
			layoutData.horizontalSpan= 2;

			checkBox.setLayoutData(layoutData);
			return checkBox;
		}

		/*
		 * @see org.eclipse.jdt.internal.ui.refactoring.TextInputWizardPage#validateTextField(String)
		 */
		@Override
		protected RefactoringStatus validateTextField(String text) {
			final ExtractInterfaceProcessor processor= fProcessor;
			processor.setTypeName(text);
			return processor.checkTypeName(text);
		}

		/*
		 * @see org.eclipse.jface.wizard.IWizardPage#getNextPage()
		 */
		@Override
		public IWizardPage getNextPage() {
			try {
				initializeRefactoring();
				storeDialogSettings();
				return super.getNextPage();
			} catch (JavaModelException e) {
				JavaPlugin.log(e);
				return null;
			}
		}

		/*
		 * @see org.eclipse.jdt.internal.ui.refactoring.RefactoringWizardPage#performFinish()
		 */
		@Override
		public boolean performFinish(){
			try {
				initializeRefactoring();
				storeDialogSettings();
				return super.performFinish();
			} catch (JavaModelException e) {
				JavaPlugin.log(e);
				return false;
			}
		}

		private void initializeRefactoring() throws JavaModelException {
			fProcessor.setTypeName(getText());
			fProcessor.setReplace(fReplaceAllCheckbox.getSelection());
			fProcessor.setExtractedMembers(getCheckedMembers());
			fProcessor.setAnnotations(fGenerateAnnotationsCheckbox.getSelection());
			fProcessor.setComments(fGenerateCommentsCheckbox.getSelection());
			fProcessor.setInstanceOf(fInstanceofCheckbox.getSelection());
		}

		private IMember[] getCheckedMembers() {
			List<?> checked= Arrays.asList(fTableViewer.getCheckedElements());
			return checked.toArray(new IMember[checked.size()]);
		}

		@Override
		public void dispose() {
			fInstanceofCheckbox= null;
			fGenerateCommentsCheckbox= null;
			fReplaceAllCheckbox= null;
			fTableViewer= null;
			super.dispose();
		}

		private void initializeCheckboxes() {
			initializeCheckBox(fGenerateAnnotationsCheckbox, SETTING_ANNOTATIONS, true);
			initializeCheckBox(fReplaceAllCheckbox, SETTING_REPLACE, true);
			initializeCheckBox(fGenerateCommentsCheckbox, SETTING_COMMENTS, true);
			initializeCheckBox(fInstanceofCheckbox, SETTING_INSTANCEOF, false);
		}

		private void initializeCheckBox(Button checkbox, String property, boolean def) {
			String s= JavaPlugin.getDefault().getDialogSettings().get(property);
			if (s != null)
				checkbox.setSelection(Boolean.parseBoolean(s));
			else
				checkbox.setSelection(def);
		}

		private void storeDialogSettings() {
			final IDialogSettings settings= JavaPlugin.getDefault().getDialogSettings();
			settings.put(SETTING_ANNOTATIONS, fGenerateAnnotationsCheckbox.getSelection());
			settings.put(SETTING_REPLACE, fReplaceAllCheckbox.getSelection());
			settings.put(SETTING_COMMENTS, fGenerateCommentsCheckbox.getSelection());
			settings.put(SETTING_INSTANCEOF, fInstanceofCheckbox.getSelection());
		}
	}
}
