/********************************************************************************
 * Copyright (c) 2009, 2012 MontaVista Software, Inc. and others.
 * 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:
 * Anna Dushistova (MontaVista)      - initial API and implementation
 * Anna Dushistova (Mentor Graphics) - [314659] moved common methods for DSF and CDI launches to this class
 * Anna Dushistova (Mentor Graphics) - changed spaceEscapify visibility
 * Anna Dushistova (MontaVista)      - [318051][remote debug] Terminating when "Remote shell" process is selected doesn't work
 ********************************************************************************/

package org.eclipse.cdt.launch.remote;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.internal.launch.remote.Activator;
import org.eclipse.cdt.internal.launch.remote.Messages;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.osgi.util.NLS;
import org.eclipse.rse.core.RSECorePlugin;
import org.eclipse.rse.core.model.IHost;
import org.eclipse.rse.core.subsystems.ISubSystem;
import org.eclipse.rse.services.IService;
import org.eclipse.rse.services.clientserver.messages.SystemOperationCancelledException;
import org.eclipse.rse.services.files.IFileService;
import org.eclipse.rse.services.shells.HostShellProcessAdapter;
import org.eclipse.rse.services.shells.IHostShell;
import org.eclipse.rse.services.shells.IShellService;
import org.eclipse.rse.subsystems.files.core.servicesubsystem.IFileServiceSubSystem;

public class RSEHelper {

	private final static String EXIT_CMD = "exit"; //$NON-NLS-1$
	private final static String CMD_DELIMITER = ";"; //$NON-NLS-1$

	
	public static IHost getRemoteConnectionByName(String remoteConnection) {
		if (remoteConnection == null)
			return null;
		IHost[] connections = RSECorePlugin.getTheSystemRegistry().getHosts();
		for (int i = 0; i < connections.length; i++)
			if (connections[i].getAliasName().equals(remoteConnection))
				return connections[i];
		return null; // TODO Connection is not found in the list--need to react
		// somehow, throw the exception?

	}

	public static IService getConnectedRemoteFileService(
			IHost currentConnection, IProgressMonitor monitor) throws Exception {
		final ISubSystem subsystem = getFileSubsystem(currentConnection);

		if (subsystem == null)
			throw new Exception(Messages.RemoteRunLaunchDelegate_4);

		try {
			subsystem.connect(monitor, false);
		} catch (CoreException e) {
			throw e;
		} catch (OperationCanceledException e) {
			throw new CoreException(Status.CANCEL_STATUS);
		}

		if (!subsystem.isConnected())
			throw new Exception(Messages.RemoteRunLaunchDelegate_5);

		return ((IFileServiceSubSystem) subsystem).getFileService();
	}

	public static IService getConnectedRemoteShellService(
			IHost currentConnection, IProgressMonitor monitor) throws Exception {
		ISubSystem subsystem = getSubSystemWithShellService(currentConnection);
		if (subsystem != null) {
			try {
				subsystem.connect(monitor, false);
			} catch (CoreException e) {
				throw e;
			} catch (OperationCanceledException e) {
				throw new CoreException(Status.CANCEL_STATUS);
			}
			if (!subsystem.isConnected())
				throw new Exception(Messages.RemoteRunLaunchDelegate_5);

			return (IShellService) subsystem.getSubSystemConfiguration()
					.getService(currentConnection).getAdapter(
							IShellService.class);
		} else {
			throw new Exception(Messages.RemoteRunLaunchDelegate_4);
		}
	}

	/**
	 * Find the first shell service associated with the host.
	 * 
	 * @param host
	 *            the connection
	 * @return shell service object, or <code>null</code> if not found.
	 */
	public static IShellService getShellService(IHost host) {
		ISubSystem ss = getSubSystemWithShellService(host);
		if (ss != null) {
			return (IShellService) ss.getSubSystemConfiguration().getService(
					host).getAdapter(IShellService.class);
		}
		return null;
	}

	/**
	 * Find the first IShellServiceSubSystem service associated with the host.
	 * 
	 * @param host
	 *            the connection
	 * @return shell service subsystem, or <code>null</code> if not found.
	 */
	public static ISubSystem getSubSystemWithShellService(IHost host) {
		if (host == null)
			return null;
		ISubSystem[] subSystems = host.getSubSystems();
		IShellService ssvc = null;
		for (int i = 0; subSystems != null && i < subSystems.length; i++) {
			IService svc = subSystems[i].getSubSystemConfiguration()
					.getService(host);
			if (svc != null) {
				ssvc = (IShellService) svc.getAdapter(IShellService.class);
				if (ssvc != null) {
					return subSystems[i];
				}
			}
		}
		return null;
	}

	public static ISubSystem getFileSubsystem(IHost host) {
		if (host == null)
			return null;
		ISubSystem[] subSystems = host.getSubSystems();
		for (int i = 0; i < subSystems.length; i++) {
			if (subSystems[i] instanceof IFileServiceSubSystem)
				return subSystems[i];
		}
		return null;
	}

	public static IHost[] getSuitableConnections() {
		ArrayList shellConnections = new ArrayList(Arrays.asList(RSECorePlugin.getTheSystemRegistry()
				.getHostsBySubSystemConfigurationCategory("shells"))); //$NON-NLS-1$
		ArrayList terminalConnections = new ArrayList(Arrays.asList(RSECorePlugin.getTheSystemRegistry()
		.getHostsBySubSystemConfigurationCategory("terminals")));//$NON-NLS-1$

		Iterator iter = terminalConnections.iterator();
		while(iter.hasNext()){
			Object terminalConnection = iter.next();
			if(!shellConnections.contains(terminalConnection)){
				shellConnections.add(terminalConnection);
			}
		}
		
		return (IHost[]) shellConnections.toArray(new IHost[shellConnections.size()]);
	}
	

	public static void remoteFileDownload(ILaunchConfiguration config,
			ILaunch launch, String localExePath, String remoteExePath,
			IProgressMonitor monitor) throws CoreException {

		boolean skipDownload = config
				.getAttribute(
						IRemoteConnectionConfigurationConstants.ATTR_SKIP_DOWNLOAD_TO_TARGET,
						false);

		if (skipDownload)
			// Nothing to do. Download is skipped.
			return;
		monitor.beginTask(Messages.RemoteRunLaunchDelegate_2, 100);
		IFileService fileService;
		try {
			fileService = (IFileService) RSEHelper
					.getConnectedRemoteFileService(
							getCurrentConnection(config),
							new SubProgressMonitor(monitor, 10));
			File file = new File(localExePath);
			Path remotePath = new Path(remoteExePath);
			fileService.upload(file, remotePath.removeLastSegments(1)
					.toString(), remotePath.lastSegment(), true, null, null,
					new SubProgressMonitor(monitor, 85));
			// Need to change the permissions to match the original file
			// permissions because of a bug in upload
			remoteShellExec(
					config,
					"", "chmod", "+x " + spaceEscapify(remotePath.toString()), new SubProgressMonitor(monitor, 5)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		} catch (SystemOperationCancelledException e) {
			abort(e.getLocalizedMessage(), null, IStatus.CANCEL);
		} catch (Exception e) {
			abort(Messages.RemoteRunLaunchDelegate_6, e,
					ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR);
		} finally {
			monitor.done();
		}
	}

	public static String spaceEscapify(String inputString) {
		if (inputString == null)
			return null;

		return inputString.replaceAll(" ", "\\\\ "); //$NON-NLS-1$ //$NON-NLS-2$
	}

	public static IHost getCurrentConnection(ILaunchConfiguration config)
			throws CoreException {
		String remoteConnection = config.getAttribute(
				IRemoteConnectionConfigurationConstants.ATTR_REMOTE_CONNECTION,
				""); //$NON-NLS-1$
		IHost connection = RSEHelper
				.getRemoteConnectionByName(remoteConnection);
		if (connection == null) {
			abort(Messages.RemoteRunLaunchDelegate_13, null,
					ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR);
		}
		return connection;
	}

	public static Process remoteShellExec(ILaunchConfiguration config,
			String prelaunchCmd, String remoteCommandPath, String arguments,
			IProgressMonitor monitor) throws CoreException {
		// The exit command is called to force the remote shell to close after
		// our command
		// is executed. This is to prevent a running process at the end of the
		// debug session.
		// See Bug 158786.
		monitor.beginTask(NLS.bind(Messages.RemoteRunLaunchDelegate_8,
				remoteCommandPath, arguments), 10);
		String realRemoteCommand = arguments == null ? spaceEscapify(remoteCommandPath)
				: spaceEscapify(remoteCommandPath) + " " + arguments; //$NON-NLS-1$

		String remoteCommand = realRemoteCommand + CMD_DELIMITER + EXIT_CMD;

		if (!prelaunchCmd.trim().equals("")) //$NON-NLS-1$
			remoteCommand = prelaunchCmd + CMD_DELIMITER + remoteCommand;

		IShellService shellService;
		Process p = null;
		try {
			shellService = (IShellService) RSEHelper
					.getConnectedRemoteShellService(
							getCurrentConnection(config),
							new SubProgressMonitor(monitor, 7));

			// This is necessary because runCommand does not actually run the
			// command right now.
			String env[] = new String[0];
			try {
				IHostShell hostShell = shellService.launchShell(
						"", env, new SubProgressMonitor(monitor, 3)); //$NON-NLS-1$
				hostShell.writeToShell(remoteCommand);
				p = new HostShellProcessAdapter(hostShell);
			} catch (Exception e) {
				if (p != null) {
					p.destroy();
				}
				abort(Messages.RemoteRunLaunchDelegate_7, e,
						ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR);
			}
		} catch (Exception e1) {
			abort(e1.getMessage(), e1,
					ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR);
		}

		monitor.done();
		return p;
	}

	public static IHostShell execCmdInRemoteShell(ILaunchConfiguration config,
			String prelaunchCmd, String remoteCommandPath, String arguments,
			IProgressMonitor monitor) throws Exception {
		// The exit command is called to force the remote shell to close after
		// our command
		// is executed. This is to prevent a running process at the end of the
		// debug session.
		// See Bug 158786.
		monitor.beginTask(NLS.bind(Messages.RemoteRunLaunchDelegate_8,
				remoteCommandPath, arguments), 10);
		String realRemoteCommand = arguments == null ? spaceEscapify(remoteCommandPath)
				: spaceEscapify(remoteCommandPath) + " " + arguments; //$NON-NLS-1$

		String remoteCommand = realRemoteCommand + CMD_DELIMITER + EXIT_CMD;

		if (!prelaunchCmd.trim().equals("")) //$NON-NLS-1$
			remoteCommand = prelaunchCmd + CMD_DELIMITER + remoteCommand;

		IShellService shellService = null;
		shellService = (IShellService) RSEHelper
				.getConnectedRemoteShellService(getCurrentConnection(config),
						new SubProgressMonitor(monitor, 7));

		// This is necessary because runCommand does not actually run the
		// command right now.
		String env[] = new String[0];
		IHostShell hostShell = null;
		if (shellService != null) {
			hostShell = shellService.launchShell(
					"", env, new SubProgressMonitor(monitor, 3)); //$NON-NLS-1$
			hostShell.writeToShell(remoteCommand);
		}
		monitor.done();
		return hostShell;
	}

	public static String getRemoteHostname(ILaunchConfiguration config)
			throws CoreException {
		IHost currentConnection = getCurrentConnection(config);
		return currentConnection.getHostName();
	}

	/**
	 * Throws a core exception with an error status object built from the given
	 * message, lower level exception, and error code.
	 * 
	 * @param message
	 *            the status message
	 * @param exception
	 *            lower level exception associated with the error, or
	 *            <code>null</code> if none
	 * @param code
	 *            error code
	 */
	public static void abort(String message, Throwable exception, int code) throws CoreException {
		IStatus status;
		if (exception != null) {
			MultiStatus multiStatus = new MultiStatus(Activator.PLUGIN_ID, code, message, exception);
			multiStatus.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, code, exception.getLocalizedMessage(), exception));
			status= multiStatus;
		} else {
			status= new Status(IStatus.ERROR, Activator.PLUGIN_ID, code, message, null);
		}
		throw new CoreException(status);
	}

	
}
