// Copyright (c) 2017, 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:isolate';

import 'package:analyzer_plugin/protocol/protocol.dart';
import 'package:analyzer_plugin/protocol/protocol_generated.dart';
import 'package:analyzer_plugin/src/channel/isolate_channel.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

void main() {
  defineReflectiveTests(PluginIsolateChannelTest);
}

@reflectiveTest
class PluginIsolateChannelTest {
  late TestSendPort sendPort;
  late PluginIsolateChannel channel;

  void setUp() {
    sendPort = TestSendPort();
    channel = PluginIsolateChannel(sendPort);
  }

  void tearDown() {
    // If the test doesn't listen to the channel, then close will not cancel the
    // subscription and the process will not terminate.
    try {
      channel.listen((request) {});
    } catch (exception) {
      // Ignore the exception if the test has already registered a listener.
    }
    channel.close();
  }

  Future<void> test_close() async {
    var done = false;
    channel.listen((Request request) {}, onDone: () {
      done = true;
    });
    channel.close();
    await _pumpEventQueue();
    expect(done, isTrue);
  }

  Future<void> test_listen() async {
    var sentRequest = PluginShutdownParams().toRequest('5');
    Request? receivedRequest;
    channel.listen((Request request) {
      receivedRequest = request;
    });
    sendPort.receivePort?.send(sentRequest.toJson());
    await _pumpEventQueue(1);
    expect(receivedRequest, sentRequest);
  }

  void test_sendNotification() {
    var notification = PluginErrorParams(false, '', '').toNotification();
    channel.sendNotification(notification);
    expect(sendPort.sentMessages, hasLength(1));
    expect(sendPort.sentMessages[0], notification.toJson());
  }

  void test_sendResponse() {
    var response = PluginShutdownResult().toResponse('3', 1);
    channel.sendResponse(response);
    expect(sendPort.sentMessages, hasLength(1));
    expect(sendPort.sentMessages[0], response.toJson());
  }

  /// Returns a [Future] that completes after pumping the event queue [times]
  /// times. By default, this should pump the event queue enough times to allow
  /// any code to run, as long as it's not waiting on some external event.
  Future<void> _pumpEventQueue([int times = 5000]) {
    if (times == 0) return Future.value();
    // We use a delayed future to allow microtask events to finish. The
    // Future.value or Future() constructors use scheduleMicrotask themselves and
    // would therefore not wait for microtask callbacks that are scheduled after
    // invoking this method.
    return Future.delayed(Duration.zero, () => _pumpEventQueue(times - 1));
  }
}

/// A send port used in tests.
class TestSendPort implements SendPort {
  /// The receive port used to receive messages from the server.
  SendPort? receivePort;

  /// The messages sent to the server.
  List<Object> sentMessages = <Object>[];

  @override
  void send(message) {
    if (receivePort == null) {
      if (message is SendPort) {
        receivePort = message;
      } else {
        fail('Did not receive a receive port as the first communication.');
      }
    } else {
      sentMessages.add(message!);
    }
  }
}
