// 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.

// Flags: --allow-natives-syntax

var Debug = debug.Debug;

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() {
    %LiveEditPatchScript(fun, Debug.scriptSource(fun).replace(from, to));
  }
  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;
    assertThrowsEquals(function() {
      patch(asyncfn, '\'Cat\'', '\'Capybara\'')
    }, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
  };
  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);

  %PerformMicrotaskCheckpoint();

  // 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})});
  assertThrowsEquals(function() {
    patch(asyncfn, '\'Capybara\'', '\'Tapir\'')
  }, 'LiveEdit failed: BLOCKED_BY_RUNNING_GENERATOR');
  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 = eval('((callback) => { callback(); return \'Cat\';})');
  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.
    assertThrowsEquals(function() {
      patch(fun_outside, '\'Cat\'', '\'Cobra\'')
    }, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
    // Patching inside an async function activation may succeed.
    patch(fun_inside, "'Cat'", "'Koala'");
  }
  result = fun_outside(() => asyncfn(function() {
    return fun_inside(attempt_fun_patches);
  }));
  assertEquals('Cat',
               fun_outside(function () {
                 assertEquals(result, 'Cat');
                 assertTrue(fun_patch_restarted);
                 assertTrue(fun_inside.toString().includes("'Koala'"));
               }));
})();

%PerformMicrotaskCheckpoint();
