/******************************************************************************* 
 * Copyright (c) 2012 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.utils;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.egit.ui.internal.credentials.EGitCredentialsProvider;
import org.eclipse.jgit.transport.CredentialsProviderUserInfo;
import org.eclipse.jgit.transport.JschConfigSessionFactory;
import org.eclipse.jgit.transport.OpenSshConfig;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FS;
import org.eclipse.jsch.core.IJSchService;
import org.eclipse.jsch.ui.UserInfoPrompter;
import org.jboss.tools.openshift.express.internal.ui.OpenShiftUIActivator;
import org.jboss.tools.openshift.express.internal.ui.console.JschToEclipseLogger;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;

import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;
import com.openshift.client.IApplication;
import com.openshift.client.OpenShiftSSHOperationException;

/**
 * Same as EclipseSshSessinFactory, but provides a mean to retrieve the pure Jsch Session, not a RemoteSession.
 * 
 * @author Xavier Coulon
 * 
 */
@SuppressWarnings("restriction")
public class OpenShiftSshSessionFactory extends JschConfigSessionFactory {

	private static OpenShiftSshSessionFactory INSTANCE = new OpenShiftSshSessionFactory();

	/**
	 * Get the currently configured JVM-wide factory.
	 * <p>
	 * A factory is always available. By default the factory will read from the user's <code>$HOME/.ssh</code> and
	 * assume OpenSSH compatibility.
	 * 
	 * @return factory the current factory for this JVM.
	 */
	public static OpenShiftSshSessionFactory getInstance() {
		return INSTANCE;
	}

	private final IJSchService provider;

	private OpenSshConfig config;

	private final Map<URIish, Session> cache = new HashMap<URIish, Session>();

	OpenShiftSshSessionFactory() {
		final BundleContext context = OpenShiftUIActivator.getDefault().getBundle().getBundleContext();
		final ServiceReference<?> ssh = context.getServiceReference(IJSchService.class.getName());
		this.provider = (IJSchService) context.getService(ssh);
	}

	public Session createSession(final IApplication application) throws OpenShiftSSHOperationException {
		final URIish uri = getSshUri(application);
		final Session session = cache.get(uri);
		if (session == null || !session.isConnected()) {
			final FS fs = FS.DETECTED;
			if (config == null) {
				config = OpenSshConfig.get(fs);
			}
			String user = uri.getUser();
			String host = uri.getHost();
			int port = uri.getPort();
			JSch.setLogger(new JschToEclipseLogger());
			final OpenSshConfig.Host hc = config.lookup(host);
			try {
				cache.put(uri, createSession(hc, user, host, port, fs));
			} catch (JSchException e) {
				throw new OpenShiftSSHOperationException(e, "Unable to create SSH session for application ''{0}''", application);
			}
		}
		return cache.get(uri);
	}

	static URIish getSshUri(IApplication application) {
		final String host = application.getName() + "-" + application.getDomain().getId() + "."
				+ application.getDomain().getSuffix();
		final String user = application.getUUID();
		final URIish uri = new URIish().setHost(host).setPort(22).setUser(user);
		return uri;
	}
	
	@Override
	protected Session createSession(final OpenSshConfig.Host hc, final String user, final String host, final int port,
			FS fs) throws JSchException {
		final JSch jsch = getJSch(hc, FS.DETECTED);
		if (jsch == provider.getJSch()) {
			// If its the default JSch desired, let the provider
			// manage the session creation for us.
			//
			final Session session = provider.createSession(host, port, user);
			configure(hc, session);
			session.connect();
			return session;
		} else {
			// This host configuration is using a different IdentityFile,
			// one that is not available through the default JSch.
			//
			final Session session = jsch.getSession(user, host, port);
			configure(hc, session);
			session.connect(0);
			return session;
		}
	}

	@Override
	protected JSch createDefaultJSch(FS fs) throws JSchException {
		// Forcing a dummy session to be created will cause the known hosts
		// and configured private keys to be initialized. This is needed by
		// our parent class in case non-default JSch instances need to be made.
		//
		provider.createSession("127.0.0.1", 0, "eclipse"); //$NON-NLS-1$ //$NON-NLS-2$
		return provider.getJSch();
	}

	@Override
	protected void configure(final OpenSshConfig.Host hc, final Session session) {
		final EGitCredentialsProvider credentialsProvider = new EGitCredentialsProvider();
		if ((!hc.isBatchMode() || !credentialsProvider.isInteractive())) {
			session.setUserInfo(new CredentialsProviderUserInfo(session, credentialsProvider));
		} else {
			UserInfo userInfo = session.getUserInfo();

			if (!hc.isBatchMode() && userInfo == null)
				new UserInfoPrompter(session);
		}
	}

}
