/*******************************************************************************
 * Copyright (c) 2004, 2009 QNX Software Systems 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:
 * QNX Software Systems - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.debug.mi.core; 

import java.io.File;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.MessageFormat;
import java.util.Date;
import java.util.Map;
import org.eclipse.cdt.core.IBinaryParser.IBinaryObject;
import org.eclipse.cdt.debug.core.ICDIDebugger2;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.debug.core.cdi.CDIException;
import org.eclipse.cdt.debug.core.cdi.ICDISession;
import org.eclipse.cdt.debug.core.cdi.ICDISessionConfiguration;
import org.eclipse.cdt.debug.core.cdi.model.ICDITarget;
import org.eclipse.cdt.debug.mi.core.cdi.Session;
import org.eclipse.cdt.debug.mi.core.cdi.model.Target;
import org.eclipse.cdt.debug.mi.core.command.CommandFactory;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.model.IProcess;
 
/**
 * Base class for the gdb/mi-based <code>ICDIDebugger</code> extension point 
 * implementations.
 */
abstract public class AbstractGDBCDIDebugger implements ICDIDebugger2 {

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.ICDIDebugger#createDebuggerSession(org.eclipse.debug.core.ILaunch, org.eclipse.cdt.core.IBinaryParser.IBinaryObject, org.eclipse.core.runtime.IProgressMonitor)
	 */
	@Override
	public ICDISession createDebuggerSession( ILaunch launch, IBinaryObject exe, IProgressMonitor monitor ) throws CoreException {
		return createSession( launch, exe.getPath().toFile(), monitor );
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.ICDIDebugger2#createSession(org.eclipse.debug.core.ILaunch, java.io.File, org.eclipse.core.runtime.IProgressMonitor)
	 */
	@Override
	public ICDISession createSession( ILaunch launch, File executable, IProgressMonitor monitor ) throws CoreException {
		boolean failed = false;
		if ( monitor == null ) {
			monitor = new NullProgressMonitor();
		}
		if ( monitor.isCanceled() ) {
			throw new OperationCanceledException();
		}
		boolean verboseMode = verboseMode( launch.getLaunchConfiguration() );
		boolean breakpointsFullPath = getBreakpointsWithFullNameAttribute(launch.getLaunchConfiguration() );
		Session session = createGDBSession( launch, executable, monitor );
		if ( session != null ) {
			try {
				ICDITarget[] targets = session.getTargets();
				for( int i = 0; i < targets.length; i++ ) {
					Process debugger = session.getSessionProcess( targets[i] );
					if ( debugger != null ) {
						IProcess debuggerProcess = createGDBProcess( (Target)targets[i], launch, debugger, renderDebuggerProcessLabel( launch ), null );
						launch.addProcess( debuggerProcess );
					}
					Target target = (Target)targets[i];
					target.enableVerboseMode( verboseMode );
					target.getMISession().setBreakpointsWithFullName(breakpointsFullPath);
					target.getMISession().start();
				
				}
				doStartSession( launch, session, monitor );
			}
			catch( MIException e ) {
				failed = true;
				throw newCoreException( e );
			}
			catch( CoreException e ) {
				failed = true;
				throw e;
			}
			finally {
				try {
					if ( (failed || monitor.isCanceled()) && session != null )
						session.terminate();
				}
				catch( CDIException e1 ) {
				}
			}
		}
		return session;
	}

	protected Session createGDBSession( ILaunch launch, File executable, IProgressMonitor monitor ) throws CoreException {
		Session session = null;
		IPath gdbPath = getGDBPath( launch );
		ILaunchConfiguration config = launch.getLaunchConfiguration();
		CommandFactory factory = getCommandFactory( config );
		String[] extraArgs = getExtraArguments( config );
		boolean usePty = usePty( config );
		try {
			session = MIPlugin.getDefault().createSession( getSessionType( config ), gdbPath.toOSString(), factory, executable, extraArgs, usePty, monitor );
			ICDISessionConfiguration sessionConfig = getSessionConfiguration( session );
			if ( sessionConfig != null ) {
				session.setConfiguration( sessionConfig );
			}
		}
		catch( OperationCanceledException e ) {
		}
		catch( Exception e ) {
			// Catch all wrap them up and rethrow
			if ( e instanceof CoreException ) {
				throw (CoreException)e;
			}
			throw newCoreException( e );
		}
		return session;
	}

	protected int getSessionType( ILaunchConfiguration config ) throws CoreException {
		String debugMode = config.getAttribute( ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE, ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN );
		if ( ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN.equals( debugMode ) )
			return MISession.PROGRAM;
		if ( ICDTLaunchConfigurationConstants.DEBUGGER_MODE_ATTACH.equals( debugMode ) )
			return MISession.ATTACH;
		if ( ICDTLaunchConfigurationConstants.DEBUGGER_MODE_CORE.equals( debugMode ) )
			return MISession.CORE;
		throw newCoreException( MIPlugin.getResourceString( "src.AbstractGDBCDIDebugger.0" ) + debugMode, null ); //$NON-NLS-1$
	}

	protected String[] getExtraArguments( ILaunchConfiguration config ) throws CoreException {
		return new String[0];
	}

	protected boolean usePty( ILaunchConfiguration config ) throws CoreException {
		return config.getAttribute( ICDTLaunchConfigurationConstants.ATTR_USE_TERMINAL, true );
	}

	abstract protected CommandFactory getCommandFactory( ILaunchConfiguration config ) throws CoreException;

	protected void doStartSession( ILaunch launch, Session session, IProgressMonitor monitor ) throws CoreException {		
	}

	protected String renderDebuggerProcessLabel( ILaunch launch ) {
		String format = "{0} ({1})"; //$NON-NLS-1$
		String timestamp = DateFormat.getInstance().format( new Date( System.currentTimeMillis() ) );
		String label = MIPlugin.getResourceString( "src.AbstractGDBCDIDebugger.2" ); //$NON-NLS-1$
		try {
			IPath path = getGDBPath( launch );
			label = path.toOSString();
		}
		catch( CoreException e ) {
		}
		return MessageFormat.format( format, new String[]{ label, timestamp } );
	}

   protected IPath getGDBPath(ILaunch launch) throws CoreException {
		ILaunchConfiguration config = launch.getLaunchConfiguration();
		String command = config.getAttribute(IMILaunchConfigurationConstants.ATTR_DEBUG_NAME,
				IMILaunchConfigurationConstants.DEBUGGER_DEBUG_NAME_DEFAULT);
		try {
			command = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(command, false);
		} catch (Exception e) {
			MIPlugin.log(e);
			// take value of command as it
		}
		return new Path(command);
	}

	protected ICDISessionConfiguration getSessionConfiguration( ICDISession session ) {
		return null;
	}

	/**
	 * Throws a core exception with an error status object built from 
	 * the lower level exception and error code.
	 * 
	 * @param exception lower level exception associated with the error, 
	 *                  or <code>null</code> if none
	 * @param code error code
	 */
	protected CoreException newCoreException( Throwable exception ) {
		String message = MIPlugin.getResourceString( "src.AbstractGDBCDIDebugger.1" ); //$NON-NLS-1$
		return newCoreException( message, exception );
	}

	/**
	 * 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
	 */
	protected CoreException newCoreException( String message, Throwable exception ) {
		int code = ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR;
		String ID = MIPlugin.getUniqueIdentifier();
		MultiStatus status = new MultiStatus( ID, code, message, exception );
		status.add( new Status( IStatus.ERROR, ID, code, exception == null ? new String() : exception.getLocalizedMessage(), exception ) );
		return new CoreException( status );
	}

	protected IProcess createGDBProcess( Target target, ILaunch launch, Process process, String label, Map attributes ) {
		return new GDBProcess( target, launch, process, label, attributes );
	}

	protected boolean verboseMode( ILaunchConfiguration config ) {
		boolean result = IMILaunchConfigurationConstants.DEBUGGER_VERBOSE_MODE_DEFAULT; 
		try {
			return config.getAttribute( IMILaunchConfigurationConstants.ATTR_DEBUGGER_VERBOSE_MODE, result );
		}
		catch( CoreException e ) {
			// use default
		}
		return result;
	}
	
	protected boolean getBreakpointsWithFullNameAttribute( ILaunchConfiguration config ) {
		boolean result = IMILaunchConfigurationConstants.DEBUGGER_FULLPATH_BREAKPOINTS_DEFAULT; 
		try {
			return config.getAttribute( IMILaunchConfigurationConstants.ATTR_DEBUGGER_FULLPATH_BREAKPOINTS, result );
		}
		catch( CoreException e ) {
			// use default
		}
		return result;
	}
}
