/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package flash.tools.debugger.concrete;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import flash.tools.debugger.AIRLaunchInfo;
import flash.tools.debugger.Frame;
import flash.tools.debugger.IDebuggerCallbacks;
import flash.tools.debugger.ILauncher;
import flash.tools.debugger.InProgressException;
import flash.tools.debugger.Isolate;
import flash.tools.debugger.IsolateController;
import flash.tools.debugger.IsolateSession;
import flash.tools.debugger.Location;
import flash.tools.debugger.NoResponseException;
import flash.tools.debugger.NotConnectedException;
import flash.tools.debugger.NotSupportedException;
import flash.tools.debugger.NotSuspendedException;
import flash.tools.debugger.PlayerDebugException;
import flash.tools.debugger.Session;
import flash.tools.debugger.SessionManager;
import flash.tools.debugger.SourceFile;
import flash.tools.debugger.SourceLocator;
import flash.tools.debugger.SuspendedException;
import flash.tools.debugger.SwfInfo;
import flash.tools.debugger.Value;
import flash.tools.debugger.ValueAttribute;
import flash.tools.debugger.Variable;
import flash.tools.debugger.VariableAttribute;
import flash.tools.debugger.VariableType;
import flash.tools.debugger.VersionException;
import flash.tools.debugger.Watch;
import flash.tools.debugger.concrete.DProtocol.ListenerIndex;
import flash.tools.debugger.events.DebugEvent;
import flash.tools.debugger.events.ExceptionFault;
import flash.tools.debugger.events.FaultEvent;
import flash.tools.debugger.expression.ECMA;
import flash.tools.debugger.expression.PlayerFaultException;
import flash.util.Trace;


public class PlayerSession implements Session, DProtocolNotifierIF, Runnable, IsolateController
{
	public static final int MAX_STACK_DEPTH = 256;
	public static final long MAX_TERMINATE_WAIT_MILLIS = 10000;

	private Socket				m_socket;
	private DProtocol			m_protocol;
	private DManager			m_manager;
	private IDebuggerCallbacks	m_debuggerCallbacks;
	private Process				m_process;
	private Map<String, Object> m_prefs; // WARNING -- accessed from multiple threads
	private static final String	s_newline = System.getProperty("line.separator"); //$NON-NLS-1$

	private volatile boolean m_isConnected; // WARNING -- accessed from multiple threads
	private volatile boolean m_isHalted; // WARNING -- accessed from multiple threads
	private volatile boolean m_incoming; // WARNING -- accessed from multiple threads
	private volatile boolean m_lastResponse;  // whether there was a reponse from the last message to the Player
	private volatile HashMap<Integer, PlayerSessionIsolateStatus> m_isolateStatus = new HashMap<Integer, PlayerSessionIsolateStatus>();
	
	private int				m_watchTransactionTag;
	private Boolean			m_playerCanCallFunctions;
	private Boolean			m_playerSupportsWatchpoints;
	private Boolean			m_playerCanBreakOnAllExceptions;
	private Boolean			m_playerSupportsConcurrency;
	private Boolean			m_playerSupportsWideLine;
	
	private ILauncher launcher;

	/**
	 * The URL that was launched, or <code>null</code> if not known.  Note:
	 * This is NOT the value returned by getURI().  getURI() returns the
	 * URL that came from the Player, and is therefore probably the URI of
	 * the SWF; but m_launchedUrl contains the URL that we tried to launch,
	 * which might be an HTML wrapper, e.g. http://localhost/myapp.html
	 */
	private String				m_launchUrl;

	private AIRLaunchInfo	m_airLaunchInfo; // null if this is not an AIR app

	static volatile boolean	m_debugMsgOn;		// debug ONLY; turned on with "set $debug_messages = 1"
	volatile int			m_debugMsgSize;		// debug ONLY; controlled with "set $debug_message_size = NNN"
	static volatile boolean	m_debugMsgFileOn;	// debug ONLY for file dump; turned on with "set $debug_message_file = 1"
	volatile int			m_debugMsgFileSize;	// debug ONLY for file dump; controlled with "set $debug_message_file_size = NNN"

	//FIXME: Make this concurrency aware
	/**
	 * A simple cache of previous "is" and "instanceof" queries, in order to
	 * avoid having to send redundant messages to the player.
	 */
	private Map<String, Boolean> m_evalIsAndInstanceofCache = new HashMap<String, Boolean>();
	
	private volatile int m_lastPreIsolate = Isolate.DEFAULT_ID;
	
	private final Map<Integer, IsolateSession> m_isolateSessions;

	private static final String DEBUG_MESSAGES = "$debug_messages"; //$NON-NLS-1$
	private static final String DEBUG_MESSAGE_SIZE = "$debug_message_size"; //$NON-NLS-1$
	private static final String DEBUG_MESSAGE_FILE = "$debug_message_file"; //$NON-NLS-1$
	private static final String DEBUG_MESSAGE_FILE_SIZE = "$debug_message_file_size"; //$NON-NLS-1$

	private static final String CONSOLE_ERRORS = "$console_errors"; //$NON-NLS-1$

	private static final String FLASH_PREFIX = "$flash_"; //$NON-NLS-1$
	
	PlayerSession(Socket s, DProtocol proto, DManager manager, IDebuggerCallbacks debuggerCallbacks)
	{
		m_isConnected = false;
		m_isHalted = false;
		m_socket = s;
		m_protocol = proto;
		m_manager = manager;
		m_prefs = Collections.synchronizedMap(new HashMap<String, Object>());
		m_incoming = false;
		m_debugMsgOn = false;
		m_debugMsgSize = 16;
		m_debugMsgFileOn = false;
		m_debugMsgFileSize = 128;
		m_watchTransactionTag = 1;  // number that is sent for each watch transaction that occurs
		m_playerCanCallFunctions = null;
		m_debuggerCallbacks = debuggerCallbacks;
		m_isolateSessions = Collections.synchronizedMap(new HashMap<Integer, IsolateSession>());
	}
	
	private static PlayerSession createFromSocketHelper(Socket s, IDebuggerCallbacks debuggerCallbacks, DProtocol proto) throws IOException
	{
		// let the manager hear incoming messages
		DManager manager = new DManager();

		PlayerSession session = new PlayerSession(s, proto, manager, debuggerCallbacks);
		return session;
	}

	/**
	 * @deprecated Use createFromSocketWithOptions
	 * @param s
	 * @param debuggerCallbacks
	 * @return
	 * @throws IOException
	 */
	public static PlayerSession createFromSocket(Socket s, IDebuggerCallbacks debuggerCallbacks) throws IOException
	{
		DProtocol proto = DProtocol.createFromSocket(s);

		return createFromSocketHelper(s, debuggerCallbacks, proto);
	}
	
	/**
	 * Creates a session from the socket. Sets session specific 
	 * socket settings and stores the callback object.
	 * @param s
	 * @param debuggerCallbacks
	 * @param sessionManager
	 * @return
	 * @throws IOException
	 */
	public static PlayerSession createFromSocketWithOptions(Socket s, IDebuggerCallbacks debuggerCallbacks, SessionManager sessionManager) throws IOException
	{
		DProtocol proto = DProtocol.createFromSocket(s, sessionManager);

		return createFromSocketHelper(s, debuggerCallbacks, proto);
	}

	/* getter */
	public DMessageCounter		getMessageCounter()		{ return m_protocol.getMessageCounter(); }
	public String				getURI()				{ return m_manager.getURI(); }
	public boolean				playerSupportsGet()		{ return m_manager.isGetSupported(); }
    public int                  playerVersion()         { return m_manager.getVersion(); }
    public SourceLocator        getSourceLocator()      { return m_manager.getSourceLocator(); }

	/*
	 * @see flash.tools.debugger.Session#setSourceLocator(flash.tools.debugger.SourceLocator)
	 */
	public void setSourceLocator(SourceLocator sourceLocator)
	{
		m_manager.setSourceLocator(sourceLocator);
	}

	/**
	 * If the manager started the process for us, then note it here. We will attempt to kill
	 * it when we go down
	 */
	void setProcess(Process proc)
	{
		m_process = proc;
	}

	/*
	 * @see flash.tools.debugger.Session#getLaunchProcess()
	 */
	public Process getLaunchProcess()
	{
		return m_process;
	}

	/**
	 * Set preference
	 * If an invalid preference is passed, it will be silently ignored.
	 */
	public void			setPreferences(Map<String, ? extends Object> map)	{ m_prefs.putAll(map); mapBack(); }
	public Set<String>	keySet()								{ return m_prefs.keySet(); }
	public Object		getPreferenceAsObject(String pref)		{ return m_prefs.get(pref); }

	/**
	 * Set a property. Special logic for debug message boolean
	 */
	public void setPreference(String pref, int value)
	{
		m_prefs.put(pref, new Integer(value));
		mapBack();

		// change in console messages?
		if (pref.equals(CONSOLE_ERRORS))
			sendConsoleErrorsAsTrace(value == 1);

		// generic message for flash player wherein "$flash_xxx" causes "xxx" to be sent
		if (pref.startsWith(FLASH_PREFIX))
			sendOptionMessage(pref.substring(FLASH_PREFIX.length()), Integer.toString(value));
	}

	// helper for mapBack()
	private int mapBackOnePreference(String preferenceName, int defaultValue)
	{
		Object prefValue = getPreferenceAsObject(preferenceName);
		if (prefValue != null)
			return ((Integer)prefValue).intValue();
		else
			return defaultValue;
	}

	// helper for mapBack()
	private boolean mapBackOnePreference(String preferenceName, boolean defaultValue)
	{
		Object prefValue = getPreferenceAsObject(preferenceName);
		if (prefValue != null)
			return ((Integer)prefValue).intValue() != 0 ? true : false;
		else
			return defaultValue;
	}

	// look for preferences, that map back to variables
	private void mapBack()
	{
		m_debugMsgOn = mapBackOnePreference(DEBUG_MESSAGES, m_debugMsgOn);
		m_debugMsgSize = mapBackOnePreference(DEBUG_MESSAGE_SIZE, m_debugMsgSize);

		m_debugMsgFileOn = mapBackOnePreference(DEBUG_MESSAGE_FILE, m_debugMsgFileOn);
		m_debugMsgFileSize = mapBackOnePreference(DEBUG_MESSAGE_FILE_SIZE, m_debugMsgFileSize);
	}

	public int getPreference(String pref)
	{
		int val = 0;
		Integer i = (Integer)m_prefs.get(pref);
		if (i == null)
			throw new NullPointerException();
		else
			val = i.intValue();
		return val;
	}


	/*
	 * @see flash.tools.debugger.Session#isConnected()
	 */
	public boolean isConnected()
	{
		return m_isConnected;
	}

	/*
	 * @see flash.tools.debugger.Session#isSuspended()
	 */
	public boolean isSuspended() throws NotConnectedException
	{
		if (!isConnected())
			throw new NotConnectedException();

		return m_isHalted;
	}
	
	/*
	 * @see flash.tools.debugger.Session#isIsolateSuspended()
	 */
	public boolean isWorkerSuspended(int isolateId) throws NotConnectedException
	{
		if (isolateId == Isolate.DEFAULT_ID)
			return isSuspended();
		
		if (!isConnected())
			throw new NotConnectedException();
		
		if (m_isolateStatus.containsKey(isolateId)) {
			return m_isolateStatus.get(isolateId).m_isHalted;
		}
		
		return false;
	}

	/**
	 * Start up the session listening for incoming messages on the socket
	 */
	public boolean bind() throws VersionException
	{
		boolean bound = false;

		if (m_isConnected)
			return false;
		
		// mark that we are connected
		m_isConnected = true;

		// attach us to the pipe (we are last to ensure that DManager and msg counter
		// get updated first
		m_protocol.addListener(ListenerIndex.PlayerSession, this);

		// start up the receiving thread
		bound = m_protocol.bind();

		// transmit our first few adjustment messages
		sendStopWarning();
		sendStopOnFault();
		sendEnumerateOverride();
		sendFailureNotify();
		sendInvokeSetters();
		sendSwfloadNotify();
		sendGetterTimeout();
		sendSetterTimeout();
		boolean responded = sendSquelch(true, Isolate.DEFAULT_ID);

		// now note in our preferences whether get is working or not.
		setPreference(SessionManager.PLAYER_SUPPORTS_GET, playerSupportsGet() ? 1 : 0);
		if (supportsConcurrency()) {
			sendConcurrentDebugger();
		}
		
		if (supportsWideLineNumbers()) {
			sendWideLineDebugger();
		}

		// Spawn a background thread which fetches the SWF and SWD
		// from the Player and uses them to build function name tables
		// for each source file
		Thread t = new Thread(this, "SWF/SWD reader"); //$NON-NLS-1$
		t.setDaemon(true);
		t.start();

		// we're probably using a bad version
		if (!responded)
			throw new VersionException();

		return bound;
	}

	/**
	 * Permanently stops the debugging session and breaks the
	 * connection to the Player
	 */
	public void unbind()
	{
		unbind(false);
	}

	/**
	 * @param requestTerminate
	 *            if true, and if the player to which we are attached is capable
	 *            of terminating itself (e.g. Adobe AIR), then the player will
	 *            be told to terminate.
	 * @return true if the player is capable of terminating itself and has been
	 *         told to do so
	 */
	private boolean unbind(boolean requestTerminate)
	{
		// If the caller asked us to terminate the player, then we first check
		// whether the player to which we are connected is capable of that.
		// (The web-based players are not; Adobe AIR is.)
		requestTerminate = requestTerminate && playerCanTerminate();
		DMessage dm = DMessageCache.alloc(1);
		dm.setType(DMessage.OutExit);
		dm.putByte((byte)(requestTerminate ? 1 : 0));
		sendMessage(dm);

		// unbind from the socket, so that we don't receive any more messages
		m_protocol.unbind();

		// kill the socket
		try { m_socket.close(); } catch(IOException io) {}

		m_isConnected = false;
		m_isHalted = false;

		return requestTerminate; // true if player was told to terminate
	}

	/**
	 * Execute the specified AppleScript by passing it to /usr/bin/osascript.
	 *
	 * @param appleScript
	 *            the AppleScript to execute, as a series of lines
	 * @param argv
	 *            any arguments; these can be accessed from within your
	 *            AppleScript via "item 1 or argv", "item 2 of argv", etc.
	 * @return any text which was sent to stdout by /usr/bin/osascript, with the
	 *         trailing \n already removed
	 */
	private String executeAppleScript(String[] appleScript, String[] argv)
	{
		StringBuilder retval = new StringBuilder();
		try
		{
			List<String> execArgs = new LinkedList<String>();
			// "osascript" is the command-line way of executing AppleScript.
			execArgs.add("/usr/bin/osascript"); //$NON-NLS-1$
			execArgs.add("-"); //$NON-NLS-1$
			if (argv != null)
			{
				for (int i=0; i<argv.length; ++i)
					execArgs.add(argv[i]);
			}
			Process osascript = Runtime.getRuntime().exec(execArgs.toArray(new String[execArgs.size()]));
			// feed our AppleScript code to osascript's stdin
			OutputStream outputStream = osascript.getOutputStream();
			PrintWriter writer = new PrintWriter(outputStream, true);
			writer.println("on run argv"); //$NON-NLS-1$ // this gives the name "argv" to the command-line args
			for (int i=0; i<appleScript.length; ++i)
				writer.println(appleScript[i]);
			writer.println("end run"); //$NON-NLS-1$
			writer.close();
			InputStreamReader reader = new InputStreamReader(osascript.getInputStream());
			int ch;
			while ( (ch=reader.read()) != -1 )
				retval.append((char)ch);
		}
		catch (IOException e)
		{
			// ignore
		}
		return retval.toString().replaceAll("\n$", ""); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Execute the specified AppleScript by passing it to /usr/bin/osascript.
	 *
	 * @param appleScriptFilename
	 *            The name of the file containing AppleScript to execute.  This
	 *            must be relative to PlayerSession.java.
	 * @param argv
	 *            any arguments; these can be accessed from within your
	 *            AppleScript via "item 1 or argv", "item 2 of argv", etc.
	 * @return any text which was sent to stdout by /usr/bin/osascript, with the
	 *         trailing \n already removed
	 * @throws IOException
	 */
	private String executeAppleScript(String appleScriptFilename, String[] argv) throws IOException
	{
		InputStream stm = null;
		try {
			stm = PlayerSession.class.getResourceAsStream(appleScriptFilename);
			BufferedReader reader = new BufferedReader(new InputStreamReader(stm));
			String line;
			List<String> appleScriptLines = new ArrayList<String>();
			while ( (line=reader.readLine()) != null )
				appleScriptLines.add(line);
			String[] lines = appleScriptLines.toArray(new String[appleScriptLines.size()]);
			return executeAppleScript(lines, argv);
		} finally {
			if (stm != null) {
				stm.close();
			}
		}
	}

	/**
	 * Checks whether the specified Macintosh web browser is currently
	 * running.  You should only call this function if you have already
	 * checked that you are running on a Mac.
	 *
	 * @param browserName a name, e.g. "Safari", "Firefox", "Camino"
	 * @return true if currently running
	 */
	private Set<String> runningApplications()
	{
		String running = executeAppleScript(
			new String[]
        	{
				"tell application \"System Events\"", //$NON-NLS-1$
				"	name of processes", //$NON-NLS-1$
				"end tell" //$NON-NLS-1$
        	},
        	null
		);
		String[] apps = running.split(", "); //$NON-NLS-1$
		Set<String> retval = new HashSet<String>();
		for (int i=0; i<apps.length; ++i)
			retval.add(apps[i]);
		return retval;
	}

	/**
	 * Destroys all objects related to the connection
	 * including the process that was tied to this
	 * session via SessionManager.launch(), if it
	 * exists.
	 */
	public void terminate()
	{
		boolean playerWillTerminateItself = false;

		// unbind first
		try
		{
			// Tell player to end session.  Note that this is just a hint, and will often
			// do nothing.  For example, the Flash player running in a browser will
			// currently never terminate when you tell it to, but the AIR player will
			// terminate.
			playerWillTerminateItself = unbind(true);
		} catch(Exception e)
		{
		}

		if (!playerWillTerminateItself)
		{
			if (System.getProperty("os.name").toLowerCase().startsWith("mac os x")) //$NON-NLS-1$ //$NON-NLS-2$
			{
				if (m_airLaunchInfo != null)
				{
					// nothing we need to do -- Process.destroy() will kill the AIR app
				}
				else if (m_launchUrl != null && m_launchUrl.length() > 0)
				{
					boolean closedAnyWindows = false;
					Set<String> runningApps = runningApplications();

					if (runningApps.contains("Safari")) //$NON-NLS-1$
					{
						try {
							String url = m_launchUrl.replaceAll(" ", "%20"); //$NON-NLS-1$ //$NON-NLS-2$
							String safariClosedAnyWindows = executeAppleScript("appleScriptCloseSafariWindow.txt", new String[] { url }); //$NON-NLS-1$
							if ("true".equals(safariClosedAnyWindows)) { //$NON-NLS-1$ 								
								closedAnyWindows = true;								
							}
							else if ( "appquit".equals(safariClosedAnyWindows) ) { //$NON-NLS-1$
								closedAnyWindows = true;
								//we closed Safari, verify safari was closed
								runningApps = waitForMacAppQuit("Safari"); //$NON-NLS-1$
							}
						} catch (IOException e) {
							// ignore
						}
					}
										
					if (!closedAnyWindows && runningApps.contains("Camino")) //$NON-NLS-1$
					{
						// For local file: URLs, Camino uses "file://localhost/..." instead of "file:///..."
						String url = m_launchUrl.replaceFirst("^file:///", "file://localhost/"); //$NON-NLS-1$ //$NON-NLS-2$
						try {
							String caminoClosedAnyWindows = executeAppleScript("appleScriptCloseCaminoWindow.txt", new String[] { url }); //$NON-NLS-1$
							if ("true".equals(caminoClosedAnyWindows)) { //$NON-NLS-1$								
								closedAnyWindows = true;
							}
							else if ( "appquit".equals(caminoClosedAnyWindows) ) { //$NON-NLS-1$
								closedAnyWindows = true;
								//we closed camino, verify camino was closed
								runningApps = waitForMacAppQuit("Camino"); //$NON-NLS-1$
							}
								
						} catch (IOException e) {
							// ignore
						}
					}

					// The standalone player on the Mac has gone through several name changes,
					// so we have to look for all of these.
					String[] macStandalonePlayerNames =
					{
						"Flash Player Debugger",	// New name as of Player 10.1	//$NON-NLS-1$
						"Flash Player",				// New name as of 12/4/06		//$NON-NLS-1$
						"SAFlashPlayer",			// An older name				//$NON-NLS-1$
						"standalone"				// Another older name			//$NON-NLS-1$
					};

					for (int i=0; !closedAnyWindows && i<macStandalonePlayerNames.length; ++i)
					{
						if (runningApps.contains(macStandalonePlayerNames[i]))
						{
							executeAppleScript(new String[] { "tell application \"" + macStandalonePlayerNames[i] + "\" to quit" }, null); //$NON-NLS-1$ //$NON-NLS-2$
							waitForMacAppQuit(macStandalonePlayerNames[i]);
							closedAnyWindows = true;
						}
					}
				}
			}

			// if we have a process pop it
			if (m_process != null)
			{
				try
				{
					//if a launcher is set for handling the launcher operations then use it.
					if(null != launcher)
					{
						m_debuggerCallbacks.terminateDebugTarget(m_process,launcher);
					}
					else
					{
						m_debuggerCallbacks.terminateDebugTarget(m_process);
					}
				}
				catch (IOException e)
				{
					// ignore
				}
			}
		}
		else if (m_process != null) {
			try {
				m_process.waitFor();
			}
			catch (Exception e) {
			}
		}

		// now clear it all
		m_isConnected = false;
		m_isHalted = false;
	}

	/**
	 * Utility function to wait for a mac application to quit.
	 * This waits for a maximum of MAX_TERMINATE_WAIT_MILLIS.
	 * 
	 * Waiting is important because applescript "quit" is not
	 * synchronous and launching a URL while the browser is
	 * quitting is not good. (See FB-21879)
	 * @return Set<String> of running applications.
	 */
	private Set<String> waitForMacAppQuit(String browser) {
		Set<String> runningApps;
		boolean appClosed = true;
		final long startMillis = System.currentTimeMillis();		
		final long waitMillis = 100;
		do {
			runningApps = runningApplications();
			if ( runningApps.contains(browser) ) {
				appClosed = false;
				
				try {
					Thread.sleep(waitMillis);					
				} catch (InterruptedException e) {
					return runningApps;
				}
				
				long currentMillis = System.currentTimeMillis();
				
				if ( currentMillis - startMillis >= MAX_TERMINATE_WAIT_MILLIS )
					break;
			}
			else {
				appClosed = true;
			}
		}
		while ( !appClosed );
		return runningApps;
	}

	/*
	 * @see flash.tools.debugger.Session#resume()
	 */
	public void resume() throws NotSuspendedException, NotConnectedException, NoResponseException
	{
		resumeWorker(Isolate.DEFAULT_ID);
	}

	/*
	 * @see flash.tools.debugger.Session#suspend()
	 */
	public void suspend() throws SuspendedException, NotConnectedException, NoResponseException
	{
		suspendWorker(Isolate.DEFAULT_ID);
	}

	/**
	 * Obtain all the suspend information
	 */
	public DSuspendInfo getSuspendInfo()
	{
		return getSuspendInfoIsolate(Isolate.DEFAULT_ID);
	}

	/**
	 * Return the reason that the player has suspended itself.
	 */
	public int suspendReason()
	{
		DSuspendInfo info = getSuspendInfo();
		return info.getReason();
	}

	/**
	 * Return the offset in which the player has suspended itself.  The BreakReason
	 * message contains both reason and offset.
	 */
	public int getSuspendOffset()
	{
		DSuspendInfo info = getSuspendInfo();
		return info.getOffset();
	}

	/**
	 * Return the offset in which the player has suspended itself.  The BreakReason
	 * message contains both reason and offset.
	 */
	public int getSuspendActionIndex()
	{
		DSuspendInfo info = getSuspendInfo();
		return info.getActionIndex();
	}

	/**
	 * Obtain information about the various SWF(s) that have been
	 * loaded into the Player, for this session.
	 *
	 * Note: As SWFs are loaded by the Player a SwfLoadedEvent is
	 * fired.  At this point, a call to getSwfInfo() will provide
	 * updated information.
	 *
	 * @return array of records describing the SWFs
	 */
	public SwfInfo[] getSwfs() throws NoResponseException
	{
		return getSwfsWorker(Isolate.DEFAULT_ID);
	}

	/**
	 * Request information on a particular swf, used by DSwfInfo
	 * to fill itself correctly
	 */
	public void requestSwfInfo(int at, int isolateId) throws NoResponseException
	{
		// nope don't have it...might as well go out and ask for all of them.
		DMessage dm = DMessageCache.alloc(4);
		dm.setType( DMessage.OutSwfInfo );
		dm.setTargetIsolate(isolateId);
		dm.putWord(at);
		dm.putWord(0);  // rserved

		int to = getPreference(SessionManager.PREF_CONTEXT_RESPONSE_TIMEOUT);

		if (!simpleRequestResponseMessage(dm, DMessage.InSwfInfo, to))
			throw new NoResponseException(to);
	}

	/**
	 * Request a set of actions from the player
	 */
	public byte[] getActions(int which, int at, int len) throws NoResponseException
	{
		byte[] actions = null;

		// send a actions message
		DMessage dm = DMessageCache.alloc(12);
		dm.setType( DMessage.OutGetActions );
		dm.putWord(which);
		dm.putWord(0); // rsrvd
		dm.putDWord(at);
		dm.putDWord(len);

		// request action bytes
		int to = getPreference(SessionManager.PREF_CONTEXT_RESPONSE_TIMEOUT);
		if (simpleRequestResponseMessage(dm, DMessage.InGetActions, to))
			actions = m_manager.getActions();
		else
			throw new NoResponseException(to);

		return actions;
	}

	/*
	 * @see flash.tools.debugger.Session#stepInto()
	 */
	public void stepInto() throws NotSuspendedException, NoResponseException, NotConnectedException
	{
		stepIntoWorker(Isolate.DEFAULT_ID);
	}

	/*
	 * @see flash.tools.debugger.Session#stepOut()
	 */
	public void stepOut() throws NotSuspendedException, NoResponseException, NotConnectedException
	{
		stepOutWorker(Isolate.DEFAULT_ID);
	}

	/*
	 * @see flash.tools.debugger.Session#stepOver()
	 */
	public void stepOver() throws NotSuspendedException, NoResponseException, NotConnectedException
	{
		stepOverWorker(Isolate.DEFAULT_ID);
	}

	/*
	 * @see flash.tools.debugger.Session#stepContinue()
	 */
	public void stepContinue() throws NotSuspendedException, NoResponseException, NotConnectedException
	{
		if (!isSuspended())
			throw new NotSuspendedException();

		// send a step-continue message and then wait for the Flash player to tell us that is has
		// resumed execution
		if (!simpleRequestResponseMessage(DMessage.OutStepContinue, DMessage.InContinue))
			throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT));
	}

    /**
     * Sends a request to the player to obtain function names.
     * The resultant message end up populating the function name array
     * for the given DModule.
     *
     * @param moduleId
     * @param lineNbr
     * @return
     */
    public void requestFunctionNames(int moduleId, int lineNbr, int isolateId) throws VersionException, NoResponseException
    {
        // only player 9 supports this message
        if (m_manager.getVersion() >= 9)
        {
            DMessage dm = DMessageCache.alloc(8);
            dm.setType(DMessage.OutGetFncNames);
            dm.setTargetIsolate(isolateId);
            dm.putDWord(moduleId);
            dm.putDWord(lineNbr);

            if (!simpleRequestResponseMessage(dm, DMessage.InGetFncNames))
                throw new NoResponseException(0);
        }
        else
        {
            throw new VersionException();
        }
    }

	/**
	 * From a given file identifier return a source file object
	 */
	public SourceFile getFile(int fileId, int isolateId)
	{
		return m_manager.getSource(fileId, isolateId);
	}

	/**
	 * Get a list of breakpoints
	 */
	public Location[] getBreakpointList()
	{
		return m_manager.getBreakpoints(Isolate.DEFAULT_ID);
	}

	/*
	 * @see flash.tools.debugger.Session#setBreakpoint(int, int)
	 */
	public Location setBreakpoint(int fileId, int lineNum) throws NoResponseException, NotConnectedException
	{
		return setBreakpointWorker(fileId, lineNum, Isolate.DEFAULT_ID);
	}

	/*
	 * @see flash.tools.debugger.Session#clearBreakpoint(flash.tools.debugger.Location)
	 */
	public Location clearBreakpoint(Location local)
	{
		/* first find it */
		SourceFile source = local.getFile();
		int fileId = source.getId();
		int lineNum = local.getLine();
		int bp = DLocation.encodeId(fileId, lineNum);
		int isolateId = local.getIsolateId();
		Location l = null;
		l = m_manager.getBreakpoint(bp, isolateId);
		
		if (l != null)
		{
			/* send the message */
			int wideLineSize = 0;
			if (supportsWideLineNumbers())
				wideLineSize = 4;
			DMessage dm = DMessageCache.alloc(8 + wideLineSize);
			dm.setType(DMessage.OutRemoveBreakpoints);
			dm.setTargetIsolate(isolateId);
			dm.putDWord(1);
			if (!supportsWideLineNumbers())
				dm.putDWord(bp);
			else {
				dm.putDWord(fileId);
				dm.putDWord(lineNum);
			}
			sendMessage(dm);

			/* no callback from the player so we remove it ourselves */
			m_manager.removeBreakpoint(bp, isolateId);
		}
		return l;
	}

	/*
	 * @see flash.tools.debugger.Session#getWatchList()
	 */
	public Watch[] getWatchList() throws NoResponseException, NotConnectedException
	{
		return getWatchListWorker(Isolate.DEFAULT_ID);
	}
	
	/*
	 * @see flash.tools.debugger.Session#getWatchList()
	 */
	public Watch[] getWatchListWorker(int isolateId) throws NoResponseException, NotConnectedException
	{
			return m_manager.getWatchpoints(isolateId);
	}

	private Watch setWatch(long varId, String memberName, int kind, int isolateId) throws NoResponseException, NotConnectedException, NotSupportedException
	{
		// we really have two cases here, one where we add a completely new
		// watchpoint and the other where we modify an existing one.
		// In either case the DManager logic is such that the last watchpoint
		// in the list will contain our id if successful.
		Watch w = null;
		int tag = m_watchTransactionTag++;

		if (addWatch(varId, memberName, kind, tag, isolateId))
		{
			// good that we got a response now let's check that
			// it actually worked.
			int count = m_manager.getWatchpointCount(isolateId);
			if (count > 0)
			{
				DWatch lastWatch = m_manager.getWatchpoint(count-1, isolateId);
				if (lastWatch.getTag() == tag)
					w = lastWatch;
			}
		}
		return w;
	}
	
	/*
	 * @see flash.tools.debugger.Session#setWatch(flash.tools.debugger.Variable, java.lang.String, int)
	 */
	public Watch setWatch(Value v, String memberName, int kind) throws NoResponseException, NotConnectedException, NotSupportedException
	{
		return setWatch(v.getId(), memberName, kind, v.getIsolateId());
	}

	public Watch setWatch(Watch watch) throws NoResponseException, NotConnectedException, NotSupportedException
	{
		return setWatch(watch.getValueId(), watch.getMemberName(), watch.getKind(), watch.getIsolateId());
	}

	/*
	 * @see flash.tools.debugger.Session#clearWatch(flash.tools.debugger.Watch)
	 */
	public Watch clearWatch(Watch watch) throws NoResponseException, NotConnectedException
	{
		Watch[] list = getWatchListWorker(watch.getIsolateId());
		Watch w = null;
		if ( removeWatch(watch.getValueId(), watch.getMemberName(), watch.getIsolateId()) )
		{
			// now let's first check the size of the list, it
			// should now be one less
			if (m_manager.getWatchpointCount(watch.getIsolateId()) < list.length)
			{
				// ok we made a change. So let's compare list and see which
				// one went away
				Watch[] newList = getWatchListWorker(watch.getIsolateId());
				for(int i=0; i<newList.length; i++)
				{
					// where they differ is the missing one
					if (list[i] != newList[i])
					{
						w = list[i];
						break;
					}
				}

				// might be the last one...
				if (w == null)
					w = list[list.length-1];
			}
		}
		return w;
	}

	/*
	 * @see flash.tools.debugger.Session#getVariableList()
	 */
	public Variable[] getVariableList() throws NotSuspendedException, NoResponseException, NotConnectedException, VersionException
	{
		return getVariableListWorker(Isolate.DEFAULT_ID);
	}
	
	public Variable[] getVariableListWorker(int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException, VersionException
	{
		// make sure the player has stopped and send our message awaiting a response
		if (!isWorkerSuspended(isolateId))
			throw new NotSuspendedException();

		requestFrame(0, isolateId);  // our 0th frame gets our local context

		// now let's request all of the special variables too
		getValueWorker(Value.GLOBAL_ID, isolateId);
		getValueWorker(Value.THIS_ID, isolateId);
		getValueWorker(Value.ROOT_ID, isolateId);

		// request as many levels as we can get
		int i = 0;
		Value v = null;
		do
		{
			v = getValueWorker(Value.LEVEL_ID-i, isolateId);
		}
		while( i++ < 128 && v != null);

		// now that we've primed the DManager we can request the base variable whose
		// children are the variables that are available
		v = m_manager.getValue(Value.BASE_ID, isolateId);
		if (v == null)
			throw new VersionException();
		return v.getMembers(this);
	}

	/*
	 * @see flash.tools.debugger.Session#getFrames()
	 */
	public Frame[] getFrames() throws NotConnectedException
	{
		return m_manager.getFrames(Isolate.DEFAULT_ID);
	}

	/**
	 * Asks the player to return information regarding our current context which includes
	 * this pointer, arguments for current frame, locals, etc.
	 */
	public void requestFrame(int depth, int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException
	{
		if (playerSupportsGet())
		{
			if (!isWorkerSuspended(isolateId))
				throw new NotSuspendedException();

			int timeout = getPreference(SessionManager.PREF_CONTEXT_RESPONSE_TIMEOUT);

			DMessage dm = DMessageCache.alloc(4);
			dm.setType(DMessage.OutGetFrame);
			dm.setTargetIsolate(isolateId);
			dm.putDWord(depth);  // depth of zero
			if (!simpleRequestResponseMessage(dm,  DMessage.InFrame, timeout)) {
				throw new NoResponseException(timeout);
			}

			pullUpActivationObjectVariables(depth, isolateId);
		}
	}
	
	/**
	 * The compiler sometimes creates special local variables called
	 * "activation objects."  When it decides to do this (e.g. if the
	 * current function contains any anonymous functions, try/catch
	 * blocks, complicated E4X expressions, or "with" clauses), then
	 * all locals and arguments are actually stored as children of
	 * this activation object, rather than the usual way.
	 *
	 * We need to hide this implementation detail from the user.  So,
	 * if we find any activation objects among the locals of the current
	 * function, then we will "pull up" its members, and represent them
	 * as if they were actually args/locals of the function itself.
	 *
	 * @param depth the depth of the stackframe we are fixing; 0 is topmost
	 */
	private void pullUpActivationObjectVariables(int depth, int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException
	{
		DValue frame = m_manager.getValue(Value.BASE_ID-depth, isolateId);
		if (frame == null)
			return;
		DStackContext context = m_manager.getFrame(depth, isolateId);		
		DVariable[] frameVars = (DVariable[]) frame.getMembers(this);
		Map<String, DVariable> varmap = new LinkedHashMap<String, DVariable>(frameVars.length); // preserves order
		List<DVariable> activationObjects = new ArrayList<DVariable>();
		Pattern activationObjectNamePattern = Pattern.compile("^.*\\$\\d+$"); //$NON-NLS-1$

		// loop through all frame variables, and separate them into two
		// groups: activation objects, and all others (locals and arguments)
		for (int i=0; i<frameVars.length; ++i)
		{
			DVariable member = frameVars[i];
			Matcher matcher = activationObjectNamePattern.matcher(member.getName());
			if (matcher.matches())
				activationObjects.add(member);
			else
				varmap.put(member.getName(), member);
		}

		// If there are no activation objects, then we don't need to do anything
		if (activationObjects.size() == 0)
			return;

		// overwrite existing args and locals with ones pulled from the activation objects
		for (int i=0; i<activationObjects.size(); ++i)
		{
			DVariable activationObject = activationObjects.get(i);
			DVariable[] activationMembers = (DVariable[]) activationObject.getValue().getMembers(this);
			for (int j=0; j<activationMembers.length; ++j)
			{
				DVariable member = activationMembers[j];
				int attributes = member.getAttributes();

				// For some odd reason, the activation object often contains a whole bunch of
				// other variables that we shouldn't be displaying.  I don't know what they
				// are, but I do know that they are all marked "static".
				if ((attributes & VariableAttribute.IS_STATIC) != 0)
					continue;

				// No matter what the activation object member's scope is, we want all locals
				// and arguments to be considered "public"
				attributes &= ~(VariableAttribute.PRIVATE_SCOPE | VariableAttribute.PROTECTED_SCOPE | VariableAttribute.NAMESPACE_SCOPE);
				attributes |= VariableAttribute.PUBLIC_SCOPE;
				member.setAttributes(attributes);

				String name = member.getName();
				DVariable oldvar = varmap.get(name);
				int vartype;
				if (oldvar != null)
					vartype = oldvar.getAttributes() & (VariableAttribute.IS_ARGUMENT | VariableAttribute.IS_LOCAL);
				else
					vartype = VariableAttribute.IS_LOCAL;
				member.setAttributes(member.getAttributes() | vartype);
				varmap.put(name, member);
			}

			context.convertLocalToActivationObject(activationObject);
		}

		for (DVariable var: varmap.values())
		{
			frame.addMember(var);
			if (var.isAttributeSet(VariableAttribute.IS_LOCAL))
			{
				context.addLocal(var);
			}
			else if (var.isAttributeSet(VariableAttribute.IS_ARGUMENT))
			{
				if (var.getName().equals("this")) //$NON-NLS-1$
					context.setThis(var);
				else
					context.addArgument(var);
			}
		}
	}

	/*
	 * @see flash.tools.debugger.Session#getValue(int)
	 */
	public Value getValue(long valueId) throws NotSuspendedException, NoResponseException, NotConnectedException
	{
		return getValueWorker(valueId, Isolate.DEFAULT_ID);
	}
	
	public Value getValueWorker(long valueId, int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException
	{
		DValue val = null;

		if (!isWorkerSuspended(isolateId))
			throw new NotSuspendedException();

		// get it from cache if we can
		val = m_manager.getValue(valueId, isolateId);

		if (val == null)
		{
			// if a special variable, then we need to trigger a local frame call, otherwise just use id to get it
			if (valueId < Value.UNKNOWN_ID)
			{
				requestFrame(0, isolateId); // force our current frame to get populated, BASE_ID will be available
			}
			else if (valueId > Value.UNKNOWN_ID)
			{
				requestVariable(valueId, null, isolateId);
			}

			// after all this we should have our variable cache'd so try again if it wasn't there the first time
			val = m_manager.getValue(valueId, isolateId);
		}

		return val;
	}

	/**
	 * Returns the current value object for the given id; never requests it from the player.
	 */
	public Value getRawValue(long valueId, int isolateId)
	{
		return m_manager.getValue(valueId, isolateId);
	}

	/**
	 * Returns the previous value object for the given id -- that is, the value that that
	 * object had the last time the player was suspended.  Never requests it from the
	 * player (because it can't, of course).  Returns <code>null</code> if we don't have
	 * a value for that id.
	 */
	public Value getPreviousValue(long valueId, int isolateId)
	{
		return m_manager.getPreviousValue(valueId, isolateId);
	}

	/**
	 * Launches a request to obtain all the members of the specified variable, and
	 * store them in the variable which would be returned by
	 * {@link DManager#getVariable(long)}.
	 *
	 * @param valueId id of variable whose members we want; underlying Variable must
	 * already be known by the PlayerSessionManager.
	 *
	 * @throws NoResponseException
	 * @throws NotConnectedException
	 * @throws NotSuspendedException
	 */
	void obtainMembers(long valueId, int isolateId) throws NoResponseException, NotConnectedException, NotSuspendedException
	{
		if (!isWorkerSuspended(isolateId))
			throw new NotSuspendedException();

		// Get it from cache.  Normally, this should never fail; however, in
		// the case of Flex Builder, which is multithreaded, it is possible
		// that a thread has called this even after a different thread has
		// single-stepped, so that the original variable is no longer valid.
		// So, we'll check for a null return value.
		DValue v = m_manager.getValue(valueId, isolateId);

		if (v != null && !v.membersObtained())
		{
			requestVariable(valueId, null, false, true, isolateId);
		}
	}

	public Value getGlobal(String name) throws NotSuspendedException, NoResponseException, NotConnectedException
	{
		return getGlobalWorker(name, Isolate.DEFAULT_ID);
	}
	
	public Value getGlobalWorker(String name, int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException
	{
		Value v = getValue(0, name, isolateId);

		if (v==null || v.getType() == VariableType.UNDEFINED)
			return null;
		else
			return v;
	}

	/**
	 * Get the value of the variable named 'name' using varId
	 * as the context id for the Variable.
	 *
	 * This call is used to fire getters, where the id must
	 * be that of the original object and not the object id
	 * of where the getter actually lives.  For example
	 * a getter a() may live under o.__proto__.__proto__
	 * but you must use the id of o and the name of 'a'
	 * in order for the getter to fire correctly.  [Note: This
	 * paragraph was written for AS2; __proto__ doesn't exist
	 * in AS3.  TODO: revise this paragraph]
	 */
	public Value getValue(long varId, String name, int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException
	{
		Value v = null;
		if (isWorkerSuspended(isolateId))
		{
			int fireGetter = getPreference(SessionManager.PREF_INVOKE_GETTERS);

			// disable children attaching to parent variables and clear our
			// most recently seen variable
			m_manager.clearLastVariable(isolateId);
			m_manager.enableChildAttach(false, isolateId);

			try
			{
				requestVariable(varId, name, (fireGetter != 0), false, isolateId);

				DVariable lastVariable = m_manager.lastVariable(isolateId);
				if (lastVariable != null)
					v = lastVariable.getValue();
				else
					v = DValue.forPrimitive(Value.UNDEFINED, isolateId);
			}
			catch (NoResponseException e)
			{
				if (fireGetter != 0)
				{
					// We fired a getter -- most likely, what happened is that that getter
					// (which is actual code in the user's movie) just took too long to
					// calculate its value.  So rather than throwing an exception, we store
					// some error text for the value of the variable itself.
					//
					// TODO [mmorearty 4/20/06] Even though I wrote the below code, I now
					// am wondering if it is incorrect that I am calling addVariableMember(),
					// because in every other case, this function does not add members to
					// existing objects.  Need to revisit this.
					v = new DValue(VariableType.STRING, "String", "String", ValueAttribute.IS_EXCEPTION, //$NON-NLS-1$ //$NON-NLS-2$
							e.getLocalizedMessage(), isolateId);
					if (varId != 0) {
						DVariable var = new DVariable(name, (DValue)v, isolateId);
						m_manager.enableChildAttach(true, isolateId);
						m_manager.addVariableMember(varId, var, isolateId);
					}
				}
				else
				{
					throw e; // re-throw
				}
			}
			finally
			{
				// reset our attach flag, so that children attach to parent variables.
				m_manager.enableChildAttach(true, isolateId);
			}
		}
		else
			throw new NotSuspendedException();

		return v;
	}

	private void requestVariable(long id, String name, int isolateId) throws NoResponseException, NotConnectedException, NotSuspendedException
	{
		requestVariable(id, name, false, false, isolateId);
	}

	/**
	 * @param thisValue the value of the "this" pointer; meaningless if isConstructor is true
	 * @param isConstructor whether we're calling a constructor as opposed to a regular function
	 * @param funcname the name of the function to call (or class whose constructor we're calling)
	 * @param args the args to the function
	 * @return the return value of the function
	 */
	private Value callFunction(Value thisValue, boolean isConstructor, String funcname, Value[] args, int isolateId) throws PlayerDebugException
	{
		if (!isWorkerSuspended(isolateId))
			throw new NotSuspendedException();

		if (!playerCanCallFunctions(isolateId))
			throw new NotSupportedException(PlayerSessionManager.getLocalizationManager().getLocalizedTextString("functionCallsNotSupported")); //$NON-NLS-1$

		// name = getRawMemberName(id, name);

		m_manager.clearLastFunctionCall(isolateId);

		DMessage dm = buildCallFunctionMessage(isConstructor, thisValue, funcname, args);

		dm.setTargetIsolate(isolateId);
		
		// make sure any exception during the setter gets held onto
		m_manager.beginPlayerCodeExecution(isolateId);

		// TODO wrong timeout
		int timeout = getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT);
		timeout += 500; // give the player enough time to raise its timeout exception

		boolean result = simpleRequestResponseMessage(dm, DMessage.InCallFunction, timeout);

		// tell manager we're done; ignore returned FaultEvent
		m_manager.endPlayerCodeExecution(isolateId);

		if (!result)
			throw new NoResponseException(timeout);

		DVariable lastFunctionCall = m_manager.lastFunctionCall(isolateId);
		if (lastFunctionCall != null)
			return lastFunctionCall.getValue();
		else
			return DValue.forPrimitive(Value.UNDEFINED, isolateId);
	}

	/*
	 * @see flash.tools.debugger.Session#callFunction(flash.tools.debugger.Value, java.lang.String, flash.tools.debugger.Value[])
	 */
	public Value callFunction(Value thisValue, String funcname, Value[] args) throws PlayerDebugException
	{
		return callFunctionWorker(thisValue, funcname, args, Isolate.DEFAULT_ID);
	}
	
	public Value callFunctionWorker(Value thisValue, String funcname, Value[] args, int isolateId) throws PlayerDebugException
	{
		Value retval = callPseudoFunction(thisValue, funcname, args, isolateId);
		if (retval != null) {
			return retval;
		}

		return callFunction(thisValue, false, funcname, args, isolateId);
	}

	/**
	 * Checks to see if the function being called is a debugger pseudofunction such as
	 * $obj(), and if so, handles that directly rather than calling the player.  Returns
	 * null if the function being called is not a pseudofunction.
	 */
	private Value callPseudoFunction(Value thisValue, String funcname, Value[] args, int isolateId) throws PlayerDebugException{
		if (thisValue.getType() == VariableType.UNDEFINED || thisValue.getType() == VariableType.NULL) {
			if ("$obj".equals(funcname)) { //$NON-NLS-1$
				return callObjPseudoFunction(args, isolateId);
			}
		}

		return null;
	}

	/**
	 * Handles a call to the debugger pseudofunction $obj() -- e.g. $obj(1234) returns
	 * a pointer to the object with id 1234.
	 */
	private Value callObjPseudoFunction(Value[] args, int isolateId) throws PlayerDebugException {
		if (args.length != 1) {
			return DValue.forPrimitive(DValue.UNDEFINED, isolateId);
		}
		double arg = ECMA.toNumber(this, args[0]);
		long id = (long) arg;
		if (id != arg) {
			return DValue.forPrimitive(DValue.UNDEFINED, isolateId);
		}
		DValue value = m_manager.getValue(id, isolateId);
		if (value == null) {
			return DValue.forPrimitive(DValue.UNDEFINED, isolateId);
		}
		return value;
	}

	public Value callConstructor(String funcname, Value[] args) throws PlayerDebugException
	{
		return callConstructorWorker(funcname, args, Isolate.DEFAULT_ID);
	}
	
	public Value callConstructorWorker(String funcname, Value[] args, int isolateId) throws PlayerDebugException
	{
		return callFunction(DValue.forPrimitive(null, isolateId), true, funcname, args, isolateId);
	}

	private DMessage buildCallFunctionMessage(boolean isConstructor, Value thisValue, String funcname, Value[] args)
	{
		funcname = (funcname == null) ? "" : funcname; //$NON-NLS-1$

		int messageSize = 8; // DWORD representing flags + DWORD representing frame
		String thisType = DVariable.typeNameFor(thisValue.getType());
		String thisValueString = thisValue.getValueAsString();
		messageSize += DMessage.getStringLength(thisType)+1;
		messageSize += DMessage.getStringLength(thisValueString)+1;
		messageSize += DMessage.getStringLength(funcname)+1;
		messageSize += 4; // DWORD representing the number of args
		String[] argTypes = new String[args.length];
		String[] argValues = new String[args.length];
		for (int i=0; i<args.length; ++i)
		{
			argTypes[i] = DVariable.typeNameFor(args[i].getType());
			argValues[i] = args[i].getValueAsString();
			messageSize += DMessage.getStringLength(argValues[i])+1;
			messageSize += DMessage.getStringLength(argTypes[i])+1;
		}

		DMessage dm = DMessageCache.alloc(messageSize);
		dm.setType(DMessage.OutCallFunction);
		try
		{
			dm.putDWord(isConstructor ? 1 : 0);
			dm.putDWord(0); // TODO: the currently active frame number
			dm.putString(thisType);
			dm.putString(thisValueString);
			dm.putString(funcname);
			dm.putDWord(args.length);
			for (int i=0; i<args.length; ++i)
			{
				dm.putString(argTypes[i]);
				dm.putString(argValues[i]);
			}
		}
		catch(UnsupportedEncodingException uee)
		{
			// couldn't write out the string, so just terminate it and complete anyway
			dm.putByte((byte)'\0');
		}

		return dm;
	}

	private void requestVariable(long id, String name, boolean fireGetter, boolean alsoGetChildren, int isolateId) throws NoResponseException, NotConnectedException, NotSuspendedException
	{
		if (!isWorkerSuspended(isolateId))
			throw new NotSuspendedException();

		name = getRawMemberName(id, name, isolateId);

		DMessage dm = buildOutGetMessage(id, name, fireGetter, alsoGetChildren);

		dm.setTargetIsolate(isolateId);
		
		// make sure any exception during the setter gets held onto
		m_manager.beginPlayerCodeExecution(isolateId);

		int timeout = getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT);
		timeout += 500; // give the player enough time to raise its timeout exception

		boolean result = simpleRequestResponseMessage(dm, DMessage.InGetVariable, timeout);

		// tell manager we're done; ignore returned FaultEvent
		m_manager.endPlayerCodeExecution(isolateId);

		if (!result)
			throw new NoResponseException(timeout);
	}

	private DMessage buildOutGetMessage(long id, String name, boolean fireGetter, boolean alsoGetChildren)
	{
		final int FLAGS_SIZE = 4;
		name = (name == null) ? "" : name; //$NON-NLS-1$

		DMessage dm = DMessageCache.alloc(DMessage.getSizeofPtr() + DMessage.getStringLength(name)+1 + FLAGS_SIZE);
		dm.setType( (!fireGetter) ? DMessage.OutGetVariable : DMessage.OutGetVariableWhichInvokesGetter );
		dm.putPtr(id);
		try
		{
			dm.putString(name);
		}
		catch(UnsupportedEncodingException uee)
		{
			// couldn't write out the string, so just terminate it and complete anyway
			dm.putByte((byte)'\0');
		}

		// as an optimization, newer player builds allow us to tell them not to
		// send all the children of an object along with the object, because
		// frequently we don't care about the children
		int flags = GetVariableFlag.DONT_GET_FUNCTIONS; // we never want functions
		if (fireGetter)
			flags |= GetVariableFlag.INVOKE_GETTER;
		if (alsoGetChildren)
			flags |= GetVariableFlag.ALSO_GET_CHILDREN | GetVariableFlag.GET_CLASS_HIERARCHY;
		dm.putDWord(flags);

		return dm;
	}

	public FaultEvent setScalarMember(long varId, String memberName, int type, String value, int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException
	{
		if (!isWorkerSuspended(isolateId))
			throw new NotSuspendedException();

		// If the varId is that of a stack frame, then we need to check whether that
		// stack frame has an "activation object".  If it does, then all of the
		// arguments and locals are actually kept as members of that activation
		// object, and so we need to change varId to be the ID of that activation
		// object -- that way, the player will modify the member of the activation
		// object rather than modifying the "regular" argument or local.  See bug
		// 155031.
		if (varId <= Value.BASE_ID && varId > Value.LEVEL_ID)
		{
			int depth = (int) (Value.BASE_ID - varId);
			DStackContext context = m_manager.getFrame(depth,isolateId);
			DVariable activationObject = context.getActivationObject();
			if (activationObject != null)
				varId = activationObject.getValue().getId();
		}

		memberName = getRawMemberName(varId, memberName, isolateId);

		// see if it is our any of our special variables
		FaultEvent faultEvent = requestSetVariable( isPseudoVarId(varId) ? 0 : varId, memberName, type, value, isolateId);

		// now that we sent it out, we need to clear our variable cache
		// if it is our special context then mark the frame as stale.
		if (isPseudoVarId(varId) && m_manager.getFrameCount(isolateId) > 0)
		{
			m_manager.getFrame(0, isolateId).markStale();
		}
		else
		{
			DValue parent = m_manager.getValue(varId,isolateId);
			if (parent != null)
				parent.removeAllMembers();
		}

		return faultEvent;
	}

	/**
	 * Returns whether a variable ID is "real" or not.  For example,
	 * Value.THIS_ID is a "pseudo" varId, as are all the other special
	 * hard-coded varIds in the Value class.
	 */
	private boolean isPseudoVarId(long varId)
	{
		/*
		 * Unfortunately, this is actually just taking a guess.  The old code
		 * used "varId &lt; 0"; however, the Linux player sometimes has real
		 * variable IDs which are less than zero.
		 */
		return (varId < 0 && varId > -65535);
	}

	/**
	 * <code>memberName</code> might be just <code>"varname"</code>, or it
	 * might be <code>"namespace::varname"</code>, or it might be
	 * <code>"namespace@hexaddr::varname"</code>.  In the third case, it is
	 * fully resolved, and there is nothing we need to do.  But in the first
	 * and second cases, we may need to fully resolve it so that the Player
	 * will recognize it.
	 */
	private String getRawMemberName(long parentValueId, String memberName, int isolateId)
	{
		if (memberName != null)
		{
			DValue parent = m_manager.getValue(parentValueId, isolateId);
			if (parent != null)
			{
				int doubleColon = memberName.indexOf("::"); //$NON-NLS-1$
				String shortName = (doubleColon==-1) ? memberName : memberName.substring(doubleColon+2);
				DVariable member = parent.findMember(shortName);
				if (member != null)
					memberName = member.getRawName();
			}
		}
		return memberName;
	}

	/**
	 * @return null for success, or fault event if a setter in the player threw an exception
	 */
	private FaultEvent requestSetVariable(long id, String name, int t, String value, int isolateId) throws NoResponseException
	{
		// convert type to typeName
		String type = DVariable.typeNameFor(t);
		DMessage dm = buildOutSetMessage(id, name, type, value);
		dm.setTargetIsolate(isolateId);
		FaultEvent faultEvent = null;
//		System.out.println("setmsg id="+id+",name="+name+",t="+type+",value="+value);

		// make sure any exception during the setter gets held onto
		m_manager.beginPlayerCodeExecution(isolateId);

		// turn off squelch so we can hear the response
		sendSquelch(false, isolateId);

		int timeout = getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT);

		if (!simpleRequestResponseMessage(dm, (t == VariableType.STRING) ? DMessage.InSetVariable : DMessage.InSetVariable2, timeout))
			throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT));

		// turn it back on
		sendSquelch(true, isolateId);

		// tell manager we're done, and get exception if any
		faultEvent = m_manager.endPlayerCodeExecution(isolateId);

		// hammer the variable cache and context array
		m_manager.freeValueCache(isolateId);
		return faultEvent;
	}

	private DMessage buildOutSetMessage(long id, String name, String type, String v)
	{
		DMessage dm = DMessageCache.alloc(DMessage.getSizeofPtr()+
				DMessage.getStringLength(name)+
				DMessage.getStringLength(type)+
				DMessage.getStringLength(v)+
				3);
		dm.setType(DMessage.OutSetVariable);
		dm.putPtr(id);
		try { dm.putString(name); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); }
		try { dm.putString(type); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); }
		try { dm.putString(v); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); }
		return dm;
	}

	/*
	 * @see flash.tools.debugger.Session#waitForEvent()
	 */
	public void waitForEvent() throws NotConnectedException, InterruptedException
	{
		Object eventNotifier = m_manager.getEventNotifier();
		synchronized (eventNotifier)
		{
			while (getEventCount() == 0 && isConnected())
			{
				eventNotifier.wait();
			}
		}

		// We should NOT call isConnected() to test for a broken connection!  That
		// is because we may have received one or more events AND lost the connection,
		// almost simultaneously.  If there are any messages available for the
		// caller to process, we should not throw an exception.
		if (getEventCount() == 0 && !isConnected())
			throw new NotConnectedException();
	}

	/*
	 * @see flash.tools.debugger.Session#getEventCount()
	 */
	public int getEventCount()
	{
		return m_manager.getEventCount();
	}

	/*
	 * @see flash.tools.debugger.Session#nextEvent()
	 */
	public DebugEvent nextEvent()
	{
		return m_manager.nextEvent();
	}

	/**
	 * Adds a watchpoint on the given expression
	 * @throws NotConnectedException
	 * @throws NoResponseException
	 * @throws NotSupportedException
	 * @throws NotSuspendedException
	 */
	public boolean addWatch(long varId, String varName, int type, int tag, int isolateId) throws NoResponseException, NotConnectedException, NotSupportedException
	{
		// TODO check for NoResponse, NotConnected

		if (!supportsWatchpoints(isolateId))
			throw new NotSupportedException(PlayerSessionManager.getLocalizationManager().getLocalizedTextString("watchpointsNotSupported")); //$NON-NLS-1$

		varName = getRawMemberName(varId, varName, isolateId);
		DMessage dm = DMessageCache.alloc(4+DMessage.getSizeofPtr()+DMessage.getStringLength(varName)+1);
		dm.setType(DMessage.OutAddWatch2);
		dm.setTargetIsolate(isolateId);
		dm.putPtr(varId);
		try { dm.putString(varName); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); }
		dm.putWord(type);
		dm.putWord(tag);

		int timeout = getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT);
		boolean result = simpleRequestResponseMessage(dm, DMessage.InWatch2, timeout);
		return result;
	}

	/**
	 * Removes a watchpoint on the given expression
	 * @throws NotConnectedException
	 * @throws NoResponseException
	 * @throws NotSuspendedException
	 */
	public boolean removeWatch(long varId, String memberName, int isolateId) throws NoResponseException, NotConnectedException
	{
		memberName = getRawMemberName(varId, memberName, isolateId);
		DMessage dm = DMessageCache.alloc(DMessage.getSizeofPtr()+DMessage.getStringLength(memberName)+1);
		dm.setType(DMessage.OutRemoveWatch2);
		dm.putPtr(varId);
		try { dm.putString(memberName); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); }

		int timeout = getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT);
		boolean result = simpleRequestResponseMessage(dm, DMessage.InWatch2, timeout);
		return result;
	}

	/**
	 * Send a message that contains no data
	 */
	void sendMessage(int message)
	{
		DMessage dm = DMessageCache.alloc(0);
		dm.setType(message);
		sendMessage(dm);
	}
	
	/**
	 * Send a message that contains no data
	 */
	void sendMessageIsolate(int message, int isolateId)
	{
		DMessage dm = DMessageCache.alloc(0);
		dm.setTargetIsolate(isolateId);
		dm.setType(message);
		sendMessage(dm);
	}

	/**
	 * Send a fully formed message and release it when done
	 */
	synchronized void sendMessage(DMessage dm)
	{
		try
		{
			if (dm.getType() != DMessage.OutSetActiveIsolate) {
				int isolate = dm.getTargetIsolate();
				if (isolate != getActiveIsolate().getId()) {
					DMessage dm1 = DMessageCache.alloc(4);
					dm1.setTargetIsolate(isolate);
					dm1.setType(DMessage.OutSetActiveIsolate);
					dm1.putDWord(isolate);

					/* Use sendMessage here to avoid waiting for a response. 
					 * The assumption is that once the message is sent, subsequent
					 * messages are for that isolate regardless of the player confirming
					 * it. With this change, performance has improved considerably; player
					 * debugger has not gone out of sync since the ProcessTag messages
					 * flood issue was resolved. */
					sendMessage(dm1);

					m_manager.setActiveIsolate(m_manager.getIsolate(isolate));

				}
			}
			m_protocol.txMessage(dm);

			if (m_debugMsgOn || m_debugMsgFileOn)
				trace(dm, false);
		}
		catch(IOException io)
		{
			if (Trace.error)
			{
				Trace.trace("Attempt to send message "+dm.outToString()+" failed"); //$NON-NLS-1$ //$NON-NLS-2$
				io.printStackTrace();
			}
		}
		DMessageCache.free(dm);
	}

	/**
	 * Tell the player to shut-up
	 */
	boolean sendSquelch(boolean on, int isolateId)
	{
		boolean responded;
		DMessage dm = DMessageCache.alloc(4);
		dm.setType(DMessage.OutSetSquelch);
		dm.setTargetIsolate(isolateId);
		dm.putDWord( on ? 1 : 0);
		responded = simpleRequestResponseMessage(dm, DMessage.InSquelch);
		return responded;
	}

	void sendStopWarning()
	{
		// Currently, "disable_script_stuck_dialog" only works for AS2, not for AS3.
		String option = "disable_script_stuck_dialog"; //$NON-NLS-1$
		String value = "on"; //$NON-NLS-1$

		sendOptionMessage(option, value);

		// HACK: Completely disable the script-stuck notifications, so that we can
		// get AS3 debugging working.
		option = "disable_script_stuck"; //$NON-NLS-1$
		value = "on"; //$NON-NLS-1$

		sendOptionMessage(option, value);
	}

	void sendStopOnFault()
	{
		String option = "break_on_fault"; //$NON-NLS-1$
		String value = "on"; //$NON-NLS-1$

		sendOptionMessage(option, value);
	}

	void sendEnumerateOverride()
	{
		String option = "enumerate_override"; //$NON-NLS-1$
		String value = "on"; //$NON-NLS-1$

		sendOptionMessage(option, value);
	}

	void sendFailureNotify()
	{
		String option = "notify_on_failure"; //$NON-NLS-1$
		String value = "on"; //$NON-NLS-1$

		sendOptionMessage(option, value);
	}

	void sendInvokeSetters()
	{
		String option = "invoke_setters"; //$NON-NLS-1$
		String value = "on"; //$NON-NLS-1$

		sendOptionMessage(option, value);
	}

	void sendSwfloadNotify()
	{
		String option = "swf_load_messages"; //$NON-NLS-1$
		String value = "on"; //$NON-NLS-1$

		sendOptionMessage(option, value);
	}

	void sendConsoleErrorsAsTrace(boolean on)
	{
		String option = "console_errors"; //$NON-NLS-1$
		String value = (on) ? "on" : "off"; //$NON-NLS-1$ //$NON-NLS-2$

		sendOptionMessage(option, value);
	}

	void sendGetterTimeout()
	{
		String option = "getter_timeout"; //$NON-NLS-1$
		String value = "" + getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT); //$NON-NLS-1$

		sendOptionMessage(option, value);
	}

	void sendSetterTimeout()
	{
		String option = "setter_timeout"; //$NON-NLS-1$
		String value = "" + getPreference(SessionManager.PREF_SETVAR_RESPONSE_TIMEOUT); //$NON-NLS-1$

		sendOptionMessage(option, value);
	}
	
	void sendConcurrentDebugger()
	{
		String option = "concurrent_debugger"; //$NON-NLS-1$
		String value = "on"; //$NON-NLS-1$

		sendOptionMessage(option, value);
	}
	
	void sendWideLineDebugger()
	{
		String option = "wide_line_debugger"; //$NON-NLS-1$
		String value = "on"; //$NON-NLS-1$

		sendOptionMessage(option, value);
		m_manager.setWideLines(true);
	}

	void sendOptionMessage(String option, String value)
	{
		int msgSize = DMessage.getStringLength(option)+DMessage.getStringLength(value)+2;  // add 2 for trailing nulls of each string

		DMessage dm = DMessageCache.alloc(msgSize);
		dm.setType(DMessage.OutSetOption);
		try { dm.putString(option); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); }
		try { dm.putString(value); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); }
		simpleRequestResponseMessage(dm, DMessage.InOption);
	}

	public boolean supportsWatchpoints()
	{
		return supportsWatchpoints(Isolate.DEFAULT_ID);
	}
	
	public boolean supportsWatchpoints(int isolateId)
	{
		if (m_playerSupportsWatchpoints == null)
			m_playerSupportsWatchpoints = new Boolean(getOption("can_set_watchpoints", false, isolateId)); //$NON-NLS-1$
		return m_playerSupportsWatchpoints.booleanValue();
	}

	public boolean playerCanBreakOnAllExceptions()
	{
		return playerCanBreakOnAllExceptions(Isolate.DEFAULT_ID);
	}
	
	public boolean playerCanBreakOnAllExceptions(int isolateId)
	{
		if (m_playerCanBreakOnAllExceptions == null)
			m_playerCanBreakOnAllExceptions = new Boolean(getOption("can_break_on_all_exceptions", false, isolateId)); //$NON-NLS-1$
		return m_playerCanBreakOnAllExceptions.booleanValue();
	}
	
	public boolean supportsConcurrency(int isolateId)
	{
		if (m_playerSupportsConcurrency == null)
			m_playerSupportsConcurrency = new Boolean(getOption("concurrent_player", false, isolateId)); //$NON-NLS-1$
		return m_playerSupportsConcurrency.booleanValue();
	}
	
	public boolean supportsConcurrency()
	{
		return supportsConcurrency(Isolate.DEFAULT_ID);
	}
	
	public boolean supportsWideLineNumbers()
	{
		return supportsWideLineNumbers(Isolate.DEFAULT_ID);
	}
	
	public boolean supportsWideLineNumbers(int isolateId)
	{
		if (m_playerSupportsWideLine == null)
			m_playerSupportsWideLine = new Boolean(getOption("wide_line_player", false, isolateId)); //$NON-NLS-1$
		return m_playerSupportsWideLine.booleanValue();
	}

	public boolean playerCanTerminate()
	{
		return getOption("can_terminate", false, Isolate.DEFAULT_ID); //$NON-NLS-1$
	}

	public boolean playerCanCallFunctions()
	{
		return playerCanCallFunctions(Isolate.DEFAULT_ID);
	}
	
	public boolean playerCanCallFunctions(int isolateId)
	{
		if (m_playerCanCallFunctions == null)
			m_playerCanCallFunctions = new Boolean(getOption("can_call_functions", false, isolateId)); //$NON-NLS-1$
		return m_playerCanCallFunctions.booleanValue();
	}

	/**
	 * Returns the value of a Flash Player boolean option that was requested by
	 * OutGetOption and returned by InOption.
	 *
	 * @param optionName
	 *            the name of the option
	 * @return its value, or null
	 */
	public boolean getOption(String optionName, boolean defaultValue, int isolateId)
	{
		boolean retval = defaultValue;
		String optionValue = getOption(optionName, null, isolateId);

		if (optionValue != null)
			retval = Boolean.valueOf(optionValue).booleanValue();

		return retval;
	}

	/**
	 * Returns the value of a Flash Player string option that was requested by
	 * OutGetOption and returned by InOption.
	 *
	 * @param optionName
	 *            the name of the option
	 * @return its value, or null
	 */
	public String getOption(String optionName, String defaultValue, int isolateId)
	{
		String optionValue = defaultValue;

		int msgSize = DMessage.getStringLength(optionName)+1;  // add 1 for trailing null of string

		DMessage dm = DMessageCache.alloc(msgSize);
		dm.setTargetIsolate(isolateId);
		dm.setType(DMessage.OutGetOption);
		try { dm.putString(optionName); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); }
		if (simpleRequestResponseMessage(dm, DMessage.InOption))
			optionValue = m_manager.getOption(optionName);
		return optionValue;
	}
	
	long getMessageInCount(DMessageCounter counter, long isolate, int msgType) {
		if (isolate == Isolate.DEFAULT_ID) {
			return counter.getInCount(msgType);
		}
		else {
			return counter.getIsolateInCount(isolate, msgType);
		}
	}
	
	Object getMessageInLock(DMessageCounter counter, long isolate) {
		if (isolate == Isolate.DEFAULT_ID) {
			return counter.getInLock();
		}
		else {
			return counter.getIsolateInLock(isolate);
		}
	}

	/**
	 * Send our message and assume that the next response that is received is
	 * ours.  Primitive but there is no use in setting up a full request / response
	 * pattern since the player doesn't follow it.
	 *
	 * @return false is no response.
	 */
	boolean simpleRequestResponseMessage(DMessage msg, int msgType, int timeout)
	{
		boolean response = false;

		//FIXME: Check if timeout needs to adjust to the isolate switching
		// delay
		// use default or user supplied timeout
		timeout = (timeout > 0) ? timeout : getPreference(SessionManager.PREF_RESPONSE_TIMEOUT);

		// note the number of messages of this type before our send
		DMessageCounter msgCounter = getMessageCounter();
		int isolate = msg.getTargetIsolate();
		long num = getMessageInCount(msgCounter, isolate, msgType);
		long expect = num+1;

		// send the message
		sendMessage(msg);

		long startTime = System.currentTimeMillis();
//		System.out.println("sending- "+DMessage.outTypeName(msg.getType())+",timeout="+timeout+",start="+start);

		// now wait till we see a message come in
		m_incoming = false;
		synchronized (getMessageInLock(msgCounter, isolate))
		{
			while( (expect > getMessageInCount(msgCounter, isolate, msgType)) &&
					System.currentTimeMillis() < startTime + timeout &&
					isConnected())
			{
				// block until the message counter tells us that some message has been received
				try
				{
					getMessageInLock(msgCounter, isolate).wait(timeout);
				}
				catch (InterruptedException e)
				{
					// this should never happen
					e.printStackTrace();
					//FIXME: Will resetting the interrupted status here
					//cause any problems?
//		            Thread.currentThread().interrupt();
				}

				// if we see incoming messages, then we should reset our timeout
				synchronized (this)
				{
					if (m_incoming)
					{
						startTime = System.currentTimeMillis();
						m_incoming = false;
					}
				}
			}
		}

		if (getMessageInCount(msgCounter, isolate, msgType) >= expect)
			response = true;
		else if (timeout <= 0 && Trace.error)
			Trace.trace("Timed-out waiting for "+DMessage.inTypeName(msgType)+" response to message "+msg.outToString()); //$NON-NLS-1$ //$NON-NLS-2$

//		long endTime = System.currentTimeMillis();
//		System.out.println("    response- "+response+",timeout="+timeout+",elapsed="+(endTime-startTime));
		m_lastResponse = response;
		return response;
	}

	// use default timeout
	boolean simpleRequestResponseMessage(DMessage msg, int msgType) 	{ return simpleRequestResponseMessage(msg, msgType, -1); 	}
	
	boolean simpleRequestResponseMessageIsolate(DMessage msg, int msgType, int isolateId) 	{ 
		return simpleRequestResponseMessageIsolate(msg, msgType, -1, isolateId); 			
	}
	
	boolean simpleRequestResponseMessageIsolate(DMessage msg, int msgType, int timeout, int isolateId)
	{
		msg.setTargetIsolate(isolateId);
		return simpleRequestResponseMessage(msg, msgType, timeout);
	}
	
	boolean simpleRequestResponseMessage(int msg, int msgType)			{ return simpleRequestResponseMessage(msg, msgType, -1); 	}
	
	boolean simpleRequestResponseMessageIsolate(int msg, int msgType, int isolateId) { 
		return simpleRequestResponseMessageIsolate(msg, msgType, -1, isolateId);
	}

	boolean simpleRequestResponseMessageIsolate(int msg, int msgType, int timeout, int isolateId)
	{
		DMessage dm = DMessageCache.alloc(0);
		dm.setType(msg);
		dm.setTargetIsolate(isolateId);
		return simpleRequestResponseMessage(dm, msgType, timeout);
	}
	
	// Convenience function
	boolean simpleRequestResponseMessage(int msg, int msgType, int timeout)
	{
		DMessage dm = DMessageCache.alloc(0);
		dm.setType(msg);
		return simpleRequestResponseMessage(dm, msgType, timeout);
	}

	/**
	 * We register ourself as a listener to DMessages from the pipe for the
	 * sole purpose of monitoring the state of the debugger.  All other
	 * object management occurs with DManager
	 */
	/**
	 * Issued when the socket connection to the player is cut
	 */
	public void disconnected()
	{
		m_isHalted = false;
		m_isConnected = false;
		m_manager.disconnected();
	}

	/**
	 * This is the core routine for decoding incoming messages and deciding what should be
	 * done with them.  We have registered ourself with DProtocol to be notified when any
	 * incoming messages have been received.
	 *
	 * It is important to note that we should not rely on the contents of the message
	 * since it may be reused after we exit this method.
	 */
	public void messageArrived(DMessage msg, DProtocol which)
	{
		preMessageArrived(msg, which);
		msg.reset(); // allow the message to be re-parsed
		m_manager.messageArrived(msg, which);
		msg.reset(); // allow the message to be re-parsed
		postMessageArrived(msg, which);
	}

	/**
	 * Processes the message before it is passed to the DManager.
	 */
	private void preMessageArrived(DMessage msg, DProtocol which)
	{
		switch (msg.getType())
		{
		case DMessage.InIsolate:

				m_lastPreIsolate = (int)msg.getDWord();
			
			break;
		
			case DMessage.InAskBreakpoints:
			case DMessage.InBreakAt:
			case DMessage.InBreakAtExt:
			{
				// We need to set m_isHalted to true before the DManager processes
				// the message, because the DManager may add a BreakEvent to the
				// event queue, which the host debugger may immediately process;
				// if the debugger calls back to the Session, the Session must be
				// correctly marked as halted.
				if (m_lastPreIsolate == Isolate.DEFAULT_ID)
					m_isHalted = true;
				else
					updateHaltIsolateStatus(m_lastPreIsolate, true);
				break;
			}
		}
	}

	/**
	 * Processes the message after it has been passed to the DManager.
	 */
	private void postMessageArrived(DMessage msg, DProtocol which)
	{
		if (m_debugMsgOn || m_debugMsgFileOn)
			trace(msg, true);

		/* at this point we just open up a big switch statement and walk through all possible cases */
		int type = msg.getType();
		switch(type)
		{
			case DMessage.InExit:
			{
				m_isConnected = false;
				break;
			}

			case DMessage.InProcessTag:
			{
				// need to send a response to this message to keep the player going
				sendMessageIsolate(DMessage.OutProcessedTag, msg.getTargetIsolate());
				break;
			}

			case DMessage.InContinue:
			{
				if (msg.getTargetIsolate() == Isolate.DEFAULT_ID)
					m_isHalted = false;
				else {
					updateHaltIsolateStatus(msg.getTargetIsolate(), false);
				}
				break;
			}

			case DMessage.InOption:
			{
				//workers inherit options, so only store options
				//from main thread.
				if (msg.getTargetIsolate() == Isolate.DEFAULT_ID) {
					String s = msg.getString();
					String v = msg.getString();

					// add it to our properties, for DEBUG purposes only
					m_prefs.put(s, v);
				}
				break;
			}

			case DMessage.InSwfInfo:
			case DMessage.InScript:
			case DMessage.InRemoveScript:
			{
				//FIXME: Clear this cache only for affected
				//workers. Right now, the key contains worker
				//id, so we are safe. But we unnecessarily flush
				//the queue.
				m_evalIsAndInstanceofCache.clear();
				
				m_incoming = true;
				break;
			}

			default:
			{
				/**
				 * Simple indicator that we have received a message.  We
				 * put this indicator in default so that InProcessTag msgs
				 * wouldn't generate false triggers.  Mainly, we want to
				 * reset our timeout counter when we receive trace messages.
				 */
				m_incoming = true;
				break;
			}
		}

		// something came in so assume that we can now talk
		// to the player
		m_lastResponse = true;
	}

    private void updateHaltIsolateStatus(int targetIsolate, boolean value) {
    	if (!m_isolateStatus.containsKey(targetIsolate)) {
    		PlayerSessionIsolateStatus status = new PlayerSessionIsolateStatus();
    		status.m_isHalted = value;
    		m_isolateStatus.put(targetIsolate, status);    		
    	}
    	else {
    		m_isolateStatus.get(targetIsolate).m_isHalted = value;
    	}
	}

	/**
     * A background thread which wakes up periodically and fetches the SWF and SWD
     * from the Player for new movies that have loaded.  It then uses these to create
	 * an instance of MovieMetaData (a class shared with the Profiler) from which
	 * fdb can cull function names.
     * This work is done on a background thread because it can take several
     * seconds, and we want the fdb user to be able to execute other commands
     * while it is happening.
     */
    public void run()
    {
    	long last = 0;
    	while(isConnected())
    	{
    		// try every 250ms
    		try { Thread.sleep(250); } catch(InterruptedException ie) {}

    		try
    		{
    			// let's make sure that the traffic level is low before
    			// we do our requests.
    			long current = m_protocol.messagesReceived();
    			long delta = last - current;
    			last = current;

    			// if the last message that went out was not responded to
    			// or we are not suspended and have high traffic
    			// then wait for later.
    			if (!m_lastResponse || (!isSuspended() && delta > 5))
    				throw new NotSuspendedException();

    			// we are either suspended or low enough traffic

    			// get the list of swfs we have
    			for (Isolate isolate : m_manager.getIsolates()) {
    				int isolateId = isolate.getId();
    				if (isolateId != Isolate.DEFAULT_ID && !isWorkerSuspended(isolateId) && delta > 5) {
    					throw new NotSuspendedException();
    				}
    				int count = m_manager.getSwfInfoCount(isolateId);
    				for(int i=0; i<count; i++)
    				{
    					DSwfInfo info = m_manager.getSwfInfo(i, isolateId);

    					// no need to process if it's been removed
    					if (info == null || info.isUnloaded() || info.isPopulated() || (info.getVmVersion() > 0) )
    						continue;

    					// see if the swd has been loaded, throws exception if unable to load it.
    					// Also triggers a callback into the info object to freshen its contents
    					// if successful
    					//FIXME: remove sysout
    					info.getSwdSize(this);
    					// check since our vm version info could get updated in between.
    					if (info.getVmVersion() > 0)
    					{
    						// mark it populated if we haven't already done so
    						info.setPopulated();
    						continue;
    					}

    					// so by this point we know that we've got good swd data,
    					// or we've made too many attempts and gave up.
    					if (!info.isSwdLoading() && !info.isUnloaded())
    					{
    						// now load the swf, if we haven't already got it
    						if (info.getSwf() == null && !info.isUnloaded())
    							info.setSwf(requestSwf(i));

    						// only get the swd if we haven't got it
    						if (info.getSwd() == null && !info.isUnloaded())
    							info.setSwd(requestSwd(i));

    						try
    						{
    							// now go populate the functions tables...
    							if (!info.isUnloaded())
    								info.parseSwfSwd(m_manager);
    						}
    						catch(Exception e)
    						{
    							// oh this is not good and means that we should probably
    							// give up.
    							if (Trace.error)
    							{
    								Trace.trace("Error while parsing swf/swd '"+info.getUrl()+"'. Giving up and marking it processed"); //$NON-NLS-1$ //$NON-NLS-2$
    								e.printStackTrace();
    							}

    							info.setPopulated();
    						}
    					}
    				}
    			}
    		}
    		catch(InProgressException ipe)
    		{
    			// swd is still loading so give us a bit of
    			// time and then come back and try again
    		}
    		catch(NoResponseException nre)
    		{
    			// timed out on one of our requests so don't bother
    			// continuing right now,  try again later
    		}
    		catch(NotSuspendedException nse)
    		{
    			// probably want to wait until we are halted before
    			// doing this heavy action
    		}
    		catch(Exception e)
    		{
    			// maybe not good
    			if (Trace.error)
    			{
    				Trace.trace("Exception in background swf/swd processing thread"); //$NON-NLS-1$
    				e.printStackTrace();
    			}
    		}
    	}
    }

	byte[] requestSwf(int index) throws NoResponseException
	{
		/* send the message */
		int to = getPreference(SessionManager.PREF_SWFSWD_LOAD_TIMEOUT);
		byte[] swf = null;

		// the query
		DMessage dm = DMessageCache.alloc(2);
		dm.setType(DMessage.OutGetSwf);
		dm.putWord(index);

		if (simpleRequestResponseMessage(dm, DMessage.InGetSwf, to))
			swf = m_manager.getSWF();
		else
			throw new NoResponseException(to);

		return swf;
	}

	byte[] requestSwd(int index) throws NoResponseException
	{
		/* send the message */
		int to = getPreference(SessionManager.PREF_SWFSWD_LOAD_TIMEOUT);
		byte[] swd = null;

		// the query
		DMessage dm = DMessageCache.alloc(2);
		dm.setType(DMessage.OutGetSwd);
		dm.putWord(index);

		if (simpleRequestResponseMessage(dm, DMessage.InGetSwd, to))
			swd = m_manager.getSWD();
		else
			throw new NoResponseException(to);

		return swd;
	}

	//
	// Debug purposes only.  Dump contents of our messages to the screen
	// and/or file.
	//
	synchronized void trace(DMessage dm, boolean in)
	{
		try
		{
			if (m_debugMsgOn) {
				System.out.println( (in) ? dm.inToString(m_debugMsgSize) : dm.outToString(m_debugMsgSize) );
			}

			if (m_debugMsgFileOn)
			{
				traceFile().write( (in) ? dm.inToString(m_debugMsgFileSize) : dm.outToString(m_debugMsgFileSize) );
				m_trace.write(s_newline);
				m_trace.flush();
			}
		}
		catch(Exception e) {}
	}

	// i/o for tracing
    java.io.Writer m_trace;
	

	java.io.Writer traceFile() throws IOException
	{
		if (m_trace == null)
		{
			m_trace = new java.io.FileWriter("mm_debug_api_trace.txt"); //$NON-NLS-1$
			try { m_trace.write(new java.util.Date().toString()); } catch(Exception e) { m_trace.write("Date unknown"); } //$NON-NLS-1$
			try
			{
				m_trace.write(s_newline);

				// java properties dump
				java.util.Properties props = System.getProperties();
				props.list(new java.io.PrintWriter(m_trace));

				m_trace.write(s_newline);

				// property dump
				for (String key: m_prefs.keySet())
				{
					Object value = m_prefs.get(key);
					m_trace.write(key);
					m_trace.write(" = "); //$NON-NLS-1$
					m_trace.write(value.toString());
					m_trace.write(s_newline);
				}
			}
			catch(Exception e) { if (Trace.error) e.printStackTrace(); }
			m_trace.write(s_newline);
		}
		return m_trace;
	}

	public void setLaunchUrl(String url)
	{
		if (url.startsWith("/")) { //$NON-NLS-1$
			url = "file://" + url; //$NON-NLS-1$
		}
		m_launchUrl = url;
	}

	public void setAIRLaunchInfo(AIRLaunchInfo airLaunchInfo)
	{
		m_airLaunchInfo = airLaunchInfo;
	}

	public void breakOnCaughtExceptions(boolean b) throws NotSupportedException, NoResponseException {
		breakOnCaughtExceptions(b, Isolate.DEFAULT_ID);
	}
	
	public void breakOnCaughtExceptions(boolean b, int isolateId) throws NotSupportedException, NoResponseException {
		if (!playerCanBreakOnAllExceptions(isolateId))
			throw new NotSupportedException(PlayerSessionManager.getLocalizationManager().getLocalizedTextString("exceptionBreakpointsNotSupported")); //$NON-NLS-1$

		DMessage dm = DMessageCache.alloc(1);
		dm.setType(DMessage.OutPassAllExceptionsToDebugger);
		dm.putByte((byte)(b ? 1 : 0));
		dm.setTargetIsolate(isolateId);
		/* TODO: Verify that sendMessage below is a bug */
//		sendMessage(dm);
		if (!simpleRequestResponseMessage(dm, DMessage.InPassAllExceptionsToDebugger))
			throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT));
	}


	public boolean evalIs(Value value, Value type) throws PlayerDebugException, PlayerFaultException
	{
		return evalIsOrInstanceof(BinaryOp.Is, value, type, Isolate.DEFAULT_ID);
	}

	public boolean evalIs(Value value, String type) throws PlayerDebugException, PlayerFaultException
	{
		return evalIsOrInstanceof(BinaryOp.Is, value, type, Isolate.DEFAULT_ID);
	}

	public boolean evalInstanceof(Value value, Value type) throws PlayerDebugException, PlayerFaultException
	{
		return evalIsOrInstanceof(BinaryOp.Instanceof, value, type, Isolate.DEFAULT_ID);
	}

	public boolean evalInstanceof(Value value, String type) throws PlayerDebugException, PlayerFaultException
	{
		return evalIsOrInstanceof(BinaryOp.Instanceof, value, type, Isolate.DEFAULT_ID);
	}
	
	// isolate version
	
	public boolean evalIsWorker(Value value, Value type, int isolateId) throws PlayerDebugException, PlayerFaultException
	{
		return evalIsOrInstanceof(BinaryOp.Is, value, type, isolateId);
	}

	public boolean evalIsWorker(Value value, String type, int isolateId) throws PlayerDebugException, PlayerFaultException
	{
		return evalIsOrInstanceof(BinaryOp.Is, value, type, isolateId);
	}

	public boolean evalInstanceofWorker(Value value, Value type, int isolateId) throws PlayerDebugException, PlayerFaultException
	{
		return evalIsOrInstanceof(BinaryOp.Instanceof, value, type, isolateId);
	}

	public boolean evalInstanceofWorker(Value value, String type, int isolateId) throws PlayerDebugException, PlayerFaultException
	{
		return evalIsOrInstanceof(BinaryOp.Instanceof, value, type, isolateId);
	}

	private boolean evalIsOrInstanceof(BinaryOp op, Value value, Value type, int isolateId) throws PlayerDebugException, PlayerFaultException
	{
		String key = value.getTypeName() + " " + op + " " + type.getTypeName() + " " + String.valueOf(isolateId); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		Boolean retval = m_evalIsAndInstanceofCache.get(key);
		if (retval == null)
		{
			retval = new Boolean(ECMA.toBoolean(evalBinaryOp(op, value, type, isolateId)));
			m_evalIsAndInstanceofCache.put(key, retval);
		}

		return retval.booleanValue();
	}

	private boolean evalIsOrInstanceof(BinaryOp op, Value value, String type, int isolateId) throws PlayerDebugException, PlayerFaultException
	{
		String key = value.getTypeName() + " " + op + " " + type + " " + String.valueOf(isolateId); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		Boolean retval = m_evalIsAndInstanceofCache.get(key);
		if (retval == null)
		{
			Value typeval = getGlobalWorker(type, isolateId);
			if (typeval == null)
				retval = Boolean.FALSE;
			else
				retval = new Boolean(ECMA.toBoolean(evalBinaryOp(op, value, typeval, isolateId)));
			m_evalIsAndInstanceofCache.put(key, retval);
		}

		return retval.booleanValue();
	}

	public boolean evalIn(Value property, Value object) throws PlayerDebugException, PlayerFaultException
	{
		return ECMA.toBoolean(evalBinaryOp(BinaryOp.In, property, object, Isolate.DEFAULT_ID));
	}

	public Value evalAs(Value value, Value type) throws PlayerDebugException, PlayerFaultException {
		return evalBinaryOp(BinaryOp.As, value, type, Isolate.DEFAULT_ID);
	}

	private Value evalBinaryOp(BinaryOp op, Value lhs, Value rhs, int isolateId) throws PlayerDebugException, PlayerFaultException
	{
		if (!isWorkerSuspended(isolateId))
			throw new NotSuspendedException();

		if (!playerCanCallFunctions(isolateId))
		{
			Map<String,String> parameters = new HashMap<String,String>();
			parameters.put("operator", op.getName()); //$NON-NLS-1$
			String message = PlayerSessionManager.getLocalizationManager().getLocalizedTextString("operatorNotSupported", parameters); //$NON-NLS-1$
			throw new NotSupportedException(message);
		}

		int id = (int) (Math.random() * 65536); // good 'nuff
		DMessage dm = buildBinaryOpMessage(id, op, lhs, rhs);

		dm.setTargetIsolate(isolateId);
		m_manager.clearLastBinaryOp(isolateId);

		// make sure any exception gets held onto
		m_manager.beginPlayerCodeExecution(isolateId);

		// TODO wrong timeout
		int timeout = getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT);
		timeout += 500; // give the player enough time to raise its timeout exception

		boolean result = simpleRequestResponseMessage(dm, DMessage.InBinaryOp, timeout);

		// tell manager we're done; ignore returned FaultEvent
		m_manager.endPlayerCodeExecution(isolateId);

		if (!result)
			throw new NoResponseException(timeout);

		DVariable lastBinaryOp = m_manager.lastBinaryOp(isolateId);
		Value v;
		if (lastBinaryOp != null)
			v = lastBinaryOp.getValue();
		else
			v = DValue.forPrimitive(Value.UNDEFINED, isolateId);

		if (v.isAttributeSet(ValueAttribute.IS_EXCEPTION))
			throw new PlayerFaultException(new ExceptionFault(v.getValueAsString(), false, v, isolateId));

		return v;
	}

	private DMessage buildBinaryOpMessage(int id, BinaryOp op, Value lhs, Value rhs) {
		int messageSize = 5; // DWORD representing id + byte representing op
		String lhsType = DVariable.typeNameFor(lhs.getType());
		String lhsValueString = lhs.getValueAsString();
		String rhsType = DVariable.typeNameFor(rhs.getType());
		String rhsValueString = rhs.getValueAsString();
		messageSize += DMessage.getStringLength(lhsType)+1;
		messageSize += DMessage.getStringLength(lhsValueString)+1;
		messageSize += DMessage.getStringLength(rhsType)+1;
		messageSize += DMessage.getStringLength(rhsValueString)+1;

		DMessage dm = DMessageCache.alloc(messageSize);
		dm.setType(DMessage.OutBinaryOp);
		try
		{
			dm.putDWord(id);
			dm.putByte((byte) op.getValue());
			dm.putString(lhsType);
			dm.putString(lhsValueString);
			dm.putString(rhsType);
			dm.putString(rhsValueString);
		}
		catch(UnsupportedEncodingException uee)
		{
			// couldn't write out the string, so just terminate it and complete anyway
			dm.putByte((byte)'\0');
		}

		return dm;
	}

	/* (non-Javadoc)
	 * @see flash.tools.debugger.Session#getDisconnectCause()
	 */
	public Exception getDisconnectCause() {
		if (m_protocol != null)
			return m_protocol.getDisconnectCause();
		
		return null;
	}

	/* (non-Javadoc)
	 * @see flash.tools.debugger.Session#refreshIsolates()
	 */
	public Isolate[] refreshWorkers() throws NotSupportedException,
			NotSuspendedException, NoResponseException, NotConnectedException {
		if (!supportsConcurrency()) {
			throw new NotSupportedException(PlayerSessionManager.getLocalizationManager().getLocalizedTextString("concurrencyNotSupported")); //$NON-NLS-1$
		}
		if (!isSuspended())
			throw new NotSuspendedException();
		
		boolean response = simpleRequestResponseMessage(DMessage.OutIsolateEnumerate, DMessage.InIsolateEnumerate);
		
		if (response) {
			return m_manager.getIsolates();
		}
		return null;
	}

	private Isolate getActiveIsolate() {
		return m_manager.getActiveIsolate();
	}

	@Override
	public Isolate[] getWorkers() {
		return m_manager.getIsolates();
	}

	@Override
	public void resumeWorker(int isolateId) throws NotSuspendedException,
			NotConnectedException, NoResponseException {
		if (!isWorkerSuspended(isolateId))
			throw new NotSuspendedException();

		if (!simpleRequestResponseMessageIsolate(DMessage.OutContinue, DMessage.InContinue, isolateId))
			throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT));
	}

	@Override
	public int suspendReasonWorker(int isolateId) throws NotConnectedException {
		DSuspendInfo info = getSuspendInfoIsolate(isolateId);
		return info.getReason();
	}
	
	public DSuspendInfo getSuspendInfoIsolate(int isolateId)
	{
		DSuspendInfo info = m_manager.getSuspendInfo(isolateId);
		if (info == null)
		{
			if (simpleRequestResponseMessageIsolate(DMessage.OutGetBreakReason, DMessage.InBreakReason, isolateId))
				info = m_manager.getSuspendInfo(isolateId);

			// if we still can't get any info from the manager...
			if (info == null)
				info = new DSuspendInfo();  // empty unknown break information
		}
		return info;
	}

	@Override
	public void stepContinueWorker(int isolateId)
			throws NotSuspendedException, NoResponseException,
			NotConnectedException {
		if (!isWorkerSuspended(isolateId))
			throw new NotSuspendedException();

		// send a step-continue message and then wait for the Flash player to tell us that is has
		// resumed execution
		if (!simpleRequestResponseMessageIsolate(DMessage.OutStepContinue, DMessage.InContinue, isolateId))
			throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT));
	}

	@Override
	public Location setBreakpointWorker(int fileId, int lineNum, int isolateId)
			throws NoResponseException, NotConnectedException {
		/* send the message to the player and await a response */
		Location l = null;
		int bp = DLocation.encodeId(fileId, lineNum);
		int wideLineSize = 0;
		if (supportsWideLineNumbers())
			wideLineSize = 4;
		DMessage dm = DMessageCache.alloc(8 + wideLineSize);
		dm.setType(DMessage.OutSetBreakpoints);
		dm.putDWord(1);
		if (!supportsWideLineNumbers())
			dm.putDWord(bp);
		else {
			dm.putDWord(fileId);
			dm.putDWord(lineNum);
		}

		boolean gotResponse = simpleRequestResponseMessageIsolate(dm, DMessage.InSetBreakpoint, isolateId);
		if(gotResponse)
		{
			/* even though we think we got an answer check that the breakpoint was added */
			l = m_manager.getBreakpoint(bp, isolateId);
		}
		else
			throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT));

		return l;
	}

	@Override
	public Frame[] getFramesWorker(int isolateId) throws NotConnectedException {
		return m_manager.getFrames(isolateId);		
	}

	@Override
	public SwfInfo[] getSwfsWorker(int isolateId) throws NoResponseException {
		int swfCount = 0;
		swfCount = m_manager.getSwfInfoCount(isolateId);
		if (swfCount == 0)
		{
			// need to help out on the first one since the player
			// doesn't send it
			requestSwfInfo(0, isolateId);
		}
		//SwfInfo[] swfs = m_manager.getSwfInfos();
		
		ArrayList<SwfInfo> swfList = new ArrayList<SwfInfo>(); 

		for ( SwfInfo info : m_manager.getSwfInfos(isolateId) ) {
			swfList.add(info);
		}

		return swfList.toArray(new SwfInfo[0]);
	}

	@Override
	public void stepIntoWorker(int isolateId) throws NotSuspendedException,
			NoResponseException, NotConnectedException {
		if (isWorkerSuspended(isolateId))
		{
			// send a step-into message and then wait for the Flash player to tell us that is has
			// resumed execution
			if (!simpleRequestResponseMessageIsolate(DMessage.OutStepInto, DMessage.InContinue, isolateId))
				throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT));
		}
		else
			throw new NotSuspendedException();
		
	}

	@Override
	public void stepOutWorker(int isolateId) throws NotSuspendedException,
			NoResponseException, NotConnectedException {
		if (isWorkerSuspended(isolateId))
		{
			// send a step-out message and then wait for the Flash player to tell us that is has
			// resumed execution
			if (!simpleRequestResponseMessageIsolate(DMessage.OutStepOut, DMessage.InContinue, isolateId))
				throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT));
		}
		else
			throw new NotSuspendedException();
		
	}

	@Override
	public void stepOverWorker(int isolateId) throws NotSuspendedException,
			NoResponseException, NotConnectedException {
		if (isWorkerSuspended(isolateId))
		{
			// send a step-over message and then wait for the Flash player to tell us that is has
			// resumed execution
			if (!simpleRequestResponseMessageIsolate(DMessage.OutStepOver, DMessage.InContinue, isolateId))
				throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT));
		}
		else
			throw new NotSuspendedException();
		
	}

	public boolean evalInWorker(Value property, Value object, int isolateId) throws PlayerDebugException, PlayerFaultException
	{
		return ECMA.toBoolean(evalBinaryOp(BinaryOp.In, property, object, isolateId));
	}

	public Value evalAsWorker(Value value, Value type, int isolateId) throws PlayerDebugException, PlayerFaultException {
		return evalBinaryOp(BinaryOp.As, value, type, isolateId);
	}

	@Override
	public void suspendWorker(int isolateId) throws SuspendedException,
			NotConnectedException, NoResponseException {
		// send a halt message
		int wait = getPreference(SessionManager.PREF_SUSPEND_WAIT);
 		int every = 50; // wait this long for a response.  The lower the number the more aggressive we are!

		if (isWorkerSuspended(isolateId))
			throw new SuspendedException();

		while (!isWorkerSuspended(isolateId) && wait > 0)
		{
			simpleRequestResponseMessageIsolate(DMessage.OutStopDebug, DMessage.InBreakAtExt, every, isolateId);
			wait -= every;
		}

		if (!isWorkerSuspended(isolateId))
			throw new NoResponseException(wait);	
	}

	@Override
	public IsolateSession getWorkerSession(int isolateId) {
		if (m_isolateSessions.containsKey(isolateId)) {
			return m_isolateSessions.get(isolateId);
		}
		else {
			IsolateSession workerSession = new IsolatePlayerSession(isolateId, this);
			m_isolateSessions.put(isolateId, workerSession);
			return workerSession;
		}
		
	}
	
	public boolean setExceptionBreakpoint(String exceptionClass) throws NoResponseException, NotConnectedException {
		return setExceptionBreakpointWorker(exceptionClass, Isolate.DEFAULT_ID);
	}
	
	public boolean setExceptionBreakpointWorker(String exceptionClass, int isolateId) throws NoResponseException, NotConnectedException {
		int messageSize = DMessage.getStringLength(exceptionClass) + 1;
		
		DMessage dm = DMessageCache.alloc(messageSize);
		dm.setType(DMessage.OutSetExceptionBreakpoint);
		dm.setTargetIsolate(isolateId);
		try {
			dm.putString(exceptionClass);
		} catch (UnsupportedEncodingException e) {
			// couldn't write out the string, so just terminate it and complete anyway
			dm.putByte((byte)'\0');
		}

		boolean gotResponse = simpleRequestResponseMessageIsolate(dm, DMessage.InSetExceptionBreakpoint, isolateId);
		if(gotResponse)
		{
			return true;
		}
		else
			throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT));

	}
	
	@Override
	public boolean clearExceptionBreakpoint(String exceptionClass) throws NoResponseException, NotConnectedException {
		return clearExceptionBreakpointWorker(exceptionClass, Isolate.DEFAULT_ID);
	}

	@Override
	public boolean clearExceptionBreakpointWorker(String exceptionClass, int isolateId) throws NoResponseException, NotConnectedException {
		int messageSize = DMessage.getStringLength(exceptionClass) + 1;
		DMessage dm = DMessageCache.alloc(messageSize);
		dm.setType(DMessage.OutRemoveExceptionBreakpoint);
		dm.setTargetIsolate(isolateId);
		try {
			dm.putString(exceptionClass);
		} catch (UnsupportedEncodingException e) {
			// couldn't write out the string, so just terminate it and complete anyway
			dm.putByte((byte)'\0');
		}

		boolean gotResponse = simpleRequestResponseMessageIsolate(dm, DMessage.InRemoveExceptionBreakpoint, isolateId);
		if(gotResponse)
		{
			return true;
		}
		else
			throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT));

	}

	@Override
	public void setLauncher(ILauncher launcher) {
		this.launcher = launcher;
	}

}
