// Copyright 2013 The Flutter Authors. 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:isolate';
import 'dart:ui';

import 'package:test/test.dart';

const String kPortName = 'foobar';
const int kErrorCode = -1;
const int kStartCode = 0;
const int kCloseCode = 1;
const int kDeletedCode = 2;

void isolateSpawnEntrypoint(SendPort port) {
  void sendHelper(int code, [String message = '']) {
    port.send(<dynamic>[code, message]);
  }

  final SendPort shared = IsolateNameServer.lookupPortByName(kPortName);
  if (shared == null) {
    sendHelper(kErrorCode, 'Could not find port: $kPortName');
    return;
  }

  // ack that the SendPort lookup was successful.
  sendHelper(kStartCode);

  shared.send(kPortName);
  sendHelper(kCloseCode);

  // We'll fail if the ReceivePort's callback is called more than once. Try to
  // send another message to ensure we don't crash.
  shared.send('garbage');

  final bool result = IsolateNameServer.removePortNameMapping(kPortName);
  if (result) {
    sendHelper(kDeletedCode);
  } else {
    sendHelper(kErrorCode, 'Was unable to remove mapping for $kPortName');
  }
}

void main() {
  tearDown(() {
    IsolateNameServer.removePortNameMapping(kPortName);
  });

  test('simple isolate name server', () {
    // Mapping for 'foobar' isn't set. Check these cases to ensure correct
    // negative response.
    expect(IsolateNameServer.lookupPortByName(kPortName), isNull);
    expect(IsolateNameServer.removePortNameMapping(kPortName), isFalse);

    // Register a SendPort.
    final ReceivePort receivePort = ReceivePort();
    final SendPort sendPort = receivePort.sendPort;
    expect(IsolateNameServer.registerPortWithName(sendPort, kPortName), isTrue);
    expect(IsolateNameServer.lookupPortByName(kPortName), sendPort);

    // Check we can't register the same name twice.
    final ReceivePort receivePort2 = ReceivePort();
    final SendPort sendPort2 = receivePort2.sendPort;
    expect(
        IsolateNameServer.registerPortWithName(sendPort2, kPortName), isFalse);
    expect(IsolateNameServer.lookupPortByName(kPortName), sendPort);

    // Remove the mapping.
    expect(IsolateNameServer.removePortNameMapping(kPortName), isTrue);
    expect(IsolateNameServer.lookupPortByName(kPortName), isNull);

    // Ensure registering a new port with the old name returns the new port.
    expect(
        IsolateNameServer.registerPortWithName(sendPort2, kPortName), isTrue);
    expect(IsolateNameServer.lookupPortByName(kPortName), sendPort2);

    // Close so the test runner doesn't hang.
    receivePort.close();
    receivePort2.close();
  });

  test('isolate name server multi-isolate', () async {
    // Register our send port with the name server.
    final ReceivePort receivePort = ReceivePort();
    final SendPort sendPort = receivePort.sendPort;
    expect(IsolateNameServer.registerPortWithName(sendPort, kPortName), isTrue);

    // Test driver.
    final ReceivePort testReceivePort = ReceivePort();
    testReceivePort.listen(expectAsync1<void, dynamic>((dynamic response) {
      final int code = response[0];
      final String message = response[1];
      switch (code) {
        case kStartCode:
          break;
        case kCloseCode:
          receivePort.close();
          break;
        case kDeletedCode:
          expect(IsolateNameServer.lookupPortByName(kPortName), isNull);
          // Test is done, close the last ReceivePort.
          testReceivePort.close();
          break;
        case kErrorCode:
          throw message;
        default:
          throw 'UNREACHABLE';
      }
    }, count: 3));

    receivePort.listen(expectAsync1<void, dynamic>((dynamic message) {
      // If we don't get this message, we timeout and fail.
      expect(message, kPortName);
    }));

    // Run the test.
    await Isolate.spawn(isolateSpawnEntrypoint, testReceivePort.sendPort);
  });
}