// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

var Debug = debug.Debug;
var LiveEdit = Debug.LiveEdit;

unique_id = 0;

var AsyncFunction = (async function(){}).constructor;

function assertPromiseValue(value, promise) {
  promise.then(resolve => {
    went = true;
    if (resolve !== value) {
      print(`expected ${value} found ${resolve}`);
      quit(1);
    }
  }, reject => {
    print(`rejected ${reject}`);
    quit(1);
  });
}

function MakeAsyncFunction() {
  // Prevents eval script caching.
  unique_id++;
  return AsyncFunction('callback',
      "/* " + unique_id + "*/\n" +
      "await callback();\n" +
      "return 'Cat';\n");
}

function MakeFunction() {
  // Prevents eval script caching.
  unique_id++;
  return Function('callback',
      "/* " + unique_id + "*/\n" +
      "callback();\n" +
      "return 'Cat';\n");
}

// First, try MakeGenerator with no perturbations.
(function(){
  var asyncfn = MakeAsyncFunction();
  function callback() {};
  var promise = asyncfn(callback);
  assertPromiseValue('Cat', promise);
})();

function ExecuteInDebugContext(f) {
  var result;
  var exception = null;
  Debug.setListener(function(event) {
    if (event == Debug.DebugEvent.Break) {
      try {
        result = f();
      } catch (e) {
        // Rethrow this exception later.
        exception = e;
      }
    }
  });
  debugger;
  Debug.setListener(null);
  if (exception !== null) throw exception;
  return result;
}

function patch(fun, from, to) {
  function debug() {
    var log = new Array();
    var script = Debug.findScript(fun);
    var pos = script.source.indexOf(from);
    print(`pos ${pos}`);
    try {
      LiveEdit.TestApi.ApplySingleChunkPatch(script, pos, from.length, to,
                                             log);
    } finally {
      print("Change log: " + JSON.stringify(log) + "\n");
    }
  }
  ExecuteInDebugContext(debug);
}

// Try to edit a MakeAsyncFunction while it's running, then again while it's
// stopped.
(function(){
  var asyncfn = MakeAsyncFunction();

  var patch_attempted = false;
  function attempt_patch() {
    assertFalse(patch_attempted);
    patch_attempted = true;
    assertThrows(function() { patch(asyncfn, "'Cat'", "'Capybara'") },
                 LiveEdit.Failure);
  };
  var promise = asyncfn(attempt_patch);
  // Patch should not succeed because there is a live async function activation
  // on the stack.
  assertPromiseValue("Cat", promise);
  assertTrue(patch_attempted);

  %RunMicrotasks();

  // At this point one iterator is live, but closed, so the patch will succeed.
  patch(asyncfn, "'Cat'", "'Capybara'");
  promise = asyncfn(function(){});
  // Patch successful.
  assertPromiseValue("Capybara", promise);

  // Patching will fail however when an async function is suspended.
  var resolve;
  promise = asyncfn(function(){return new Promise(function(r){resolve = r})});
  assertThrows(function() { patch(asyncfn, "'Capybara'", "'Tapir'") },
               LiveEdit.Failure);
  resolve();
  assertPromiseValue("Capybara", promise);

  // Try to patch functions with activations inside and outside async
  // function activations.  We should succeed in the former case, but not in the
  // latter.
  var fun_outside = MakeFunction();
  var fun_inside = MakeFunction();
  var fun_patch_attempted = false;
  var fun_patch_restarted = false;
  function attempt_fun_patches() {
    if (fun_patch_attempted) {
      assertFalse(fun_patch_restarted);
      fun_patch_restarted = true;
      return;
    }
    fun_patch_attempted = true;
    // Patching outside an async function activation must fail.
    assertThrows(function() { patch(fun_outside, "'Cat'", "'Cobra'") },
                 LiveEdit.Failure);
    // Patching inside an async function activation may succeed.
    patch(fun_inside, "'Cat'", "'Koala'");
  }
  promise = asyncfn(function() { return fun_inside(attempt_fun_patches) });
  assertEquals('Cat',
               fun_outside(function () {
                 assertPromiseValue('Capybara', promise);
                 assertTrue(fun_patch_restarted);
                 assertTrue(fun_inside.toString().includes("'Koala'"));
               }));
})();

%RunMicrotasks();
