var spawn = require('child_process').spawn,
  path = require('path'),
  DebuggerClient = require('../../lib/DebuggerClient').DebuggerClient,
  stopDebuggerCallbacks = [];

function startDebugger(scriptPath, breakOnStart, done) {
  if (done === undefined) {
    done = breakOnStart;
    breakOnStart = false;
  }

  var testDir = path.dirname(__filename),
    debugPort = 61000,
    debugOption,
    child,
    debuggerClient,
    ignoreErrors = false;

  debugOption = '--debug' + (breakOnStart ? '-brk=' : '=');
  if (scriptPath.indexOf(path.sep) == -1)
    scriptPath = path.join(testDir, '..', 'fixtures', scriptPath);
  child = spawn('node', [debugOption + debugPort, scriptPath]);

  child.stderr.on('data', function(data) { process.stderr.write(data); });

  stopDebuggerCallbacks.push(function stopDebugger() {
    ignoreErrors = true;
    debuggerClient.close();
    child.kill();
  });

  function setupDebuggerClient() {
    debuggerClient = new DebuggerClient(debugPort);
    debuggerClient.connect();
    debuggerClient.on('connect', function() {
      injectTestHelpers(debuggerClient);
      done(child, debuggerClient);
    });
    debuggerClient.on('error', function(e) {
      if (!ignoreErrors)
        throw new Error('Debugger connection error: ' + e);
      if (e.code != 'ECONNRESET')
        console.warn('(warning) debugger connection error: ' + e);
    });
  }

  // give the child process some time to start up the debugger
  setTimeout(setupDebuggerClient, 200);
}

function runOnBreakInFunction(test) {
  stopAllDebuggers();
  startDebugger('BreakInFunction.js', function(childProcess, debuggerClient) {
    debuggerClient.once('break', function() {
      test(debuggerClient);
    });
    childProcess.stdin.write('go!\n');
  });
}

/** @param {function(DebuggerClient, string)} test */
function runInspectObject(test) {
  stopAllDebuggers();
  startDebugger('InspectObject.js', function(childProcess, debuggerClient) {
    debuggerClient.once('break', function() {
      debuggerClient.fetchObjectId(
        debuggerClient,
        'inspectedObject',
        function(id) {
          test(debuggerClient, id);
        }
      );
    });

    childProcess.stdin.write('go!\n');
  });
}

function runPeriodicConsoleLog(breakOnStart, test) {
  stopAllDebuggers();
  startDebugger(
    'PeriodicConsoleLog.js',
    breakOnStart,
    function(childProcess, debuggerClient) {
      test(childProcess, debuggerClient);
    }
  );
}

function stopAllDebuggers() {
  while (stopDebuggerCallbacks.length > 0)
    stopDebuggerCallbacks.shift()();
}

function stopAllDebugersAfterEachTest() {
  afterEach(stopAllDebuggers);
}

process.on('exit', function() {
  stopAllDebuggers();
});

function injectTestHelpers(debuggerClient) {
  debuggerClient.fetchObjectId =
    function(debuggerClient, expression, callback) {
      this.request(
        'evaluate',
        {
          expression: expression
        },
        function(err, response) {
          if (err) throw err;
          callback(String(response.handle));
        }
      );
    };
}

exports.startDebugger = startDebugger;
exports.runOnBreakInFunction = runOnBreakInFunction;
exports.runPeriodicConsoleLog = runPeriodicConsoleLog;
exports.stopAllDebuggers = stopAllDebuggers;
exports.stopAllDebuggersAfterEachTest = stopAllDebugersAfterEachTest;
exports.runInspectObject = runInspectObject;

@@ -686,48 +686,801 @@
 %0A%0A  
-child.stderr.on('data', function(data) %7B
+var startupTimer = setTimeout(function() %7B%0A    throw new Error('Timeout while waiting for the child process to initialize the debugger.');%0A  %7D, 1000);%0A%0A  child.stderr.on('data', function(data) %7B%0A    // Wait for the child process to initialize the debugger before connecting%0A    // Node v0.10 prints %22debugger listening...%22%0A    // Node v0.11 prints %22Debugger listening...%22%0A    if (/%5E%5BDd%5Debugger listening on port %5Cd+$/m.test(data.toString())) %7B%0A      clearTimeout(startupTimer);%0A      // give the child process some time to finish the initialization code%0A      // this is especially important when breakOnStart is true%0A      setTimeout(setupDebuggerClient, 200);%0A    %7D else %7B%0A      // Forward any error messages from the child process%0A      // to our stderr to make troubleshooting easier%0A     
  pro
@@ -1503,16 +1503,24 @@
 e(data);
+%0A    %7D%0A 
  %7D);%0A%0A  
@@ -2140,115 +2140,11 @@
 %7D);%0A
+
   %7D
-%0A%0A  // give the child process some time to start up the debugger%0A  setTimeout(setupDebuggerClient, 200);
 %0A%7D%0A%0A

fix race condition in launcher