/*******************************************************************************
 * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
 * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
 * Copyright (C) 2013, Robin Stocker <robin@nibor.org>
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.egit.ui.internal.clone;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.components.RepositorySelection;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
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.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.WorkingSetGroup;

/**
 * Wizard page that allows the user entering the location of a repository to be
 * cloned.
 */
public class CloneDestinationPage extends WizardPage {

	private final List<Ref> availableRefs = new ArrayList<Ref>();

	private RepositorySelection validatedRepoSelection;

	private List<Ref> validatedSelectedBranches;

	private Ref validatedHEAD;

	private boolean showProjectImport;

	private ComboViewer initialBranch;

	private Text directoryText;

	private Text remoteText;

	private Button importProjectsButton;

	private Button cloneSubmodulesButton;

	private WorkingSetGroup workingSetGroup;

	private String helpContext = null;

	private File clonedDestination;

	private Ref clonedInitialBranch;

	private String clonedRemote;

	CloneDestinationPage() {
		super(CloneDestinationPage.class.getName());
		setTitle(UIText.CloneDestinationPage_title);
	}

	public void createControl(final Composite parent) {
		final Composite panel = new Composite(parent, SWT.NULL);
		final GridLayout layout = new GridLayout();
		layout.numColumns = 1;
		panel.setLayout(layout);

		createDestinationGroup(panel);
		createConfigGroup(panel);
		if (showProjectImport)
			createProjectGroup(panel);

		Dialog.applyDialogFont(panel);
		setControl(panel);
		checkPage();
	}

	@Override
	public void setVisible(final boolean visible) {
		if (visible)
			if (this.availableRefs.isEmpty())
				initialBranch.getCombo().setEnabled(false);
		super.setVisible(visible);
		if (visible)
			directoryText.setFocus();
	}

	/**
	 * @param repositorySelection
	 *            selection of remote repository made by user
	 * @param availableRefs
	 *            all available refs
	 * @param branches
	 *            branches selected to be cloned
	 * @param head
	 *            HEAD in source repository
	 */
	public void setSelection(RepositorySelection repositorySelection, List<Ref> availableRefs, List<Ref> branches, Ref head){
		this.availableRefs.clear();
		this.availableRefs.addAll(availableRefs);
		checkPreviousPagesSelections(repositorySelection, branches, head);
		revalidate(repositorySelection,branches, head);
	}

	private void checkPreviousPagesSelections(
			RepositorySelection repositorySelection, List<Ref> branches,
			Ref head) {
		if (!repositorySelection.equals(validatedRepoSelection)
				|| !branches.equals(validatedSelectedBranches)
				|| !head.equals(validatedHEAD))
			setPageComplete(false);
		else
			checkPage();
	}

	private void createDestinationGroup(final Composite parent) {
		final Group g = createGroup(parent,
				UIText.CloneDestinationPage_groupDestination);

		Label dirLabel = new Label(g, SWT.NONE);
		dirLabel.setText(UIText.CloneDestinationPage_promptDirectory + ":"); //$NON-NLS-1$
		dirLabel
				.setToolTipText(UIText.CloneDestinationPage_DefaultRepoFolderTooltip);
		final Composite p = new Composite(g, SWT.NONE);
		final GridLayout grid = new GridLayout();
		grid.numColumns = 2;
		p.setLayout(grid);
		p.setLayoutData(createFieldGridData());
		directoryText = new Text(p, SWT.BORDER);
		directoryText.setLayoutData(createFieldGridData());
		directoryText.addModifyListener(new ModifyListener() {
			public void modifyText(final ModifyEvent e) {
				checkPage();
			}
		});
		final Button b = new Button(p, SWT.PUSH);
		b.setText(UIText.CloneDestinationPage_browseButton);
		b.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(final SelectionEvent e) {
				final FileDialog d;

				d = new FileDialog(getShell(), SWT.APPLICATION_MODAL | SWT.SAVE);
				if (directoryText.getText().length() > 0) {
					final File file = new File(directoryText.getText())
							.getAbsoluteFile();
					d.setFilterPath(file.getParent());
					d.setFileName(file.getName());
				}
				final String r = d.open();
				if (r != null)
					directoryText.setText(r);
			}
		});

		newLabel(g, UIText.CloneDestinationPage_promptInitialBranch + ":"); //$NON-NLS-1$
		initialBranch = new ComboViewer(g, SWT.DROP_DOWN | SWT.READ_ONLY);
		initialBranch.getCombo().setLayoutData(createFieldGridData());
		initialBranch.getCombo().addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(final SelectionEvent e) {
				checkPage();
			}
		});
		initialBranch.setContentProvider(ArrayContentProvider.getInstance());
		initialBranch.setLabelProvider(new LabelProvider(){
			@Override
			public String getText(Object element) {
				if (((Ref)element).getName().startsWith(Constants.R_HEADS))
					return ((Ref)element).getName().substring(Constants.R_HEADS.length());
				return ((Ref)element).getName();
			} });

		cloneSubmodulesButton = new Button(g, SWT.CHECK);
		cloneSubmodulesButton
				.setText(UIText.CloneDestinationPage_cloneSubmodulesButton);
		GridDataFactory.swtDefaults().span(2, 1).applyTo(cloneSubmodulesButton);
	}

	private void createConfigGroup(final Composite parent) {
		final Group g = createGroup(parent,
				UIText.CloneDestinationPage_groupConfiguration);

		newLabel(g, UIText.CloneDestinationPage_promptRemoteName + ":"); //$NON-NLS-1$
		remoteText = new Text(g, SWT.BORDER);
		remoteText.setText(Constants.DEFAULT_REMOTE_NAME);
		remoteText.setLayoutData(createFieldGridData());
		remoteText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				checkPage();
			}
		});
	}

	private void createProjectGroup(final Composite parent) {
		final Group group = createGroup(parent,
				UIText.CloneDestinationPage_groupProjects);

		GridLayoutFactory.swtDefaults().applyTo(group);
		importProjectsButton = new Button(group, SWT.CHECK);
		importProjectsButton.setText(UIText.CloneDestinationPage_importButton);
		importProjectsButton.setSelection(Activator.getDefault()
				.getPreferenceStore()
				.getBoolean(UIPreferences.CLONE_WIZARD_IMPORT_PROJECTS));
		importProjectsButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				Activator
						.getDefault()
						.getPreferenceStore()
						.setValue(UIPreferences.CLONE_WIZARD_IMPORT_PROJECTS,
								importProjectsButton.getSelection());
			}
		});

		// TODO: replace hardcoded ids once bug 245106 is fixed
		String[] workingSetTypes = new String[] {
				"org.eclipse.ui.resourceWorkingSetPage", //$NON-NLS-1$
				"org.eclipse.jdt.ui.JavaWorkingSetPage" //$NON-NLS-1$
		};
		workingSetGroup = new WorkingSetGroup(group, null, workingSetTypes);
	}

	private static Group createGroup(final Composite parent, final String text) {
		final Group g = new Group(parent, SWT.NONE);
		final GridLayout layout = new GridLayout();
		layout.numColumns = 2;
		g.setLayout(layout);
		g.setText(text);
		final GridData gd = new GridData();
		gd.grabExcessHorizontalSpace = true;
		gd.horizontalAlignment = SWT.FILL;
		g.setLayoutData(gd);
		return g;
	}

	private static void newLabel(final Group g, final String text) {
		new Label(g, SWT.NULL).setText(text);
	}

	private static GridData createFieldGridData() {
		return new GridData(SWT.FILL, SWT.DEFAULT, true, false);
	}

	/**
	 * @return true to import projects, false otherwise
	 */
	public boolean isImportProjects() {
		return importProjectsButton != null
				&& importProjectsButton.getSelection();
	}

	/**
	 * @return true to clone submodules, false otherwise
	 */
	public boolean isCloneSubmodules() {
		return cloneSubmodulesButton != null
				&& cloneSubmodulesButton.getSelection();
	}

	/**
	 * @return selected working sets
	 */
	public IWorkingSet[] getWorkingSets() {
		if (workingSetGroup == null)
			return new IWorkingSet[0];
		return workingSetGroup.getSelectedWorkingSets();
	}

	/**
	 * @return location the user wants to store this repository.
	 */
	public File getDestinationFile() {
		return new File(directoryText.getText());
	}

	/**
	 * @return initial branch selected (includes refs/heads prefix).
	 */
	public Ref getInitialBranch() {
		IStructuredSelection selection =
			(IStructuredSelection)initialBranch.getSelection();
		return (Ref)selection.getFirstElement();
	}

	/**
	 * @return remote name
	 */
	public String getRemote() {
		return remoteText.getText().trim();
	}

	/**
	 * Set the ID for context sensitive help
	 *
	 * @param id
	 *            help context
	 */
	public void setHelpContext(String id) {
		helpContext = id;
	}

	@Override
	public void performHelp() {
		PlatformUI.getWorkbench().getHelpSystem().displayHelp(helpContext);
	}

	/**
	 * Check internal state for page completion status.
	 */
	private void checkPage() {
		if (!cloneSettingsChanged()) {
			setErrorMessage(null);
			setPageComplete(true);
			return;
		}
		final String dstpath = directoryText.getText();
		if (dstpath.length() == 0) {
			setErrorMessage(UIText.CloneDestinationPage_errorDirectoryRequired);
			setPageComplete(false);
			return;
		}
		final File absoluteFile = new File(dstpath).getAbsoluteFile();
		if (!isEmptyDir(absoluteFile)) {
			setErrorMessage(NLS.bind(
					UIText.CloneDestinationPage_errorNotEmptyDir, absoluteFile
							.getPath()));
			setPageComplete(false);
			return;
		}

		if (!canCreateSubdir(absoluteFile.getParentFile())) {
			setErrorMessage(NLS.bind(UIText.GitCloneWizard_errorCannotCreate,
					absoluteFile.getPath()));
			setPageComplete(false);
			return;
		}
		if (!availableRefs.isEmpty()
			&& initialBranch.getCombo().getSelectionIndex() < 0) {
			setErrorMessage(UIText.CloneDestinationPage_errorInitialBranchRequired);
			setPageComplete(false);
			return;
		}
		String remoteName = getRemote();
		if (remoteName.length() == 0) {
			setErrorMessage(UIText.CloneDestinationPage_errorRemoteNameRequired);
			setPageComplete(false);
			return;
		}
		if (!Repository.isValidRefName(Constants.R_REMOTES + remoteName)) {
			setErrorMessage(NLS.bind(
					UIText.CloneDestinationPage_errorInvalidRemoteName,
					remoteName));
			setPageComplete(false);
			return;
		}

		setErrorMessage(null);
		setPageComplete(true);
	}

	void saveSettingsForClonedRepo() {
		clonedDestination = getDestinationFile();
		clonedInitialBranch = getInitialBranch();
		clonedRemote = getRemote();
	}

	boolean cloneSettingsChanged() {
		boolean cloneSettingsChanged = false;
		if (clonedDestination == null || !clonedDestination.equals(getDestinationFile()) ||
				clonedInitialBranch == null || !clonedInitialBranch.equals(getInitialBranch()) ||
				clonedRemote == null || !clonedRemote.equals(getRemote()))
			cloneSettingsChanged = true;
		return cloneSettingsChanged;
	}

	private static boolean isEmptyDir(final File dir) {
		if (!dir.exists())
			return true;
		if (!dir.isDirectory())
			return false;
		return dir.listFiles().length == 0;
	}

	// this is actually just an optimistic heuristic - should be named
	// isThereHopeThatCanCreateSubdir() as probably there is no 100% reliable
	// way to check that in Java for Windows
	private static boolean canCreateSubdir(final File parent) {
		if (parent == null)
			return true;
		if (parent.exists())
			return parent.isDirectory() && parent.canWrite();
		return canCreateSubdir(parent.getParentFile());
	}

	private void revalidate(RepositorySelection repoSelection, List<Ref> branches, Ref head) {
		if (repoSelection.equals(validatedRepoSelection)
				&& branches.equals(validatedSelectedBranches)
				&& head.equals(validatedHEAD)) {
			checkPage();
			return;
		}

		if (!repoSelection.equals(validatedRepoSelection)) {
			validatedRepoSelection = repoSelection;
			// update repo-related selection only if it changed
			final String n = validatedRepoSelection.getURI().getHumanishName();
			setDescription(NLS.bind(UIText.CloneDestinationPage_description, n));
			String defaultRepoDir = UIUtils.getDefaultRepositoryDir();
			File parentDir;
			if (defaultRepoDir.length() > 0)
				parentDir = new File(defaultRepoDir);
			else
				parentDir = ResourcesPlugin.getWorkspace().getRoot()
						.getRawLocation().toFile();
			directoryText.setText(new File(parentDir, n).getAbsolutePath());
		}

		validatedSelectedBranches = branches;
		validatedHEAD = head;

		initialBranch.setInput(branches);
		if (head != null && branches.contains(head))
			initialBranch.setSelection(new StructuredSelection(head));
		else if (branches.size() > 0)
			initialBranch
					.setSelection(new StructuredSelection(branches.get(0)));
		checkPage();
	}

	/**
	 * Set whether to show project import options
	 *
	 * @param show
	 * @return this wizard page
	 */
	public CloneDestinationPage setShowProjectImport(boolean show) {
		showProjectImport = show;
		return this;
	}
}
