/*******************************************************************************
 * Copyright (c) 2013, 2015 Red Hat, Inc.
 * 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:
 *    Red Hat Inc. - initial API and implementation
 *    Marc Khouzam (Ericsson) - Update for remote debugging support (bug 450080)
 *******************************************************************************/
package org.eclipse.cdt.debug.application;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;

import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.internal.debug.application.DebugAttachedExecutable;
import org.eclipse.cdt.internal.debug.application.DebugCoreFile;
import org.eclipse.cdt.internal.debug.application.DebugExecutable;
import org.eclipse.cdt.internal.debug.application.DebugRemoteExecutable;
import org.eclipse.cdt.internal.debug.application.JobContainer;
import org.eclipse.cdt.utils.spawner.ProcessFactory;
import org.eclipse.core.resources.ResourcesPlugin;
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.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.application.ActionBarAdvisor;
import org.eclipse.ui.application.IActionBarConfigurer;
import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
import org.eclipse.ui.application.WorkbenchWindowAdvisor;

public class ApplicationWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor {

	private static final String STANDALONE_QUALIFIER = "org.eclipse.cdt.debug.application"; //$NON-NLS-1$
	private static final String LAST_LAUNCH = "lastLaunch"; //$NON-NLS-1$
	private ILaunchConfiguration config;

	private class StartupException extends FileNotFoundException {
		private static final long serialVersionUID = 1L;

		public StartupException(String s) {
			super();
		}
	}
	
    public ApplicationWorkbenchWindowAdvisor(IWorkbenchWindowConfigurer configurer) {
        super(configurer);
    }

    @Override
	public ActionBarAdvisor createActionBarAdvisor(IActionBarConfigurer configurer) {
        return new ApplicationActionBarAdvisor(configurer);
    }
    
    @Override
	public void preWindowOpen() {
        IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
//        configurer.setInitialSize(new Point(400, 300));
		configurer.setShowCoolBar(true);
		configurer.setShowStatusLine(true);
		configurer.setShowMenuBar(true);
		configurer.setShowProgressIndicator(true);
		configurer.setTitle(Messages.Debugger_Title);
	}

	@Override
	public void postWindowCreate() {
		super.postWindowCreate();
		try {
			IRunnableWithProgress op = new PostWindowCreateRunnable();
			new ProgressMonitorDialog(getWindowConfigurer().getWindow().getShell()).run(true, true, op);
		} catch (InvocationTargetException e) {
			// handle exception
		} catch (InterruptedException e) {
			// handle cancelation
		}
	}

	// Private method to search for executable names on PATH
	private String findExecutable(String input) {
		String result = input;
		
		Path x = new Path(input);
		try {
			if (!x.isAbsolute() && x.segmentCount() == 1) {
				String command = "which " + input; //$NON-NLS-1$
				Process p = null;
				InputStream in = null;
				try {
					p = ProcessFactory.getFactory().exec(command);
					in = p.getInputStream();
					InputStreamReader reader = new InputStreamReader(in);
					BufferedReader br = new BufferedReader(reader);
					String line = br.readLine();
					if (line != null)
						result = line;
				} finally {
					if (in != null)
						in.close();
					if (p != null)
						p.destroy();
				}
			}
		} catch (IOException e) {
			// do nothing
		}
		return result;
	}
	
	public class PostWindowCreateRunnable implements IRunnableWithProgress {

		@Override
		public void run(IProgressMonitor monitor)
				throws InvocationTargetException, InterruptedException {
			monitor.beginTask(Messages.InitializingDebugger, 10);
			boolean attachExecutable = false;
			String executable = null;
			String corefile = null;
			String buildLog = null;
			String arguments = null;
			String remoteAddress = null;
			String remotePort = null;
			String pid = null;
			String[] args = Platform.getCommandLineArgs();

			try {
				for (int i = 0; i < args.length; ++i) {
					if ("-application".equals(args[i])) //$NON-NLS-1$
						i++; // ignore the application specifier
					else if ("-product".equals(args[i])) //$NON-NLS-1$
						i++; // ignore the product specifier
					else if ("-b".equals(args[i])) { //$NON-NLS-1$
						++i;
						if (i < args.length)
							buildLog = args[i];
					}
					else if ("-a".equals(args[i])) { //$NON-NLS-1$
						attachExecutable = true;
						// Make sure 'executable' is still null in case we are dealing with a remote
						// session that is also an attach, as the -r flag could have been set first
						executable = null;
						
						// Check for optional pid
						if (i + 1 < args.length) {
							if (!args[i+1].startsWith("-")) { //$NON-NLS-1$
								++i;
								pid = args[i];
							}
						}
					}
					else if ("-c".equals(args[i])) { //$NON-NLS-1$
						++i;
						corefile = ""; //$NON-NLS-1$
						executable = ""; //$NON-NLS-1$
						if (i < args.length)
							corefile = args[i];
					}
					else if ("-r".equals(args[i])) { //$NON-NLS-1$
						++i;
						remoteAddress = ""; //$NON-NLS-1$
						if (!attachExecutable) executable = ""; //$NON-NLS-1$
						if (i < args.length) {
							String[] params = args[i].split(":"); //$NON-NLS-1$
							if (params.length == 2) {
								remoteAddress = params[0];
								remotePort = params[1];
							}
						}
					}
					else if ("-e".equals(args[i])) { //$NON-NLS-1$
						++i;
						if (i < args.length)
							executable = findExecutable(args[i]);
						++i;
						StringBuffer argBuffer = new StringBuffer();
						// Remaining values are arguments to the executable
						if (i < args.length)
							argBuffer.append(args[i++]);
						while (i < args.length) {
							argBuffer.append(" "); //$NON-NLS-1$
							argBuffer.append(args[i++]);
						}
						arguments = argBuffer.toString();
					}
				}
				// Verify any core file or executable path is valid.
				if (corefile != null) {
					File executableFile = null;
					if (executable != null) {
						executableFile = new File(executable);
						executable = executableFile.getCanonicalPath();
					}
					File coreFile = new File(corefile);
					corefile = coreFile.getCanonicalPath();
					if (executableFile == null || !executableFile.exists() || !coreFile.exists()) {
						final CoreFileInfo info = new CoreFileInfo("", "", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ $NON-NLS-2$ $NON-NLS-3$
						final IStatus errorStatus = new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, 
								Messages.GdbDebugNewExecutableCommand_Binary_file_does_not_exist, null);
						final String executablePath = executable;
						final String coreFilePath = corefile;

						Display.getDefault().syncExec(new Runnable() {

							@Override
							public void run() {

								CoreFileDialog dialog = new CoreFileDialog(getWindowConfigurer().getWindow().getShell(),
										0, executablePath, coreFilePath);
								dialog.setBlockOnOpen(true);
								if (dialog.open() == IDialogConstants.OK_ID) {
									CoreFileInfo info2 = dialog.getCoreFileInfo();
									info.setHostPath(info2.getHostPath());
									info.setCoreFilePath(info2.getCoreFilePath());
								} else {
									ErrorDialog.openError(null,
											Messages.DebuggerInitializingProblem, null, errorStatus,
											IStatus.ERROR | IStatus.WARNING);
								}
							}
						});
						// Check and see if we failed above and if so, quit
						if (info.getHostPath().equals("")) { //$NON-NLS-1$
							monitor.done();
							// throw internal exception which will be caught below
							throw new StartupException(errorStatus.getMessage());
						}
						executable = info.getHostPath();
						corefile = info.getCoreFilePath();
					}
				} else if (remoteAddress != null) {
					// Verify what we can about the port, address and executable.
					File executableFile = null;
					if (executable != null) {
						executableFile = new File(executable);
						executable = executableFile.getCanonicalPath();
					}
					
					Integer port = null;
					try {
						port = Integer.parseInt(remotePort);
					} catch (NumberFormatException e) {
						port = null;
					}
					
					if ((!attachExecutable && (executableFile == null || !executableFile.exists())) ||
							remoteAddress.length() == 0 || port == null) {
						final RemoteExecutableInfo[] info = new RemoteExecutableInfo[1];
						final IStatus errorStatus = new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, 
								Messages.GdbDebugNewExecutableCommand_Binary_file_does_not_exist, null);
						final String executablePath = executable;
						final String addressStr = remoteAddress;
						final String portStr = remotePort;
						final String buildLogPath = buildLog;
						final boolean attach = attachExecutable;
						
						Display.getDefault().syncExec(new Runnable() {

							@Override
							public void run() {

								RemoteExecutableDialog dialog = new RemoteExecutableDialog(getWindowConfigurer().getWindow().getShell(),
										executablePath, buildLogPath, addressStr, portStr, attach);
								dialog.setBlockOnOpen(true);
								if (dialog.open() == IDialogConstants.OK_ID) {
									info[0] = dialog.getExecutableInfo();
								} else {
									info[0] = null;
									ErrorDialog.openError(null,
											Messages.DebuggerInitializingProblem, null, errorStatus,
											IStatus.ERROR | IStatus.WARNING);
								}
							}
						});
						// Check and see if we failed above and if so, quit
						if (info[0] == null) {
							monitor.done();
							// throw internal exception which will be caught below
							throw new StartupException(errorStatus.getMessage());
						}
						executable = info[0].getHostPath();
						buildLog = info[0].getBuildLog();
						remoteAddress = info[0].getAddress();
						remotePort = info[0].getPort();
						attachExecutable = info[0].isAttach();
					}
				} else if (executable != null) {
					File executableFile = new File(executable);
					executable = executableFile.getCanonicalPath();
					File buildLogFile = null;
					if (buildLog != null) {
						buildLogFile = new File(buildLog);
						buildLog = buildLogFile.getCanonicalPath();
					}
					if (!executableFile.exists() || (buildLogFile != null && !buildLogFile.exists())) {
						final NewExecutableInfo info = new NewExecutableInfo("", "", "", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ $NON-NLS-2$ $NON-NLS-3$
						final IStatus errorStatus = new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, 
								Messages.GdbDebugNewExecutableCommand_Binary_file_does_not_exist, null);
						final String executablePath = executable;
						final String executableArgs = arguments;
						final String buildLogPath = buildLog;

						Display.getDefault().syncExec(new Runnable() {

							@Override
							public void run() {

								NewExecutableDialog dialog = new NewExecutableDialog(getWindowConfigurer().getWindow().getShell(),
										0, executablePath, buildLogPath, executableArgs);
								dialog.setBlockOnOpen(true);
								if (dialog.open() == IDialogConstants.OK_ID) {
									NewExecutableInfo info2 = dialog.getExecutableInfo();
									info.setHostPath(info2.getHostPath());
									info.setArguments(info2.getArguments());
								} else {
									ErrorDialog.openError(null,
											Messages.DebuggerInitializingProblem, null, errorStatus,
											IStatus.ERROR | IStatus.WARNING);
								}
							}
						});
						// Check and see if we failed above and if so, quit
						if (info.getHostPath().equals("")) { //$NON-NLS-1$
							monitor.done();
							// throw internal exception which will be caught below
							throw new StartupException(errorStatus.getMessage());
						}
						executable = info.getHostPath();
						arguments = info.getArguments();
					}
				}
				monitor.worked(1);
				if (remoteAddress != null && remoteAddress.length() > 0 && 
						remotePort != null && remotePort.length() > 0) {
					config = DebugRemoteExecutable.createLaunchConfig(monitor, buildLog, executable, remoteAddress, remotePort, attachExecutable);
				} else if (attachExecutable) {
					config = DebugAttachedExecutable.createLaunchConfig(monitor, buildLog, pid);
				} else if (corefile != null && corefile.length() > 0) {
					config = DebugCoreFile.createLaunchConfig(monitor, buildLog, executable, corefile);
				} else if (executable != null && executable.length() > 0) {
					config = DebugExecutable.importAndCreateLaunchConfig(monitor, executable, buildLog, arguments, true);
				} else {
					// No executable specified, look for last launch
					// and offer that to the end-user.
					monitor.subTask(Messages.RestorePreviousLaunch);
					String memento = ResourcesPlugin.getWorkspace().getRoot().getPersistentProperty(new QualifiedName(STANDALONE_QUALIFIER, LAST_LAUNCH));
					if (memento != null)
						config = DebugExecutable.getLaunchManager().getLaunchConfiguration(memento);
					String oldExecutable = ""; //$NON-NLS-1$
					String oldArguments = ""; //$NON-NLS-1$
					String oldBuildLog = ""; //$NON-NLS-1$
					if (config != null) {
						oldExecutable = config.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, ""); //$NON-NLS-1$
						oldArguments = config.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, ""); //$NON-NLS-1$
						oldBuildLog = config.getAttribute(ICDTStandaloneDebugLaunchConstants.BUILD_LOG_LOCATION, ""); //$NON-NLS-1$
					}
					final NewExecutableInfo info = new NewExecutableInfo("", "", "", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ $NON-NLS-2$ $NON-NLS-3$
					final IStatus errorStatus = new Status(IStatus.WARNING, Activator.PLUGIN_ID, 0, 
							Messages.GdbDebugNewExecutableCommand_Binary_file_does_not_exist, null);
					final String executablePath = oldExecutable;
					final String executableArgs = oldArguments;
					final String buildLogPath = oldBuildLog;
					// Bring up New Executable dialog with values from
					// the last launch.
					Display.getDefault().syncExec(new Runnable() {

						@Override
						public void run() {

							NewExecutableDialog dialog = new NewExecutableDialog(getWindowConfigurer().getWindow().getShell(),
									0, executablePath, buildLogPath, executableArgs);
							dialog.setBlockOnOpen(true);
							if (dialog.open() == IDialogConstants.OK_ID) {
								NewExecutableInfo info2 = dialog.getExecutableInfo();
								info.setHostPath(info2.getHostPath());
								info.setArguments(info2.getArguments());
								info.setBuildLog(info2.getBuildLog());
							} else {
								ErrorDialog.openError(null,
										Messages.DebuggerInitializingProblem, null, errorStatus,
										IStatus.ERROR | IStatus.WARNING);
							}
						}
					});
					// Check and see if we failed above and if so, quit
					if (info.getHostPath().equals("")) { //$NON-NLS-1$
						monitor.done();
						// throw internal exception which will be caught below
						throw new StartupException(errorStatus.getMessage());
					}
					executable = info.getHostPath();
					arguments = info.getArguments();
					buildLog = info.getBuildLog();
					// If no last configuration or user has changed
					// the executable, we need to create a new configuration
					// and remove artifacts from the old one.
					if (config == null || !executable.equals(oldExecutable))
						config = DebugExecutable.importAndCreateLaunchConfig(monitor, executable, buildLog, arguments, true);
					ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
					wc.setAttribute(ICDTStandaloneDebugLaunchConstants.BUILD_LOG_LOCATION,
							buildLog);
					if (arguments != null)
						wc.setAttribute(
								ICDTLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,
								arguments);
					config = wc.doSave();

					monitor.worked(7);
				}
				if (config != null) {
					final JobContainer LaunchJobs = new JobContainer();
					Job.getJobManager().addJobChangeListener(new JobChangeAdapter() {

						@Override
						public void scheduled(IJobChangeEvent event) {
							Job job = event.getJob();
							if (job.getName().contains(config.getName()))
								LaunchJobs.setLaunchJob(job);
						}

						@Override
						public void done(IJobChangeEvent event) {
						}
					});
					monitor.subTask(Messages.LaunchingConfig);
					Display.getDefault().syncExec(new Runnable() {

						@Override
						public void run() {
							DebugUITools.launch(config, ILaunchManager.DEBUG_MODE);
						}
					});
					if (LaunchJobs.getLaunchJob() != null) {
						try {
							LaunchJobs.getLaunchJob().join();
						} catch (InterruptedException e) {
							IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, 
									Messages.LaunchInterruptedError, e);
							ResourcesPlugin.getPlugin().getLog().log(status);
						}
					}
				}
			} catch (InterruptedException e) {
				throw e; // rethrow exception
			} catch (CoreException e) {
				e.printStackTrace();
			} catch (StartupException e) {
				// do nothing..just quit
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				monitor.done();
			}
		}
	
	}

	@Override
	public void postWindowClose() {
		if (ResourcesPlugin.getWorkspace() != null)
			disconnectFromWorkspace();
		super.postWindowClose();
	}

	private void disconnectFromWorkspace() {

		// save the workspace
		final MultiStatus status = new MultiStatus(
				Activator.PLUGIN_ID, 1,
				Messages.ProblemSavingWorkbench, null);
		try {
			final ProgressMonitorDialog p = new ProgressMonitorDialog(
					null);
			IRunnableWithProgress runnable = new IRunnableWithProgress() {
				@Override
				public void  run(IProgressMonitor monitor) {
					try {
						status.merge(ResourcesPlugin
								.getWorkspace().save(true, monitor));
					} catch (CoreException e) {
						status.merge(e.getStatus());
					}
				}
			};
			p.run(true, false, runnable);
		} catch (InvocationTargetException e) {
			status.merge(new Status(IStatus.ERROR,
					Activator.PLUGIN_ID, 1,
					Messages.InternalError, 
					e.getTargetException()));
		} catch (InterruptedException e) {
			status.merge(new Status(IStatus.ERROR,
					Activator.PLUGIN_ID, 1,
					Messages.InternalError, e));
		}

		ErrorDialog.openError(null,
				Messages.ProblemsSavingWorkspace, null, status,
				IStatus.ERROR | IStatus.WARNING);

		if (!status.isOK()) {
			ResourcesPlugin.getPlugin().getLog().log(status);
		}

	}	

}
