// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';

import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart';
import 'common/test_helper.dart';

// AUTOGENERATED START
//
// Update these constants by running:
//
// dart pkg/vm_service/test/update_line_numbers.dart pkg/vm_service/test/async_generator_breakpoint_test.dart
//
const LINE_A = 25;
const LINE_B = 30;
const LINE_C = 35;
const LINE_D = 39;
const LINE_E = 56;
// AUTOGENERATED END

void printSync() {
  print('sync'); // LINE_A
}

Future<void> printAsync() async {
  await null;
  print('async'); // LINE_B
}

Stream<void> printAsyncStar() async* {
  await null;
  print('async*'); // LINE_C
}

Iterable<void> printSyncStar() sync* {
  print('sync*'); // LINE_D
}

var testerReady = false;
Future<void> testeeDo() async {
  // We block here rather than allowing the isolate to enter the
  // paused-on-exit state before the tester gets a chance to set
  // the breakpoints because we need the event loop to remain
  // operational for the async bodies to run.
  print('testee waiting');
  while (!testerReady) {}

  printSync();
  final future = printAsync();
  final stream = printAsyncStar();
  final iterator = printSyncStar();

  print('middle'); // LINE_E

  unawaited(future);
  unawaited(stream.toList());
  iterator.toList();
}

Future testAsync(VmService service, IsolateRef isolateRef) async {
  final isolateId = isolateRef.id!;
  final isolate = await service.getIsolate(isolateId);
  final Library lib =
      (await service.getObject(isolateId, isolate.rootLib!.id!)) as Library;
  final script = lib.scripts![0];
  final scriptId = script.id!;

  final bp1 = await service.addBreakpoint(isolateId, scriptId, LINE_A);
  expect(bp1, isNotNull);

  final bp2 = await service.addBreakpoint(isolateId, scriptId, LINE_B);
  expect(bp2, isNotNull);

  final bp3 = await service.addBreakpoint(isolateId, scriptId, LINE_C);
  expect(bp3, isNotNull);

  final bp4 = await service.addBreakpoint(isolateId, scriptId, LINE_D);
  expect(bp4, isNotNull);

  final bp5 = await service.addBreakpoint(isolateId, scriptId, LINE_E);
  expect(bp5, isNotNull);

  final hits = <Breakpoint>[];
  await service.streamListen(EventStreams.kDebug);

  unawaited(
    service.evaluate(isolateId, lib.id!, 'testerReady = true').then(
      (Response result) async {
        final Obj res =
            await service.getObject(isolateId, (result as InstanceRef).id!);
        print(res);
        expect((res as Instance).valueAsString, equals('true'));
      },
    ),
  );

  final stream = service.onDebugEvent;
  await for (Event event in stream) {
    if (event.kind == EventKind.kPauseBreakpoint) {
      assert(event.pauseBreakpoints!.isNotEmpty);
      final bp = event.pauseBreakpoints!.first;
      hits.add(bp);
      await service.resume(isolateId);

      if (hits.length == 5) break;
    }
  }

  expect(hits, equals([bp1, bp5, bp4, bp2, bp3]));
}

final tests = <IsolateTest>[testAsync];

void main([args = const <String>[]]) => runIsolateTests(
      args,
      tests,
      'async_generator_breakpoint_test.dart',
      testeeConcurrent: testeeDo,
    );
