/*******************************************************************************
 * 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
 *******************************************************************************/
package org.eclipse.cdt.internal.ui.wizards.folderwizard;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.cdt.core.CCProjectNature;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.CProjectNature;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICModelStatus;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IPathEntry;
import org.eclipse.cdt.core.model.ISourceRoot;
import org.eclipse.cdt.internal.core.model.InternalCoreModelUtil;
import org.eclipse.cdt.internal.core.model.PathEntryManager;
import org.eclipse.cdt.internal.ui.ICHelpContextIds;
import org.eclipse.cdt.internal.ui.dialogs.StatusInfo;
import org.eclipse.cdt.internal.ui.dialogs.TypedElementSelectionValidator;
import org.eclipse.cdt.internal.ui.dialogs.TypedViewerFilter;
import org.eclipse.cdt.internal.ui.util.CoreUtility;
import org.eclipse.cdt.internal.ui.util.EditorUtility;
import org.eclipse.cdt.internal.ui.wizards.NewElementWizardPage;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.DialogField;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.IStringButtonAdapter;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.LayoutUtil;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.SelectionButtonDialogField;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.StringButtonDialogField;
import org.eclipse.cdt.ui.CElementLabelProvider;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
import org.eclipse.ui.dialogs.ISelectionStatusValidator;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.views.navigator.ResourceComparator;

public class NewSourceFolderWizardPage extends NewElementWizardPage {
	private static final String PAGE_NAME = "NewSourceFolderWizardPage"; //$NON-NLS-1$

	private StringButtonDialogField fProjectField;
	private StatusInfo fProjectStatus;

	private StringButtonDialogField fRootDialogField;
	private StatusInfo fRootStatus;

	private SelectionButtonDialogField fExcludeInOthersFields;

	private IWorkspaceRoot fWorkspaceRoot;

	private ICProject fCurrCProject;
	private IPathEntry[] fEntries;

	private IPathEntry[] fNewEntries;

	private boolean fIsProjectAsSourceFolder;

	private ISourceRoot fCreatedRoot;

	public NewSourceFolderWizardPage() {
		super(PAGE_NAME);

		setTitle(NewFolderWizardMessages.NewSourceFolderWizardPage_title);
		setDescription(NewFolderWizardMessages.NewSourceFolderWizardPage_description);

		fWorkspaceRoot = ResourcesPlugin.getWorkspace().getRoot();

		RootFieldAdapter adapter = new RootFieldAdapter();

		fProjectField = new StringButtonDialogField(adapter);
		fProjectField.setDialogFieldListener(adapter);
		fProjectField.setLabelText(NewFolderWizardMessages.NewSourceFolderWizardPage_project_label);
		fProjectField.setButtonLabel(NewFolderWizardMessages.NewSourceFolderWizardPage_project_button);

		fRootDialogField = new StringButtonDialogField(adapter);
		fRootDialogField.setDialogFieldListener(adapter);
		fRootDialogField.setLabelText(NewFolderWizardMessages.NewSourceFolderWizardPage_root_label);
		fRootDialogField.setButtonLabel(NewFolderWizardMessages.NewSourceFolderWizardPage_root_button);

		fExcludeInOthersFields = new SelectionButtonDialogField(SWT.CHECK);
		fExcludeInOthersFields.setDialogFieldListener(adapter);
		fExcludeInOthersFields.setLabelText(NewFolderWizardMessages.NewSourceFolderWizardPage_exclude_label);

		fRootStatus = new StatusInfo();
		fProjectStatus = new StatusInfo();
	}

	// -------- Initialization ---------

	public void init(IStructuredSelection selection) {
		if (selection == null || selection.isEmpty()) {
			setDefaultAttributes();
			return;
		}

		Object selectedElement = selection.getFirstElement();
		if (selectedElement == null) {
			selectedElement = EditorUtility.getActiveEditorCInput();
		}

		String projPath = null;

		if (selectedElement instanceof IResource) {
			IProject proj = ((IResource) selectedElement).getProject();
			if (proj != null) {
				projPath = proj.getFullPath().makeRelative().toString();
			}
		} else if (selectedElement instanceof ICElement) {
			ICProject jproject = ((ICElement) selectedElement).getCProject();
			if (jproject != null) {
				projPath = jproject.getProject().getFullPath().makeRelative().toString();
			}
		}

		if (projPath != null) {
			fProjectField.setText(projPath);
			fRootDialogField.setText(""); //$NON-NLS-1$
		} else {
			setDefaultAttributes();
		}
	}

	private void setDefaultAttributes() {
		String projPath = ""; //$NON-NLS-1$

		try {
			// find the first C project
			IProject[] projects = fWorkspaceRoot.getProjects();
			for (IProject proj : projects) {
				if (proj.hasNature(CProjectNature.C_NATURE_ID) || proj.hasNature(CCProjectNature.CC_NATURE_ID)) {
					projPath = proj.getFullPath().makeRelative().toString();
					break;
				}
			}
		} catch (CoreException e) {
			// ignore here
		}
		fProjectField.setText(projPath);
		fRootDialogField.setText(""); //$NON-NLS-1$
	}

	// -------- UI Creation ---------

	@Override
	public void createControl(Composite parent) {
		initializeDialogUnits(parent);

		Composite composite = new Composite(parent, SWT.NONE);

		GridLayout layout = new GridLayout();
		layout.marginWidth = 0;
		layout.marginHeight = 0;
		layout.numColumns = 3;
		composite.setLayout(layout);

		fProjectField.doFillIntoGrid(composite, 3);
		fRootDialogField.doFillIntoGrid(composite, 3);
		fExcludeInOthersFields.doFillIntoGrid(composite, 3);

		int maxFieldWidth = convertWidthInCharsToPixels(40);
		LayoutUtil.setWidthHint(fProjectField.getTextControl(null), maxFieldWidth);
		LayoutUtil.setHorizontalGrabbing(fProjectField.getTextControl(null), true);
		LayoutUtil.setWidthHint(fRootDialogField.getTextControl(null), maxFieldWidth);

		// Bug #220003 : consistency between New Source Folder dialog and Source Location Property tab.
		fExcludeInOthersFields.setSelection(true);

		setControl(composite);
		Dialog.applyDialogFont(composite);
		PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, ICHelpContextIds.NEW_SRCFLDER_WIZARD_PAGE);
	}

	@Override
	public void setVisible(boolean visible) {
		super.setVisible(visible);
		if (visible) {
			fRootDialogField.setFocus();
		}
	}

	// -------- ContainerFieldAdapter --------

	private class RootFieldAdapter implements IStringButtonAdapter, IDialogFieldListener {
		@Override
		public void changeControlPressed(DialogField field) {
			packRootChangeControlPressed(field);
		}

		@Override
		public void dialogFieldChanged(DialogField field) {
			packRootDialogFieldChanged(field);
		}
	}

	protected void packRootChangeControlPressed(DialogField field) {
		if (field == fRootDialogField) {
			IPath initialPath = new Path(fRootDialogField.getText());
			String title = NewFolderWizardMessages.NewSourceFolderWizardPage_ChooseExistingRootDialog_title;
			String message = NewFolderWizardMessages.NewSourceFolderWizardPage_ChooseExistingRootDialog_description;
			IFolder folder = chooseFolder(title, message, initialPath);
			if (folder != null) {
				IPath path = folder.getFullPath().removeFirstSegments(1);
				fRootDialogField.setText(path.toString());
			}
		} else if (field == fProjectField) {
			ICProject jproject = chooseProject();
			if (jproject != null) {
				IPath path = jproject.getProject().getFullPath().makeRelative();
				fProjectField.setText(path.toString());
			}
		}
	}

	protected void packRootDialogFieldChanged(DialogField field) {
		if (field == fRootDialogField) {
			updateRootStatus();
		} else if (field == fProjectField) {
			updateProjectStatus();
			updateRootStatus();
		} else if (field == fExcludeInOthersFields) {
			updateRootStatus();
		}
		updateStatus(new IStatus[] { fProjectStatus, fRootStatus });
	}

	private void updateProjectStatus() {
		fCurrCProject = null;
		fIsProjectAsSourceFolder = false;

		String str = fProjectField.getText();
		if (str.isEmpty()) {
			fProjectStatus.setError(NewFolderWizardMessages.NewSourceFolderWizardPage_error_EnterProjectName);
			return;
		}
		IPath path = new Path(str);
		if (path.segmentCount() != 1) {
			fProjectStatus.setError(NewFolderWizardMessages.NewSourceFolderWizardPage_error_InvalidProjectPath);
			return;
		}
		IProject project = fWorkspaceRoot.getProject(path.toString());
		if (!project.exists()) {
			fProjectStatus.setError(NewFolderWizardMessages.NewSourceFolderWizardPage_error_ProjectNotExists);
			return;
		}
		try {
			if (project.hasNature(CProjectNature.C_NATURE_ID) || project.hasNature(CCProjectNature.CC_NATURE_ID)) {
				fCurrCProject = CoreModel.getDefault().create(project);
				fEntries = fCurrCProject.getRawPathEntries();
				fProjectStatus.setOK();
				return;
			}
		} catch (CoreException e) {
			CUIPlugin.log(e);
			fCurrCProject = null;
		}
		fProjectStatus.setError(NewFolderWizardMessages.NewSourceFolderWizardPage_error_NotACProject);
	}

	private void updateRootStatus() {
		fRootDialogField.enableButton(fCurrCProject != null);
		fIsProjectAsSourceFolder = false;
		if (fCurrCProject == null) {
			return;
		}
		fRootStatus.setOK();

		IPath projPath = fCurrCProject.getProject().getFullPath();
		String str = fRootDialogField.getText();
		if (str.length() == 0) {
			fRootStatus.setError(NLS.bind(NewFolderWizardMessages.NewSourceFolderWizardPage_error_EnterRootName,
					fCurrCProject.getProject().getFullPath().toString()));
		} else {
			IPath path = projPath.append(str);
			IStatus validate = fWorkspaceRoot.getWorkspace().validatePath(path.toString(), IResource.FOLDER);
			if (validate.matches(IStatus.ERROR)) {
				fRootStatus.setError(NLS.bind(NewFolderWizardMessages.NewSourceFolderWizardPage_error_InvalidRootName,
						validate.getMessage()));
			} else {
				IResource res = fWorkspaceRoot.findMember(path);
				if (res != null) {
					if (res.getType() != IResource.FOLDER) {
						fRootStatus.setError(NewFolderWizardMessages.NewSourceFolderWizardPage_error_NotAFolder);
						return;
					}
				}
				ArrayList<IPathEntry> newEntries = new ArrayList<>(fEntries.length + 1);
				int projectEntryIndex = -1;

				for (int i = 0; i < fEntries.length; i++) {
					IPathEntry curr = fEntries[i];
					if (curr.getEntryKind() == IPathEntry.CDT_SOURCE) {
						if (path.equals(curr.getPath())) {
							fRootStatus
									.setError(NewFolderWizardMessages.NewSourceFolderWizardPage_error_AlreadyExisting);
							return;
						}
						if (projPath.equals(curr.getPath())) {
							projectEntryIndex = i;
						}
					}
					newEntries.add(curr);
				}

				IPathEntry newEntry = CoreModel.newSourceEntry(path);

				Set<IPathEntry> modified = new HashSet<>();
				if (fExcludeInOthersFields.isSelected()) {
					InternalCoreModelUtil.addExclusionPatterns(newEntry, newEntries, modified);
					newEntries.add(CoreModel.newSourceEntry(path));
				} else {
					if (projectEntryIndex >= 0) {
						fIsProjectAsSourceFolder = true;
						newEntries.set(projectEntryIndex, newEntry);
					} else {
						newEntries.add(CoreModel.newSourceEntry(path));
					}
				}

				fNewEntries = newEntries.toArray(new IPathEntry[newEntries.size()]);

				ICModelStatus status = PathEntryManager.getDefault().validatePathEntry(fCurrCProject, fNewEntries);
				if (!status.isOK()) {
					IStatus status2 = PathEntryManager.getDefault().validatePathEntry(fCurrCProject, fNewEntries);
					if (status2.isOK()) {
						return;
					}
					fRootStatus.setError(status.getMessage());
					return;
				} else if (fIsProjectAsSourceFolder) {
					fRootStatus.setInfo(NewFolderWizardMessages.NewSourceFolderWizardPage_warning_ReplaceSF);
					return;
				}
				if (!modified.isEmpty()) {
					fRootStatus
							.setInfo(NLS.bind(NewFolderWizardMessages.NewSourceFolderWizardPage_warning_AddedExclusions,
									String.valueOf(modified.size())));
					return;
				}
			}
		}
	}

	// ---- creation ----------------

	public ISourceRoot getNewSourceRoot() {
		return fCreatedRoot;
	}

	public IResource getCorrespondingResource() {
		return fCurrCProject.getProject().getFolder(fRootDialogField.getText());
	}

	public void createSourceRoot(IProgressMonitor monitor) throws CoreException {
		SubMonitor progress = SubMonitor.convert(monitor, NewFolderWizardMessages.NewSourceFolderWizardPage_operation,
				3);
		String relPath = fRootDialogField.getText();

		IProject project = fCurrCProject.getProject();
		IFolder folder = project.getFolder(relPath);
		if (!folder.exists()) {
			CoreUtility.createFolder(folder, true, true, progress.split(1));
		}

		if (CCorePlugin.getDefault().isNewStyleProject(project)) {
			InternalCoreModelUtil.addSourceEntry(project, folder, fIsProjectAsSourceFolder, progress.split(2));
		} else {
			fCurrCProject.setRawPathEntries(fNewEntries, progress.split(2));
		}

		fCreatedRoot = fCurrCProject.findSourceRoot(folder);
	}

	// ------------- choose dialogs

	private IFolder chooseFolder(String title, String message, IPath initialPath) {
		Class<?>[] acceptedClasses = new Class<?>[] { IFolder.class };
		ISelectionStatusValidator validator = new TypedElementSelectionValidator(acceptedClasses, false);
		ViewerFilter filter = new TypedViewerFilter(acceptedClasses, null);

		ILabelProvider lp = new WorkbenchLabelProvider();
		ITreeContentProvider cp = new WorkbenchContentProvider();

		IProject currProject = fCurrCProject.getProject();

		ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(), lp, cp);
		dialog.setValidator(validator);
		dialog.setTitle(title);
		dialog.setMessage(message);
		dialog.addFilter(filter);
		dialog.setInput(currProject);
		dialog.setComparator(new ResourceComparator(ResourceComparator.NAME));
		IResource res = currProject.findMember(initialPath);
		if (res != null) {
			dialog.setInitialSelection(res);
		}

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

	private ICProject chooseProject() {
		ICProject[] projects;
		try {
			projects = CoreModel.create(fWorkspaceRoot).getCProjects();
		} catch (CModelException e) {
			CUIPlugin.log(e);
			projects = new ICProject[0];
		}

		ILabelProvider labelProvider = new CElementLabelProvider(CElementLabelProvider.SHOW_DEFAULT);
		ElementListSelectionDialog dialog = new ElementListSelectionDialog(getShell(), labelProvider);
		dialog.setTitle(NewFolderWizardMessages.NewSourceFolderWizardPage_ChooseProjectDialog_title);
		dialog.setMessage(NewFolderWizardMessages.NewSourceFolderWizardPage_ChooseProjectDialog_description);
		dialog.setElements(projects);
		dialog.setInitialSelections(new Object[] { fCurrCProject });
		if (dialog.open() == Window.OK) {
			return (ICProject) dialog.getFirstResult();
		}
		return null;
	}
}
