/*******************************************************************************
 * Copyright (C) 2011, Stefan Lay <stefan.lay@sap.com>
 * Copyright (C) 2011, 2013, Matthias Sohn <matthias.sohn@sap.com>
 *
 * 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.gerrit;

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

import org.eclipse.egit.core.internal.gerrit.GerritUtil;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.SWTUtils;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.components.RepositorySelectionPage.Protocol;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.dialogs.Dialog;
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.wizard.WizardPage;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
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.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
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.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.PlatformUI;

/**
 * Wizard page that offers simplified configuration if upstream repository
 * is hosted by Gerrit.
 */
class GerritConfigurationPage extends WizardPage {

	private final static int GERRIT_DEFAULT_SSH_PORT = 29418;

	private String helpContext = null;

	private Text branch;

	private Group pushConfigurationGroup;

	private Group fetchConfigurationGroup;

	private Group uriGroup;

	private Combo scheme;

	private Text uriText;

	private URIish pushURI;

	private Text user;

	private int eventDepth;

	private final Repository repository;

	private final String remoteName;

	/**
	 * @param repository the respository
	 * @param remoteName the remote name
	 *
	 */
	public GerritConfigurationPage(Repository repository, String remoteName) {
		super(GerritConfigurationPage.class.getName());
		this.repository = repository;
		this.remoteName = remoteName;
		setTitle(UIText.GerritConfigurationPage_title);
		String repositoryName = Activator.getDefault().getRepositoryUtil().getRepositoryName(repository);
		setDescription(MessageFormat.format(
				UIText.GerritConfigurationPage_PageDescription, remoteName,
				repositoryName));

	}

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

		createURIGroup(panel);
		createPushConfigurationGroup(panel);
		createFetchConfigurationGroup(panel);

		Dialog.applyDialogFont(panel);
		setControl(panel);
		uriText.setFocus();
	}

	private void createURIGroup(Composite panel) {
		uriGroup = SWTUtils.createHFillGroup(panel,
				UIText.GerritConfigurationPage_pushUri,
				SWTUtils.MARGINS_DEFAULT, 2);

		scheme = new Combo(uriGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
		uriText = SWTUtils.createText(uriGroup);

		new Label(uriGroup, SWT.NULL).setText(UIText.GerritConfigurationPage_UserLabel);
		user = SWTUtils.createText(uriGroup);
		user.addModifyListener(new ModifyListener() {
			public void modifyText(final ModifyEvent e) {
				eventDepth++;
				try {
					if (eventDepth == 1) {
						if (pushURI != null) {
							pushURI = pushURI.setUser(user.getText());
							uriText.setText(pushURI.toString());
							checkPage();
						}
					}
				} finally {
					eventDepth--;
				}
			}
		});

		uriText.addModifyListener(new ModifyListener() {
			public void modifyText(final ModifyEvent e) {
				eventDepth++;
				try {
					if (eventDepth == 1) {
						URIish u = new URIish(uriText.getText());
						String newUser = u.getUser();
						user.setText(newUser != null ? newUser : ""); //$NON-NLS-1$
					}
				} catch (URISyntaxException e1) {
					// empty
				} finally {
					eventDepth--;
				}
				checkPage();
			}
		});

		for (Protocol p : Protocol.values()) {
			scheme.add(p.getDefaultScheme());
		}
		scheme.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(final SelectionEvent e) {
				final int idx = scheme.getSelectionIndex();
				pushURI = pushURI.setScheme(scheme.getItem(idx));

				if (Protocol.SSH.handles(pushURI))
					pushURI = pushURI.setPort(GERRIT_DEFAULT_SSH_PORT);
				else
					pushURI = pushURI.setPort(-1);

				uriText.setText(pushURI.toString());
				scheme.setToolTipText(Protocol.values()[idx].getTooltip());
			}
		});
	}

	private void createPushConfigurationGroup(Composite panel) {
		pushConfigurationGroup = SWTUtils.createHFillGroup(panel,
				UIText.GerritConfigurationPage_groupPush,
				SWTUtils.MARGINS_DEFAULT, 3);
		Label branchLabel = new Label(pushConfigurationGroup, SWT.NULL);
		branchLabel
				.setText(UIText.GerritConfigurationPage_labelDestinationBranch);
		// we visualize the prefix here
		Text prefix = new Text(pushConfigurationGroup, SWT.READ_ONLY);
		prefix.setText(GerritUtil.REFS_FOR);
		prefix.setEnabled(false);

		branch = SWTUtils.createText(pushConfigurationGroup);
		branch.addModifyListener(new ModifyListener() {
			public void modifyText(final ModifyEvent e) {
				checkPage();
			}
		});

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

	private void createFetchConfigurationGroup(Composite panel) {
		fetchConfigurationGroup = SWTUtils.createHFillGroup(panel,
				UIText.GerritConfigurationPage_groupFetch,
				SWTUtils.MARGINS_DEFAULT, 2);
		new Label(fetchConfigurationGroup, SWT.NULL)
				.setText(UIText.GerritConfigurationPage_ConfigureFetchReviewNotes);
	}

	/**
	 * @return the push URI for Gerrit code review
	 */
	public URIish getURI() {
		return pushURI;
	}

	/**
	 * @return the branch used in the gerrit push refspec: refs/for/branch
	 */
	public String getBranch() {
		return branch.getText();
	}

	/**
	 * 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);
	}

	/**
	 * @param uri the URI of the source repository
	 */
	public void setSelection(URIish uri) {
		setSelection(uri, null);
	}

	/**
	 * @param uri
	 *            the URI of the source repository
	 * @param targetBranch
	 */
	public void setSelection(URIish uri, String targetBranch) {

		setDefaults(uri, targetBranch);
		checkPage();
	}

	private void setDefaults(URIish uri, String targetBranch) {
		URIish newPushURI = uri;
		if (Protocol.SSH.handles(uri)) {
			newPushURI = newPushURI.setPort(GERRIT_DEFAULT_SSH_PORT);
		} else if (Protocol.GIT.handles(uri)) {
			newPushURI = newPushURI.setScheme(Protocol.SSH.getDefaultScheme());
			newPushURI = newPushURI.setPort(GERRIT_DEFAULT_SSH_PORT);
		}
		uriText.setText(newPushURI.toString());
		final String uriScheme = newPushURI.getScheme();
		if (uriScheme != null)
			scheme.select(scheme.indexOf(uriScheme));
		branch.setText(targetBranch != null ? targetBranch : Constants.MASTER);
	}

	private void checkPage() {
		try {
			pushURI = new URIish(uriText.getText());
			String uriScheme = pushURI.getScheme();
			if (uriScheme != null)
				scheme.select(scheme.indexOf(uriScheme));
		} catch (URISyntaxException e) {
			setErrorMessage(e.getLocalizedMessage());
			setPageComplete(false);
			return;
		}
		String branchName = branch.getText();
		if (branchName.length() == 0) {
			setErrorMessage(UIText.GerritConfigurationPage_errorBranchName);
			setPageComplete(false);
			return;
		}

		setErrorMessage(null);
		setPageComplete(true);
	}

	private void addRefContentProposalToText(final Text textField) {
		KeyStroke stroke = UIUtils
				.getKeystrokeOfBestActiveBindingFor(IWorkbenchCommandConstants.EDIT_CONTENT_ASSIST);
		if (stroke != null)
			UIUtils.addBulbDecorator(textField, NLS.bind(
					UIText.GerritConfigurationPage_BranchTooltipHover,
					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 {
					// propose the names of the remote tracking
					// branches for the given remote
					Set<String> remotes = repository
							.getRefDatabase()
							.getRefs(Constants.R_REMOTES + remoteName + "/").keySet(); //$NON-NLS-1$
					proposals.addAll(remotes);
				} 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();
		}
	}

}
