/*******************************************************************************
 * Copyright (c) 2006, 2008 IBM Corporation 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:
 * Don Yantzi (IBM) - initial contribution.
 * David Dykstal (IBM) - initial contribution.
 * Uwe Stieber (Wind River) - refactoring and cleanup.
 * Martin Oberhuber (Wind River) - [168975] Move RSE Events API to Core
 * Martin Oberhuber (Wind River) - [184095] Replace systemTypeName by IRSESystemType
 * Martin Oberhuber (Wind River) - [177523] Unify singleton getter methods
 * Martin Oberhuber (Wind River) - [186773] split ISystemRegistryUI from ISystemRegistry
 * David Dykstal (IBM) - [217556] remove service subsystem types
 * Martin Oberhuber (Wind River) - [219086] flush event queue to shield tests from each other
 * David Dykstal (IBM) - [210474] Deny save password function missing
 *******************************************************************************/
package org.eclipse.rse.tests.internal;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.Properties;

import junit.framework.Assert;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.rse.core.IRSESystemType;
import org.eclipse.rse.core.IRSEUserIdConstants;
import org.eclipse.rse.core.PasswordPersistenceManager;
import org.eclipse.rse.core.RSECorePlugin;
import org.eclipse.rse.core.events.ISystemResourceChangeEvents;
import org.eclipse.rse.core.events.SystemResourceChangeEvent;
import org.eclipse.rse.core.model.IHost;
import org.eclipse.rse.core.model.ISystemProfile;
import org.eclipse.rse.core.model.ISystemRegistry;
import org.eclipse.rse.core.model.SystemSignonInformation;
import org.eclipse.rse.core.subsystems.IRemoteServerLauncher;
import org.eclipse.rse.core.subsystems.IServerLauncherProperties;
import org.eclipse.rse.core.subsystems.ISubSystem;
import org.eclipse.rse.core.subsystems.ISubSystemConfiguration;
import org.eclipse.rse.subsystems.files.core.model.RemoteFileUtility;
import org.eclipse.rse.subsystems.files.core.servicesubsystem.FileServiceSubSystem;
import org.eclipse.rse.subsystems.shells.core.subsystems.servicesubsystem.IShellServiceSubSystem;
import org.eclipse.rse.tests.RSETestsPlugin;
import org.eclipse.rse.tests.core.connection.IRSEConnectionManager;
import org.eclipse.rse.tests.core.connection.IRSEConnectionProperties;
import org.eclipse.rse.tests.testsubsystem.interfaces.ITestSubSystem;
import org.eclipse.rse.ui.ISystemPreferencesConstants;
import org.eclipse.rse.ui.RSEUIPlugin;
import org.eclipse.swt.widgets.Display;
import org.osgi.framework.Bundle;

/**
 * RSE connection factory implementation.
 */
public class RSEConnectionManager implements IRSEConnectionManager {
	private IPath connectionDefaultsLocation = null;

	/**
	 * Constructor.
	 */
	public RSEConnectionManager() {
		// locate the connectionDefault.properties file.
		Bundle bundle = RSETestsPlugin.getDefault().getBundle();
		if (bundle != null) {
			IPath relative = new Path ("test.data/connectionDefault.properties"); //$NON-NLS-1$
			URL url = FileLocator.find(bundle, relative, null);
			if (url != null) {
				try {
					// Resolve the URL to an absolute path
					connectionDefaultsLocation = new Path(FileLocator.resolve(url).getFile());
				} catch (IOException e) { /* ignored on purpose */ }
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.rse.tests.core.connection.IRSEConnectionManager#loadConnectionProperties(org.eclipse.core.runtime.IPath, boolean)
	 */
	public IRSEConnectionProperties loadConnectionProperties(IPath path, boolean allowDefaults) {
		assert path != null;
		
		Properties defaults = null;
		if (allowDefaults && connectionDefaultsLocation != null
				&& connectionDefaultsLocation.toFile().isFile()
				&& connectionDefaultsLocation.toFile().canRead()) {
			InputStream stream = null;
			try {
				defaults = new Properties();
				stream = new FileInputStream(connectionDefaultsLocation.toFile());
				defaults.load(stream);
			} catch (IOException e) {
				// There are no defaults if anything goes wrong reading them
				defaults = null;
			} finally {
				try { if (stream != null) stream.close(); } catch (IOException e) { /* ignored */ }
			}
		}
		
		
		Properties properties = null;
		if (path.toFile().isFile() && path.toFile().canRead()) {
			InputStream stream = null; 
			try {
				stream = new FileInputStream(path.toFile());
				properties = defaults != null ? new Properties(defaults) : new Properties();
				properties.load(stream);
			} catch (IOException e) {
				// if anything goes wrong reading the properties
				// we do not return any.
				properties = null;
			} finally {
				try { if (stream != null) stream.close(); } catch (IOException e) { /* ignored */ }
			}
		}
		
		return properties != null ? new RSEConnectionProperties(properties) : (IRSEConnectionProperties)null;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.tests.core.connection.IRSEConnectionManager#loadConnectionProperties(java.util.Properties, boolean)
	 */
	public IRSEConnectionProperties loadConnectionProperties(Properties properties, boolean allowDefaults) {
		assert properties != null;
		
		Properties defaults = null;
		if (allowDefaults && connectionDefaultsLocation != null
				&& connectionDefaultsLocation.toFile().isFile()
				&& connectionDefaultsLocation.toFile().canRead()) {
			InputStream stream = null;
			try {
				defaults = new Properties();
				stream = new FileInputStream(connectionDefaultsLocation.toFile());
				defaults.load(stream);
			} catch (IOException e) {
				// There are no defaults if anything goes wrong reading them
				defaults = null;
			} finally {
				try { if (stream != null) stream.close(); } catch (IOException e) { /* ignored */ }
			}
		}
		
		// Unfortunately, we cannot use the given properties directly (as
		// we cannot associate the defaults). We must copy everything from
		// the given properties object.
		Properties resultProperties = null;
		if (defaults != null) {
			resultProperties = new Properties(defaults);
			Enumeration names = properties.propertyNames();
			while (names.hasMoreElements()) {
				String name = (String)names.nextElement();
				if (name != null && properties.getProperty(name) != null) {
					resultProperties.setProperty(name, properties.getProperty(name));
				}
			}
		} else {
			resultProperties = properties;
		}

		return resultProperties != null ? new RSEConnectionProperties(resultProperties) : (IRSEConnectionProperties)null;
	}

	/**
	 * Delete a host given its name and the name of its profile. If the host is not found then
	 * do nothing.
	 * @param profileName the name of the profile containing the host
	 * @param name the name of the host to delete
	 */
	public void removeConnection(String profileName, String name) {
		assert profileName != null && name != null;
		
		ISystemRegistry systemRegistry = RSECorePlugin.getTheSystemRegistry();
		Assert.assertNotNull("FAILED(findOrCreateConnection): RSE system registry unavailable!", systemRegistry); //$NON-NLS-1$
		
		ISystemProfile profile = systemRegistry.getSystemProfile(profileName);
		if (profile != null) {
			IHost connection = systemRegistry.getHost(profile, name);
			if (connection != null) {
				systemRegistry.deleteHost(connection);
				systemRegistry.fireEvent(new SystemResourceChangeEvent(connection, ISystemResourceChangeEvents.EVENT_DELETE, systemRegistry));
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.tests.core.connection.IRSEConnectionManager#findOrCreateConnection(org.eclipse.rse.tests.core.connection.IRSEConnectionProperties)
	 */
	public IHost findOrCreateConnection(IRSEConnectionProperties properties) {
		assert properties != null;
		
		IHost connection = null;

		ISystemRegistry systemRegistry = RSECorePlugin.getTheSystemRegistry();
		Assert.assertNotNull("FAILED(findOrCreateConnection): RSE system registry unavailable!", systemRegistry); //$NON-NLS-1$

		Exception exception = null;
		String cause = null;
		
		// First lookup and create the profile
		String profileName = properties.getProperty(IRSEConnectionProperties.ATTR_PROFILE_NAME);
		//Assert.assertNotSame("FAILED(findOrCreateConnection): Invalid system profile name!", "unknown", profileName); //$NON-NLS-1$ //$NON-NLS-2$
		ISystemProfile profile = profileName == null ? systemRegistry.getSystemProfileManager().getDefaultPrivateSystemProfile() : systemRegistry.getSystemProfile(profileName);
		if (profile == null) {
			try {
				profile = systemRegistry.createSystemProfile(profileName, true);
			} catch(Exception e) {
				exception = e;
				cause = e.getLocalizedMessage();
			}
			Assert.assertNull("FAILED(findOrCreateConnection): Failed to create system profile '" + profileName + "'! Possible cause: " + cause, exception); //$NON-NLS-1$ //$NON-NLS-2$
		}
		profileName = profile.getName();
		Assert.assertNotNull("FAILED(findOrCreateConnection): Failed to find and/or create system profile '" + profileName + "'!", profile); //$NON-NLS-1$ //$NON-NLS-2$
		
		String name = properties.getProperty(IRSEConnectionProperties.ATTR_NAME);
		Assert.assertFalse("FAILED(findOrCreateConnection): Invalid host name!", "unknown".equals(name)); //$NON-NLS-1$ //$NON-NLS-2$
		connection = systemRegistry.getHost(profile, name);
		if (connection == null) {
				String userId = properties.getProperty(IRSEConnectionProperties.ATTR_USERID);
				Assert.assertFalse("FAILED(findOrCreateConnection): Invalid user id name!", "unknown".equals(userId)); //$NON-NLS-1$ //$NON-NLS-2$
				String password = properties.getProperty(IRSEConnectionProperties.ATTR_PASSWORD);
				Assert.assertFalse("FAILED(findOrCreateConnection): Invalid user password name!", "unknown".equals(password)); //$NON-NLS-1$ //$NON-NLS-2$
				String address = properties.getProperty(IRSEConnectionProperties.ATTR_ADDRESS);
				Assert.assertFalse("FAILED(findOrCreateConnection): Invalid remote system ip address or dns name!", "unknown".equals(address)); //$NON-NLS-1$ //$NON-NLS-2$
				String systemTypeId = properties.getProperty(IRSEConnectionProperties.ATTR_SYSTEM_TYPE_ID);
				Assert.assertFalse("FAILED(findOrCreateConnection): Invalid system type!", "unknown".equals(systemTypeId)); //$NON-NLS-1$ //$NON-NLS-2$
				IRSESystemType systemType = RSECorePlugin.getTheCoreRegistry().getSystemTypeById(systemTypeId);
				String daemonPort = properties.getProperty(IRSEConnectionProperties.ATTR_DAEMON_PORT);
				Assert.assertFalse("FAILED(findOrCreateConnection): Invalid port!", "unknown".equals(daemonPort)); //$NON-NLS-1$ //$NON-NLS-2$
				
				exception = null;
				cause = null;
				
				try {
					connection = systemRegistry.createHost(profileName, systemType, name, address, null, userId, IRSEUserIdConstants.USERID_LOCATION_HOST, null);
				} catch(Exception e) {
					exception = e;
					cause = e.getLocalizedMessage();
				}
				Assert.assertNull("FAILED(findOrCreateConnection): Failed to create connection IHost object! Possible cause: " + cause, exception); //$NON-NLS-1$

				if (userId != null && password != null) {
					SystemSignonInformation info = new SystemSignonInformation(address, userId, password, systemType);
					PasswordPersistenceManager.getInstance().add(info, true, false);
				}
				
				if (daemonPort != null) {
					int daemonPortNum = Integer.parseInt(daemonPort);
					IServerLauncherProperties connProperties = connection.getConnectorServices()[0].getRemoteServerLauncherProperties();
					if (connProperties instanceof IRemoteServerLauncher) {
						IRemoteServerLauncher launcher = (IRemoteServerLauncher) connProperties;
						launcher.setDaemonPort(daemonPortNum);
					}
				}
		}
		Assert.assertNotNull("FAILED(findOrCreateConnection): Failed to find and/or create connection IHost object!", connection); //$NON-NLS-1$
		final Display display = Display.getCurrent();
		if (display!=null) {
			while(!display.isDisposed() && display.readAndDispatch()) {
				//running on main thread: wait until all async events are fired
			}
		}
		
		return connection;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.tests.core.connection.IRSEConnectionManager#getFileSubSystem(org.eclipse.rse.core.model.IHost, java.lang.String)
	 */
	public FileServiceSubSystem getFileSubSystem(IHost connection, String desiredConfigurationId) throws Exception {
		assert connection != null && desiredConfigurationId != null;
		FileServiceSubSystem subsystem = (FileServiceSubSystem)RemoteFileUtility.getFileSubSystem(connection);
		ISubSystemConfiguration config = subsystem.getSubSystemConfiguration();
		String activeId = config.getId();
		if (!activeId.equals(desiredConfigurationId)) {
			if (subsystem.isConnected()) {
				throw new RuntimeException(MessageFormat.format("The subsystem is connected as {0}. Disconnect before changing.", new Object[] { activeId })); //$NON-NLS-1$
			}

			ISystemRegistry registry = RSECorePlugin.getTheSystemRegistry();
			ISubSystemConfiguration desiredConfiguration = registry.getSubSystemConfiguration(desiredConfigurationId);
			if (subsystem.canSwitchTo(desiredConfiguration)) {
				subsystem.switchServiceFactory(desiredConfiguration);
			}
		}
		return subsystem;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.tests.core.connection.IRSEConnectionManager#getShellSubSystem(org.eclipse.rse.core.model.IHost)
	 */
	public IShellServiceSubSystem getShellSubSystem(IHost connection) throws Exception {
		assert connection != null;
		ISystemRegistry registry = RSECorePlugin.getTheSystemRegistry();
		ISubSystem[] subSystems = registry.getSubSystems(connection);
		for (int i = 0; i < subSystems.length; i++) {
			ISubSystem subSystem = subSystems[i];
			if (subSystem instanceof IShellServiceSubSystem) {
				return (IShellServiceSubSystem)subSystem;
			}
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.tests.core.connection.IRSEConnectionManager#getTestSubSystem(org.eclipse.rse.core.model.IHost)
	 */
	public ITestSubSystem getTestSubSystem(IHost connection) throws Exception {
		assert connection != null;
		ISystemRegistry registry = RSECorePlugin.getTheSystemRegistry();
		ISubSystem[] subSystems = registry.getSubSystems(connection);
		for (int i = 0; i < subSystems.length; i++) {
			ISubSystem subSystem = subSystems[i];
			if (subSystem instanceof ITestSubSystem) {
				return (ITestSubSystem)subSystem;
			}
		}
		return null;
	}
}
