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

// Test that validates that transferables are faster than regular typed data.

import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';

import "package:expect/expect.dart";

const int toIsolateSize = 100 * 1024 * 1024;
const int fromIsolateSize = 100 * 1024 * 1024;

const int nIterations = 5;

int iteration = -1;
bool keepTimerRunning = false;

main() async {
  keepTimerRunning = true;

  print('--- standard');
  iteration = nIterations;
  final stopwatch = new Stopwatch()..start();
  await runBatch(useTransferable: false);
  final standard = stopwatch.elapsedMilliseconds;

  print('--- transferable');
  iteration = nIterations;
  stopwatch.reset();
  await runBatch(useTransferable: true);
  final transferable = stopwatch.elapsedMilliseconds;
  print(
    'standard($standard ms)/transferable($transferable ms): ${standard / transferable}x',
  );
  keepTimerRunning = false;
}

packageList(Uint8List data, bool useTransferable) {
  return useTransferable
      ? TransferableTypedData.fromList(<Uint8List>[data])
      : data;
}

class StartMessage {
  final SendPort sendPort;
  final bool useTransferable;

  StartMessage(this.sendPort, this.useTransferable);
}

runBatch({required bool useTransferable}) async {
  Timer.run(idleTimer);
  final port = ReceivePort();
  final inbox = StreamIterator<dynamic>(port);
  final worker = await Isolate.spawn(
    isolateMain,
    StartMessage(port.sendPort, useTransferable),
    paused: true,
  );
  final workerCompleted = Completer<bool>();
  final workerExitedPort = ReceivePort()
    ..listen((_) => workerCompleted.complete(true));
  worker.addOnExitListener(workerExitedPort.sendPort);
  worker.resume(worker.pauseCapability!);

  await inbox.moveNext();
  final outbox = inbox.current;
  final workWatch = new Stopwatch();
  final data = new Uint8List(toIsolateSize);

  while (iteration-- > 0) {
    final packagedData = packageList(data, useTransferable);
    workWatch.start();
    outbox.send(packagedData);
    await inbox.moveNext();

    final received = inbox.current;
    final receivedData = received is TransferableTypedData
        ? received.materialize().asUint8List()
        : received;

    int time = workWatch.elapsedMilliseconds;
    print('${time}ms for round-trip');
    workWatch.reset();

    Expect.equals(data.length, receivedData.length);
  }
  outbox.send(null);

  await workerCompleted.future;
  workerExitedPort.close();
  port.close();
}

Future<Null> isolateMain(StartMessage startMessage) async {
  final port = new ReceivePort();
  final inbox = new StreamIterator<dynamic>(port);
  startMessage.sendPort.send(port.sendPort);
  final data = Uint8List.view(new Uint8List(fromIsolateSize).buffer);
  while (true) {
    await inbox.moveNext();
    final received = inbox.current;
    if (received == null) {
      break;
    }

    final packagedData = packageList(data, startMessage.useTransferable);

    startMessage.sendPort.send(packagedData);
  }
  port.close();
}

final Stopwatch idleWatch = new Stopwatch();

void idleTimer() {
  idleWatch.stop();
  final time = idleWatch.elapsedMilliseconds;
  if (time > 5) print('${time}ms since last checkin');
  idleWatch.reset();
  idleWatch.start();
  if (keepTimerRunning) {
    Timer.run(idleTimer);
  }
}
