/*******************************************************************************
 * Copyright (c) 2011 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is 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:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package org.jboss.tools.openshift.express.internal.ui.wizard.connection;

import java.lang.reflect.InvocationTargetException;

import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.ValidationStatusProvider;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.databinding.validation.MultiValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.databinding.fieldassist.ControlDecorationSupport;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.databinding.viewers.ViewerProperties;
import org.eclipse.jface.dialogs.PageChangingEvent;
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.wizard.IWizard;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Text;
import org.jboss.tools.common.ui.BrowserUtil;
import org.jboss.tools.common.ui.DelegatingProgressMonitor;
import org.jboss.tools.common.ui.JobUtils;
import org.jboss.tools.common.ui.WizardUtils;
import org.jboss.tools.common.ui.databinding.InvertingBooleanConverter;
import org.jboss.tools.common.ui.databinding.ParametrizableWizardPageSupport;
import org.jboss.tools.common.ui.databinding.ValueBindingBuilder;
import org.jboss.tools.openshift.egit.ui.util.EGitUIUtils;
import org.jboss.tools.openshift.express.internal.core.connection.Connection;
import org.jboss.tools.openshift.express.internal.ui.OpenShiftUIActivator;
import org.jboss.tools.openshift.express.internal.ui.OpenshiftUIMessages;
import org.jboss.tools.openshift.express.internal.ui.databinding.RequiredControlDecorationUpdater;
import org.jboss.tools.openshift.express.internal.ui.databinding.RequiredStringValidator;
import org.jboss.tools.openshift.express.internal.ui.databinding.TrimmingStringConverter;
import org.jboss.tools.openshift.express.internal.ui.explorer.AbstractLabelProvider;
import org.jboss.tools.openshift.express.internal.ui.utils.Logger;
import org.jboss.tools.openshift.express.internal.ui.utils.UIUpdatingJob;
import org.jboss.tools.openshift.express.internal.ui.viewer.ConnectionColumLabelProvider;
import org.jboss.tools.openshift.express.internal.ui.viewer.NewConnectionAwareConnectionComparer;
import org.jboss.tools.openshift.express.internal.ui.viewer.NewConnectionMarker;
import org.jboss.tools.openshift.express.internal.ui.wizard.AbstractOpenShiftWizardPage;
import org.jboss.tools.openshift.express.internal.ui.wizard.IConnectionAwareModel;

/**
 * @author Andre Dietisheim
 * @author Xavier Coulon
 */
public class ConnectionWizardPage extends AbstractOpenShiftWizardPage {

	protected static final String OPENSHIFT_EXPRESS_SIGNUP_URL = "https://openshift.redhat.com/app/user/new/express"; //$NON-NLS-1$

	private final ConnectionWizardPageModel pageModel;
	private Text connectionCompositeUsernameText;
	private Text connectionCompositePasswordText;
	private Text passwordCompositePasswordText;

	public ConnectionWizardPage(IWizard wizard, IConnectionAwareModel wizardModel) {
		super("Sign in to OpenShift", "Please provide your OpenShift credentials.", "Server Connection",
				wizard);
		this.pageModel = new ConnectionWizardPageModel(wizardModel);
		/*
		 * JBIDE-12999: ensure EclipseAuthenticator is installed and overrides
		 * NetAuthenticator
		 */
		EGitUIUtils.ensureEgitUIIsStarted();
	}

	protected void doCreateControls(Composite container, DataBindingContext dbc) {
		GridLayoutFactory.fillDefaults().numColumns(2).margins(10, 10).applyTo(container);

		Link signupLink = new Link(container, SWT.WRAP);
		signupLink.setText("If you do not have an account on OpenShift, please sign up <a>here</a>.");
		GridDataFactory.fillDefaults()
				.align(SWT.LEFT, SWT.CENTER).span(2, 1).applyTo(signupLink);
		signupLink.addSelectionListener(onSignupLinkClicked());

		Label fillerLabel = new Label(container, SWT.NONE);
		GridDataFactory.fillDefaults()
				.span(2, 1).hint(SWT.DEFAULT, 6).applyTo(fillerLabel);

		Label connectionLabel = new Label(container, SWT.NONE);
		connectionLabel.setText("Connection:");
		GridDataFactory.fillDefaults()
				.align(SWT.LEFT, SWT.CENTER).hint(100, SWT.DEFAULT).applyTo(connectionLabel);
		Combo connectionCombo = new Combo(container, SWT.DEFAULT);
		GridDataFactory.fillDefaults()
				.align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(connectionCombo);
		final ComboViewer connectionComboViewer = new ComboViewer(connectionCombo);
		connectionComboViewer.setContentProvider(ArrayContentProvider.getInstance());
		connectionComboViewer.setLabelProvider(new ConnectionColumLabelProvider());
		connectionComboViewer.setInput(pageModel.getConnections());
		connectionComboViewer.setComparer(new NewConnectionAwareConnectionComparer());

		Binding selectedConnectionBinding = ValueBindingBuilder
				.bind(ViewerProperties.singleSelection().observe(connectionComboViewer))
				.validatingAfterGet(new IValidator() {

					@Override
					public IStatus validate(Object value) {
						if (value == null) {
							return ValidationStatus.cancel("You have to select or create a new connection.");
						}
						return ValidationStatus.ok();
					}
				})
				.to(BeanProperties.value(
						ConnectionWizardPageModel.PROPERTY_SELECTED_CONNECTION, Connection.class).observe(
						pageModel))
				.in(dbc);
		ControlDecorationSupport
				.create(selectedConnectionBinding, SWT.LEFT | SWT.TOP, null, new RequiredControlDecorationUpdater());

		// stack with connection widgets / password widget
		Composite connectionWidgetsContainer = new Composite(container, SWT.NONE);
		GridDataFactory.fillDefaults()
				.span(2, 1).applyTo(connectionWidgetsContainer);
		StackLayout stackLayout = new StackLayout();
		connectionWidgetsContainer.setLayout(stackLayout);
		Composite connectionWidgets = createNewConnectionComposite(connectionWidgetsContainer, dbc);
		Composite passwordWidgets = createExistingConnectionComposite(connectionWidgetsContainer, dbc);

		showConnectionWidgets(pageModel.isCreateNewConnection(), passwordWidgets, connectionWidgets, stackLayout,
				connectionWidgetsContainer);

		BeanProperties
				.value(ConnectionWizardPageModel.PROPERTY_SELECTED_CONNECTION)
				.observe(pageModel)
				.addValueChangeListener(
						onNewConnectionSelected(passwordWidgets, connectionWidgets, stackLayout,
								connectionWidgetsContainer));
	}

	private IValueChangeListener onNewConnectionSelected(final Composite passwordWidget,
			final Composite connectionWidgets, final StackLayout stackLayout, final Composite container) {
		return new IValueChangeListener() {

			@Override
			public void handleValueChange(ValueChangeEvent event) {
				Connection selectedConnection = pageModel.getSelectedConnection();
				boolean isNewConnection = selectedConnection instanceof NewConnectionMarker;
				showConnectionWidgets(isNewConnection, passwordWidget, connectionWidgets, stackLayout, container);
			}
		};
	}

	private void showConnectionWidgets(boolean isNewConnection, Composite passwordWidgets,
			Composite connectionsWidgets,
			StackLayout stackLayout, Composite container) {
		Control topControl = null;
		if (isNewConnection) {
			topControl = connectionsWidgets;
		} else {
			topControl = passwordWidgets;
		}
		stackLayout.topControl = topControl;
		container.layout();
	}

	private Composite createExistingConnectionComposite(Composite container, DataBindingContext dbc) {
		Composite passwordWidgets = new Composite(container, SWT.NONE);
		GridLayoutFactory.fillDefaults()
				.numColumns(2).applyTo(passwordWidgets);

		// password
		Label passwordLabel = new Label(passwordWidgets, SWT.NONE);
		passwordLabel.setText("&Password:");
		GridDataFactory.fillDefaults()
				.align(SWT.LEFT, SWT.CENTER).hint(100, SWT.DEFAULT).applyTo(passwordLabel);
		this.passwordCompositePasswordText = new Text(passwordWidgets, SWT.BORDER | SWT.PASSWORD);
		GridDataFactory.fillDefaults()
				.align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(passwordCompositePasswordText);
		Binding passwordBinding = ValueBindingBuilder
				.bind(WidgetProperties.text(SWT.Modify).observe(passwordCompositePasswordText))
				.validatingAfterGet(new RequiredStringValidator("password"))
				.to(BeanProperties.value(ConnectionWizardPageModel.PROPERTY_PASSWORD).observe(pageModel))
				.in(dbc);
		ControlDecorationSupport
				.create(passwordBinding, SWT.LEFT | SWT.TOP, null, new RequiredControlDecorationUpdater());

		// remember password
		Button rememberPasswordCheckBox = new Button(passwordWidgets, SWT.CHECK);
		rememberPasswordCheckBox.setText(OpenshiftUIMessages.OpenshiftWizardSavePassword);
		GridDataFactory.fillDefaults()
				.align(SWT.FILL, SWT.CENTER).span(2, 1).grab(true, false).applyTo(rememberPasswordCheckBox);
		ValueBindingBuilder
				.bind(WidgetProperties.selection().observe(rememberPasswordCheckBox))
				.to(BeanProperties.value(ConnectionWizardPageModel.PROPERTY_REMEMBER_PASSWORD).observe(pageModel))
				.in(dbc);

		// credentials status
		IObservableValue credentialsStatusObservable =
				BeanProperties.value(ConnectionWizardPageModel.PROPERTY_VALID, IStatus.class).observe(pageModel);
		final CredentialsValidator credentialsValidator =
				new CredentialsValidator(credentialsStatusObservable);
		dbc.addValidationStatusProvider(credentialsValidator);
		ControlDecorationSupport.create(credentialsValidator, SWT.LEFT | SWT.TOP);

		return passwordWidgets;
	}

	private Composite createNewConnectionComposite(Composite container, DataBindingContext dbc) {
		Composite connectionWidgets = new Composite(container, SWT.NONE);
		GridLayoutFactory.fillDefaults()
				.numColumns(2).applyTo(connectionWidgets);

		// use default server
		Button defaultServerCheckbox = new Button(connectionWidgets, SWT.CHECK);
		defaultServerCheckbox.setText("Use default server");
		GridDataFactory.fillDefaults()
				.align(SWT.FILL, SWT.CENTER).span(2, 1).applyTo(defaultServerCheckbox);
		ValueBindingBuilder
				.bind(WidgetProperties.selection().observe(defaultServerCheckbox))
				.to(BeanProperties.value(ConnectionWizardPageModel.PROPERTY_USE_DEFAULTSERVER).observe(pageModel))
				.in(dbc);

		// host
		Label serverLabel = new Label(connectionWidgets, SWT.NONE);
		serverLabel.setText("&Server:");
		GridDataFactory.fillDefaults()
				.align(SWT.LEFT, SWT.CENTER).hint(100, SWT.DEFAULT).applyTo(serverLabel);
		Combo serversCombo = new Combo(connectionWidgets, SWT.BORDER);
		ComboViewer serverComboViewer = new ComboViewer(serversCombo);
		serverComboViewer.setLabelProvider(new ServerLabelProvider());
		serverComboViewer.setContentProvider(ArrayContentProvider.getInstance());
		serverComboViewer.setInput(pageModel.getServers());
		GridDataFactory.fillDefaults()
				.align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(serversCombo);
		IObservableValue serverObservable = WidgetProperties.text().observe(serversCombo);
		Binding serverBinding = ValueBindingBuilder
				.bind(serverObservable)
				.to(BeanProperties.value(ConnectionWizardPageModel.PROPERTY_HOST).observe(pageModel))
				.in(dbc);
		ControlDecorationSupport
				.create(serverBinding, SWT.LEFT | SWT.TOP, null, new RequiredControlDecorationUpdater());
		ValidationStatusProvider hostValidation = 
				new RequiredStringValidationProvider(serverObservable, "server");
		dbc.addValidationStatusProvider(hostValidation);
		ControlDecorationSupport
				.create(hostValidation, SWT.LEFT | SWT.TOP, null, new RequiredControlDecorationUpdater());
		ValueBindingBuilder
				.bind(WidgetProperties.enabled().observe(serversCombo))
				.notUpdatingParticipant()
				.to(BeanProperties.value(ConnectionWizardPageModel.PROPERTY_USE_DEFAULTSERVER).observe(pageModel))
				.converting(new InvertingBooleanConverter())
				.in(dbc);

		// username
		Label rhLoginLabel = new Label(connectionWidgets, SWT.NONE);
		rhLoginLabel.setText("&Username:");
		GridDataFactory.fillDefaults()
				.align(SWT.LEFT, SWT.CENTER).applyTo(rhLoginLabel);
		connectionCompositeUsernameText = new Text(connectionWidgets, SWT.BORDER);
		GridDataFactory.fillDefaults()
				.align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(connectionCompositeUsernameText);
				IObservableValue usernameObservable = WidgetProperties.text(SWT.Modify).observe(connectionCompositeUsernameText);
				ValueBindingBuilder
						.bind(usernameObservable)
		 				.converting(new TrimmingStringConverter())
				.to(BeanProperties.value(ConnectionWizardPageModel.PROPERTY_USERNAME).observe(pageModel))
				.in(dbc);
		ValidationStatusProvider usernameValidation = 
				new RequiredStringValidationProvider(usernameObservable, "username");
		dbc.addValidationStatusProvider(usernameValidation);
		ControlDecorationSupport
				.create(usernameValidation, SWT.LEFT | SWT.TOP, null, new RequiredControlDecorationUpdater());

		// password
		Label passwordLabel = new Label(connectionWidgets, SWT.NONE);
		passwordLabel.setText("&Password:");
		GridDataFactory.fillDefaults()
				.align(SWT.LEFT, SWT.CENTER).applyTo(passwordLabel);
		this.connectionCompositePasswordText = new Text(connectionWidgets, SWT.BORDER | SWT.PASSWORD);
		GridDataFactory.fillDefaults()
				.align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(connectionCompositePasswordText);
		IObservableValue passwordObservable = WidgetProperties.text(SWT.Modify).observe(connectionCompositePasswordText);
		ValueBindingBuilder
				.bind(passwordObservable)
				.to(BeanProperties.value(ConnectionWizardPageModel.PROPERTY_PASSWORD).observe(pageModel))
				.in(dbc);
		ValidationStatusProvider passwordValidation =
				new RequiredStringValidationProvider(passwordObservable, "password");
		dbc.addValidationStatusProvider(passwordValidation);
		ControlDecorationSupport
				.create(passwordValidation, SWT.LEFT | SWT.TOP, null, new RequiredControlDecorationUpdater());

		Button rememberPasswordCheckBox = new Button(connectionWidgets, SWT.CHECK);
		rememberPasswordCheckBox.setText(OpenshiftUIMessages.OpenshiftWizardSavePassword);
		GridDataFactory.fillDefaults()
				.align(SWT.FILL, SWT.CENTER).span(2, 1).grab(true, false).applyTo(rememberPasswordCheckBox);
		ValueBindingBuilder
				.bind(WidgetProperties.selection().observe(rememberPasswordCheckBox))
				.to(BeanProperties.value(ConnectionWizardPageModel.PROPERTY_REMEMBER_PASSWORD).observe(pageModel))
				.in(dbc);
		// credentials status
		IObservableValue credentialsStatusObservable =
				BeanProperties.value(ConnectionWizardPageModel.PROPERTY_VALID, IStatus.class).observe(pageModel);
		final CredentialsValidator credentialsValidator =
				new CredentialsValidator(credentialsStatusObservable);
		dbc.addValidationStatusProvider(credentialsValidator);

		return connectionWidgets;
	}

	protected SelectionAdapter onSignupLinkClicked() {
		return new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				BrowserUtil.checkedCreateInternalBrowser(OPENSHIFT_EXPRESS_SIGNUP_URL, OPENSHIFT_EXPRESS_SIGNUP_URL,
						OpenShiftUIActivator.PLUGIN_ID, OpenShiftUIActivator.getDefault().getLog());
				getContainer().getShell().close();
			}
		};
	}

	@Override
	protected void onPageActivated(DataBindingContext dbc) {
		super.onPageActivated(dbc);
		setInitialFocus();
	}

	@Override
	protected void onPageWillGetDeactivated(Direction direction, PageChangingEvent event, DataBindingContext dbc) {
		if (direction == Direction.BACKWARDS) {
			return;
		}
		event.doit = connect();
		if (!event.doit) {
			setInitialFocus();
		}
	}
	
	private void setInitialFocus() {
		if (pageModel.isCreateNewConnection()) {
			if (connectionCompositeUsernameText.getText().isEmpty()) {
				connectionCompositeUsernameText.setFocus();
			} else {
				connectionCompositePasswordText.setFocus();
				connectionCompositePasswordText.selectAll();
			}
		} else {
			passwordCompositePasswordText.setFocus();
			passwordCompositePasswordText.selectAll();
		}
	}

	public boolean connect() {
		try {
			WizardUtils.runInWizard(
					new ConnectJob(), new DelegatingProgressMonitor(), getContainer(), getDatabindingContext());
			return JobUtils.isOk(pageModel.getValid());
		} catch (InterruptedException e) {
			Logger.error("Failed to authenticate on OpenShift", e);
			return false;
		} catch (InvocationTargetException e) {
			Logger.error("Failed to authenticate on OpenShift", e);
			return false;
		}
	}

	class CredentialsValidator extends MultiValidator {

		private final IObservableValue credentialsStatusObservable;

		public CredentialsValidator(IObservableValue credentialsStatusObservable) {
			this.credentialsStatusObservable = credentialsStatusObservable;
		}

		@Override
		protected IStatus validate() {
			final IStatus credentialsValidityStatus = (IStatus) credentialsStatusObservable.getValue();

			if (credentialsValidityStatus != null) {
				return pageModel.getValid();
			}
			return ValidationStatus.ok();
		}
	}

	class RequiredStringValidationProvider extends MultiValidator {

		private IObservableValue observableValue;
		private String name;

		public RequiredStringValidationProvider(IObservableValue value, String name) {
			this.observableValue = value;
			this.name = name;
		}

		@Override
		protected IStatus validate() {
			if (!(observableValue.getValue() instanceof String)) {
				return ValidationStatus.cancel("You have to provide a " + name);
			}
			String string = (String) observableValue.getValue();
			if (string.isEmpty()) {
				return ValidationStatus.cancel("You have to provide a " + name);
			}
			return ValidationStatus.ok();
		}

		@Override
		public IObservableList getTargets() {
			IObservableList targets = new WritableList();
			targets.add(observableValue);
			return targets;
		}

	}		

	public Connection getConnection() {
		return pageModel.getConnection();
	}

	@Override
	protected void setupWizardPageSupport(DataBindingContext dbc) {
		ParametrizableWizardPageSupport.create(IStatus.ERROR | IStatus.CANCEL, this, dbc);
	}

	private class ServerLabelProvider extends AbstractLabelProvider {
		@Override
		public String getText(Object element) {
			if (!(element instanceof String)) {
				return null;
			}
			return (String) element;
		}
	}

	private class ConnectJob extends UIUpdatingJob {

		private IStatus connectionStatus;

		private ConnectJob() {
			super("Verifying user credentials...");
		}

		@Override
		protected IStatus run(IProgressMonitor monitor) {
			connectionStatus = pageModel.connect();
			monitor.done();
			return Status.OK_STATUS;
		}

		@Override
		protected IStatus updateUI(IProgressMonitor monitor) {
			if (!JobUtils.isOk(connectionStatus)) {
				return Status.OK_STATUS;
			}
			pageModel.createOrUpdateConnection();

			return Status.OK_STATUS;
		}
	}
}
