/********************************************************************************
 * Copyright (c) 2002, 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
 *
 * Initial Contributors:
 * The following IBM employees contributed to the Remote System Explorer
 * component that contains this file: David McKnight, Kushal Munir,
 * Michael Berger, David Dykstal, Phil Coulthard, Don Yantzi, Eric Simpson,
 * Emily Bruner, Mazen Faraj, Adrian Storisteanu, Li Ding, and Kent Hawley.
 *
 * Contributors:
 * David Dykstal (IBM) - 168977: refactoring IConnectorService and ServerLauncher hierarchies
 * Martin Oberhuber (Wind River) - [175262] IHost.getSystemType() should return IRSESystemType
 * David Dykstal (IBM) - 142806: refactoring persistence framework
 * Martin Oberhuber (Wind River) - [185750] Remove IConnectorService.getHostType()
 * David Dykstal (IBM) - [189483] fix spelling in initialize/uninitialize method signatures
 * David Dykstal (IBM) - [210474] Deny save password function missing
 ********************************************************************************/
package org.eclipse.rse.core.subsystems;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.rse.core.IRSESystemType;
import org.eclipse.rse.core.RSEPreferencesManager;
import org.eclipse.rse.core.model.IHost;
import org.eclipse.rse.core.model.IRSEPersistableContainer;
import org.eclipse.rse.core.model.RSEModelObject;

/**
 * This is a base class to make it easier to create connector services.
 * <p>
 * An {@link org.eclipse.rse.core.subsystems.IConnectorService} object
 * is returned from a subsystem object via getConnectorService(), and
 * it is used to maintain the connection to a particular set of subsystems.
 * <p>
 * This class implements the protocol for much of the
 * standard bookkeeping for connector services including
 * server launchers (if none are required), event handling,
 * hosts, ports, addresses, descriptions, and registered subsystems.
 * Subclasses must concern themselves with actually authenticating and connecting.
 */
public abstract class AbstractConnectorService extends RSEModelObject implements IConnectorService {

	private Vector commListeners = new Vector(5);
	private ISubSystem _primarySubSystem = null;
	private List _registeredSubSystems = new ArrayList();
	private IHost _host;
	private String _description;
	private String _name;
	private int _port;
	private boolean _usingSSL;

	/**
	 * Construct a new connector service. This should be called during the construction
	 * of any subclasses.
	 * @param name The name of the connector service.
	 * @param description A description of the connector service.
	 * @param host The host associated with this connector service. A host may have multiple
	 * connector services.
	 * @param port The port associated with this connector service if this connector service
	 * is IP based. If not IP based this can be used for some other purpose.
	 */
	public AbstractConnectorService(String name, String description, IHost host, int port) {
		_name = name;
		_description = description;
		_host = host;
		_port = port;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#isServerLaunchTypeEnabled(org.eclipse.rse.core.subsystems.ISubSystem, org.eclipse.rse.core.subsystems.ServerLaunchType)
	 */
	public final boolean isServerLaunchTypeEnabled(ISubSystem subsystem, ServerLaunchType serverLaunchType) {
		IServerLauncher sl = getRemoteServerLauncher();
		if (sl instanceof RemoteServerLauncher) {
			RemoteServerLauncher isl = (RemoteServerLauncher) sl;
			return isl.isEnabledServerLaunchType(serverLaunchType);
		} else
			return subsystem.getSubSystemConfiguration().supportsServerLaunchType(serverLaunchType);
	}

	/**
	 * @return null, may be overriden
	 * @see IConnectorService#getRemoteServerLauncher()
	 */
	public IServerLauncher getRemoteServerLauncher() {
		return null;
	}

	/**
	 * @return false, may be overridden
	 * @see IConnectorService#supportsRemoteServerLaunching()
	 */
	public boolean supportsRemoteServerLaunching() {
		return false;
	}

	/**
	 * @return false, may be overridden
	 * @see IConnectorService#supportsServerLaunchProperties()
	 */
	public boolean supportsServerLaunchProperties() {
		return false;
	}

	/**
	 * @return null, may be overridden
	 * @see IConnectorService#getRemoteServerLauncherProperties()
	 */
	public IServerLauncherProperties getRemoteServerLauncherProperties() {
		return null;
	}

	/**
	 * Do nothing, may be overridden
	 * @param newRemoteServerLauncher the server launcher properties
	 * @see IConnectorService#setRemoteServerLauncherProperties(IServerLauncherProperties)
	 */
	public void setRemoteServerLauncherProperties(IServerLauncherProperties newRemoteServerLauncher) {
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#hasRemoteServerLauncherProperties()
	 */
	public final boolean hasRemoteServerLauncherProperties() {
		return getRemoteServerLauncherProperties() != null;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#addCommunicationsListener(org.eclipse.rse.core.subsystems.ICommunicationsListener)
	 */
	public final void addCommunicationsListener(ICommunicationsListener listener) {
		if (!commListeners.contains(listener)) {
			commListeners.add(listener);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#removeCommunicationsListener(org.eclipse.rse.core.subsystems.ICommunicationsListener)
	 */
	public final void removeCommunicationsListener(ICommunicationsListener listener) {
		commListeners.remove(listener);
	}

	/**
	 * Fires the communication event mentioned in the eventType.
	 * @param eventType the communications event to fire.
	 */
	final protected void fireCommunicationsEvent(int eventType) {
		CommunicationsEvent e = new CommunicationsEvent(this, eventType);
		Object[] items = commListeners.toArray();
		for (int loop=0; loop < items.length; loop++) {
			((ICommunicationsListener) items[loop]).communicationsStateChange(e);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#getHost()
	 */
	public final IHost getHost() {
		return _host;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#setHost(org.eclipse.rse.core.model.IHost)
	 */
	public final void setHost(IHost host) {
		_host = host;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.model.RSEModelObject#getDescription()
	 */
	public final String getDescription() {
		return _description;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.model.IRSEModelObject#getName()
	 */
	public final String getName() {
		return _name;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#setPort(int)
	 */
	public final void setPort(int port) {
		if (port != _port)
		{
			_port = port;
			setDirty(true);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#getPort()
	 */
	public final int getPort() {
		return _port;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#getPrimarySubSystem()
	 */
	public final ISubSystem getPrimarySubSystem() {
		if (_primarySubSystem == null)
		{
			if (_registeredSubSystems.size() == 0)
			{

			}
			else
			{
				ISubSystem ss = (ISubSystem)_registeredSubSystems.get(0);
				_primarySubSystem = ss.getPrimarySubSystem();
			}
		}
		return _primarySubSystem;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#registerSubSystem(org.eclipse.rse.core.subsystems.ISubSystem)
	 */
	public final void registerSubSystem(ISubSystem ss) {
		if (!_registeredSubSystems.contains(ss))
		{
			_registeredSubSystems.add(ss);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#deregisterSubSystem(org.eclipse.rse.core.subsystems.ISubSystem)
	 */
	public final void deregisterSubSystem(ISubSystem ss) {
		_registeredSubSystems.remove(ss);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.model.IRSEPersistableContainer#commit()
	 */
	public final boolean commit() {
		return getHost().commit();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.model.IRSEPersistableContainer#getPersistableParent()
	 */
	public final IRSEPersistableContainer getPersistableParent() {
		return _host;
	}

	public IRSEPersistableContainer[] getPersistableChildren() {
		List children = new ArrayList(20);
		IServerLauncherProperties launcherProperties = getRemoteServerLauncherProperties();
		if (launcherProperties != null) {
			children.add(getRemoteServerLauncherProperties());
		}
		children.addAll(_registeredSubSystems);
		children.addAll(Arrays.asList(getPropertySets()));
		IRSEPersistableContainer[] result = new IRSEPersistableContainer[children.size()];
		children.toArray(result);
		return result;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#getHostName()
	 */
	public final String getHostName() {
		return getHost().getHostName();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#getVersionReleaseModification()
	 */
	public String getVersionReleaseModification() {
		return ""; //$NON-NLS-1$
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#getSubSystems()
	 */
	public final ISubSystem[] getSubSystems() {
		return (ISubSystem[])_registeredSubSystems.toArray(new ISubSystem[_registeredSubSystems.size()]);
	}

	/**
	 * Initialize any subsystems just after connecting to the host.
	 * @param monitor a progress monitor to report progress of initialization.
	 */
	protected final void initializeSubSystems(IProgressMonitor monitor) {
		for (int i = 0; i < _registeredSubSystems.size(); i++)
		{
			ISubSystem ss = (ISubSystem)_registeredSubSystems.get(i);
			ss.initializeSubSystem(monitor);
		}
	}

	/**
	 * Uninitialize any subsystem just after disconnecting from the host.
	 * @param monitor a progress monitor used to track uninitialization progress.
	 */
	protected final void uninitializeSubSystems(IProgressMonitor monitor) {
		for (int i = 0; i < _registeredSubSystems.size(); i++)
		{
			ISubSystem ss = (ISubSystem)_registeredSubSystems.get(i);
			ss.uninitializeSubSystem(monitor);
		}
	}

	/**
	 * Send the event to notify listeners of a disconnection.
	 * Used by the framework and should
	 * usually not be invoked by concrete subclasses.
	 */
	protected final void notifyDisconnection() {
		 // Fire comm event to signal state changed
		if (!isConnected()) fireCommunicationsEvent(CommunicationsEvent.AFTER_DISCONNECT);
	}

	/**
	 * Send the event to notify listeners of a connection.
	 * Used by the framework and should
	 * usually not be invoked by concrete subclasses.
	 */
	protected final void notifyConnection() {
		if (isConnected()) fireCommunicationsEvent(CommunicationsEvent.AFTER_CONNECT);
	}

	/**
	 * Send the event to notify listeners of a connection establishment error.
	 * Used by the framework and should
	 * usually not be invoked by concrete subclasses.
	 */
	protected final void notifyError() {
		fireCommunicationsEvent(CommunicationsEvent.CONNECTION_ERROR);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#isUsingSSL()
	 */
	public final boolean isUsingSSL() {
		return _usingSSL;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#setIsUsingSSL(boolean)
	 */
	public final void setIsUsingSSL(boolean flag) {
		if (_usingSSL != flag)
		{
			_usingSSL = flag;
			setDirty(true);
		}
	}

	/**
	 * Returns the temp directory of the remote system for the current user,
	 * if available. This implementation returns the empty string.
	 * @return an empty string
	 * @see IConnectorService#getTempDirectory()
	 */
	public String getTempDirectory() {
		return ""; //$NON-NLS-1$
	}

	/**
	 * Returns the home directory of the remote system for the current user,
	 * if available. This implementation returns the empty string.
	 * @return an empty string
	 * @see IConnectorService#getHomeDirectory()
	 */
	public String getHomeDirectory() {
		return ""; //$NON-NLS-1$
	}

	/**
	 * Reset the connector service state if a connector service is redefined
	 * or disconnected.
	 * Each subsystem needs to be informed so it can clear out any expansions.
	 * This implementation does nothing.
	 * Implementations should override and call {@link #reset()}
	 * if there is internal state to reset.
	 * @see IConnectorService#reset()
	 */
	public void reset() {
	}

	/**
	 * This implementation returns the connector service's port property.
	 * Override if appropriate.
	 * <br> This is called by the default implementation of
	 * {@link #connect(IProgressMonitor)},
	 * if #supportsServerLaunchProperties() is true.
	 * @return the port used for connecting to the target
	 */
	protected int getConnectPort() {
		return getPort();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.rse.core.subsystems.IConnectorService#connect(org.eclipse.core.runtime.IProgressMonitor)
	 */
	public final void connect(IProgressMonitor monitor) throws Exception {
		preConnect();
		internalConnect(monitor);
		initializeSubSystems(monitor);
		postConnect();
	}

	/**
	 * Disconnects from the target system.
	 * Calls {@link #internalDisconnect(IProgressMonitor)}
	 * and {@link #postDisconnect()}
	 * @throws Exception if the disconnect fails
	 */
	public final void disconnect(IProgressMonitor monitor) throws Exception {
		preDisconnect();
		internalDisconnect(monitor);
		uninitializeSubSystems(monitor);
		postDisconnect();
	}

	/**
	 * Performs the actual connection to the target system.
	 * @param monitor for cancellation and progress reporting
	 * @throws Exception if connection does not succeed
	 */
	protected abstract void internalConnect(IProgressMonitor monitor) throws Exception;

	/**
	 * Performs the actual disconnection from the target system.
	 * @param monitor for cancellation and progress reporting
	 * @throws Exception if disconnection does not succeed
	 */
	protected abstract void internalDisconnect(IProgressMonitor monitor) throws Exception;

	/**
	 * Performs any cleanup required after disconnecting.
	 * This implementation does nothing.
	 * May be overridden.
	 * If overridden, invoke via super.
	 */
	protected void postDisconnect() {
	}

	/**
	 * Performs any tasks required immediately prior to disconnecting.
	 * This implementation does nothing.
	 * May be overridden.
	 * If overridden, invoke via super.
	 */
	protected void preDisconnect() {
	}

	/**
	 * Performs any tasks required immediately prior to connecting.
	 * This implementation does nothing.
	 * May be overridden.
	 * If overridden, invoke via super.
	 */
	protected void preConnect() {
	}

	/**
	 * Performs any tasks required immediately after connecting.
	 * This implementation does nothing.
	 * May be overridden.
	 * If overridden, invoke via super.
	 */
	protected void postConnect() {
	}

	/**
	 * {@inheritDoc}
	 * @see IConnectorService#setDenyPasswordSave(boolean)
	 * @since org.eclipse.rse.core 3.0
	 */
	public final int setDenyPasswordSave(boolean deny) {
		IHost host = getHost();
		String hostAddress = host.getHostName();
		IRSESystemType systemType = host.getSystemType();
		int result = RSEPreferencesManager.setDenyPasswordSave(systemType, hostAddress, deny);
		return result;
	}

	/**
	 * {@inheritDoc}
	 * @see IConnectorService#getDenyPasswordSave()
	 * @since org.eclipse.rse.core 3.0
	 */
	public final boolean getDenyPasswordSave() {
		IHost host = getHost();
		String hostAddress = host.getHostName();
		IRSESystemType systemType = host.getSystemType();
		boolean result = RSEPreferencesManager.getDenyPasswordSave(systemType, hostAddress);
		return result;
	}



}