/*******************************************************************************
 * Copyright (c) 2012, 2013 SAP AG and others.
 * 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
 *
 * Contributors:
 *    Mathias Kinzler (SAP AG) - initial implementation
 *******************************************************************************/
package org.eclipse.egit.ui.internal.push;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.egit.core.op.PushOperationResult;
import org.eclipse.egit.core.op.PushOperationSpecification;
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.credentials.EGitCredentialsProvider;
import org.eclipse.egit.ui.internal.gerrit.GerritUtil;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.jface.layout.GridDataFactory;
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.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.URIish;
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.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchCommandConstants;

/**
 * Push the current HEAD to Gerrit
 */
class PushToGerritPage extends WizardPage {
	private static final String PUSH_TO_GERRIT_PAGE_SECTION = "PushToGerritPage"; //$NON-NLS-1$

	private static final String LAST_URI_POSTFIX = ".lastUri"; //$NON-NLS-1$

	private static final String LAST_BRANCH_POSTFIX = ".lastBranch"; //$NON-NLS-1$

	private final Repository repository;

	private final IDialogSettings settings;

	private final String lastUriKey;

	private final String lastBranchKey;

	private Combo uriCombo;

	private Combo prefixCombo;

	private Label branchTextlabel;

	private Text branchText;

	/**
	 * @param repository
	 */
	PushToGerritPage(Repository repository) {
		super(PushToGerritPage.class.getName());
		this.repository = repository;
		setTitle(NLS.bind(UIText.PushToGerritPage_Title, Activator.getDefault()
				.getRepositoryUtil().getRepositoryName(repository)));
		setMessage(UIText.PushToGerritPage_Message);
		settings = getDialogSettings();
		lastUriKey = repository + LAST_URI_POSTFIX;
		lastBranchKey = repository + LAST_BRANCH_POSTFIX;
	}

	protected IDialogSettings getDialogSettings() {
		IDialogSettings s = Activator.getDefault().getDialogSettings();
		IDialogSettings section = s.getSection(PUSH_TO_GERRIT_PAGE_SECTION);
		if (section == null)
			section = s.addNewSection(PUSH_TO_GERRIT_PAGE_SECTION);
		return section;
	}

	public void createControl(Composite parent) {
		Composite main = new Composite(parent, SWT.NONE);
		main.setLayout(new GridLayout(3, false));
		GridDataFactory.fillDefaults().grab(true, true).applyTo(main);
		new Label(main, SWT.NONE).setText(UIText.PushToGerritPage_UriLabel);
		uriCombo = new Combo(main, SWT.DROP_DOWN);
		GridDataFactory.fillDefaults().grab(true, false).span(2, 1)
				.applyTo(uriCombo);
		uriCombo.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				checkPage();
			}
		});

		branchTextlabel = new Label(main, SWT.NONE);

		// we visualize the prefix here
		prefixCombo = new Combo(main, SWT.READ_ONLY | SWT.DROP_DOWN);
		prefixCombo.add(GerritUtil.REFS_FOR);
		prefixCombo.add("refs/drafts/"); //$NON-NLS-1$
		prefixCombo.select(0);

		branchTextlabel.setText(UIText.PushToGerritPage_BranchLabel);
		branchText = new Text(main, SWT.SINGLE | SWT.BORDER);
		GridDataFactory.fillDefaults().grab(true, false).applyTo(branchText);
		branchText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				checkPage();
			}
		});

		// give focus to the nameText if label is activated using the mnemonic
		branchTextlabel.addTraverseListener(new TraverseListener() {
			public void keyTraversed(TraverseEvent e) {
				branchText.setFocus();
				branchText.selectAll();
			}
		});
		addRefContentProposalToText(branchText);

		// get all available URIs from the repository
		SortedSet<String> uris = new TreeSet<String>();
		try {
			for (RemoteConfig rc : RemoteConfig.getAllRemoteConfigs(repository
					.getConfig())) {
				if (rc.getURIs().size() > 0)
					uris.add(rc.getURIs().get(0).toPrivateString());
				for (URIish u : rc.getPushURIs())
					uris.add(u.toPrivateString());

			}
		} catch (URISyntaxException e) {
			Activator.handleError(e.getMessage(), e, false);
			setErrorMessage(e.getMessage());
		}
		for (String aUri : uris)
			uriCombo.add(aUri);
		selectLastUsedUri();
		setLastUsedBranch();
		branchText.setFocus();
		Dialog.applyDialogFont(main);
		setControl(main);
	}

	private void storeLastUsedUri(String uri) {
		settings.put(lastUriKey, uri.trim());
	}

	private void storeLastUsedBranch(String branch) {
		settings.put(lastBranchKey, branch.trim());
	}

	private void selectLastUsedUri() {
		String lastUri = settings.get(lastUriKey);
		if (lastUri != null) {
			int i = uriCombo.indexOf(lastUri);
			if (i != -1) {
				uriCombo.select(i);
				return;
			}
		}
		uriCombo.select(0);
	}

	private void setLastUsedBranch() {
		String lastBranch = settings.get(lastBranchKey);
		if (lastBranch != null)
			branchText.setText(lastBranch);
	}

	private void checkPage() {
		setErrorMessage(null);
		try {
			if (uriCombo.getText().length() == 0) {
				setErrorMessage(UIText.PushToGerritPage_MissingUriMessage);
				return;
			}
			if (branchText.getText().length() == 0) {
				setErrorMessage(UIText.PushToGerritPage_MissingBranchMessage);
				return;
			}
		} finally {
			setPageComplete(getErrorMessage() == null);
		}
	}

	void doPush(IProgressMonitor monitor) {
		try {
			int timeout = Activator.getDefault().getPreferenceStore()
					.getInt(UIPreferences.REMOTE_CONNECTION_TIMEOUT);
			URIish uri = new URIish(uriCombo.getText());
			Ref currentHead = repository.getRef(Constants.HEAD);
			RemoteRefUpdate update = new RemoteRefUpdate(repository,
					currentHead, prefixCombo.getItem(prefixCombo
							.getSelectionIndex()) + branchText.getText(),
					false, null, null);
			PushOperationSpecification spec = new PushOperationSpecification();

			spec.addURIRefUpdates(uri, Arrays.asList(update));
			PushOperationUI op = new PushOperationUI(repository, spec, timeout,
					false);
			op.setCredentialsProvider(new EGitCredentialsProvider());
			PushOperationResult result = op.execute(monitor);
			PushResultDialog dlg = new PushResultDialog(getShell(), repository,
					result, op.getDestinationString());
			dlg.showConfigureButton(false);
			dlg.open();
			storeLastUsedUri(uriCombo.getText());
			storeLastUsedBranch(branchText.getText());
		} catch (CoreException e) {
			Activator.handleError(e.getMessage(), e, true);
		} catch (URISyntaxException e) {
			Activator.handleError(e.getMessage(), e, true);
		} catch (IOException e) {
			Activator.handleError(e.getMessage(), e, true);
		}
	}

	private void addRefContentProposalToText(final Text textField) {
		KeyStroke stroke = UIUtils
				.getKeystrokeOfBestActiveBindingFor(IWorkbenchCommandConstants.EDIT_CONTENT_ASSIST);
		if (stroke != null)
			UIUtils.addBulbDecorator(textField, NLS.bind(
					UIText.PushToGerritPage_ContentProposalHoverText,
					stroke.format()));

		IContentProposalProvider cp = new IContentProposalProvider() {
			public IContentProposal[] getProposals(String contents, int position) {
				List<IContentProposal> resultList = new ArrayList<IContentProposal>();

				// make the simplest possible pattern check: allow "*"
				// for multiple characters
				String patternString = contents;
				// ignore spaces in the beginning
				while (patternString.length() > 0
						&& patternString.charAt(0) == ' ') {
					patternString = patternString.substring(1);
				}

				// we quote the string as it may contain spaces
				// and other stuff colliding with the Pattern
				patternString = Pattern.quote(patternString);

				patternString = patternString.replaceAll("\\x2A", ".*"); //$NON-NLS-1$ //$NON-NLS-2$

				// make sure we add a (logical) * at the end
				if (!patternString.endsWith(".*")) //$NON-NLS-1$
					patternString = patternString + ".*"; //$NON-NLS-1$

				// let's compile a case-insensitive pattern (assumes ASCII only)
				Pattern pattern;
				try {
					pattern = Pattern.compile(patternString,
							Pattern.CASE_INSENSITIVE);
				} catch (PatternSyntaxException e) {
					pattern = null;
				}

				Set<String> proposals = new TreeSet<String>();

				try {
					Set<String> remotes = repository.getRefDatabase()
							.getRefs(Constants.R_REMOTES).keySet();
					for (String remote : remotes) {
						// these are "origin/master", "origin/xxx"...
						int slashIndex = remote.indexOf('/');
						if (slashIndex > 0 && slashIndex < remote.length() - 1)
							proposals
									.add(remote.substring(remote.indexOf('/') + 1));
					}
				} catch (IOException e) {
					// simply ignore, no proposals then
				}

				for (final String proposal : proposals) {
					if (pattern != null && !pattern.matcher(proposal).matches())
						continue;
					IContentProposal propsal = new BranchContentProposal(
							proposal);
					resultList.add(propsal);
				}

				return resultList.toArray(new IContentProposal[resultList
						.size()]);
			}
		};

		ContentProposalAdapter adapter = new ContentProposalAdapter(textField,
				new TextContentAdapter(), cp, stroke, null);
		// set the acceptance style to always replace the complete content
		adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
	}

	private final static class BranchContentProposal implements
			IContentProposal {
		private final String myString;

		BranchContentProposal(String string) {
			myString = string;
		}

		public String getContent() {
			return myString;
		}

		public int getCursorPosition() {
			return 0;
		}

		public String getDescription() {
			return myString;
		}

		public String getLabel() {
			return myString;
		}

		@Override
		public String toString() {
			return getContent();
		}
	}
}
