// Copyright (c) 2016, 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.

part of dart.developer;

/// Service protocol is the protocol that a client like the Observatory
/// could use to access the services provided by the Dart VM for
/// debugging and inspecting Dart programs. This class encapsulates the
/// version number and Uri for accessing this service.
final class ServiceProtocolInfo {
  /// The major version of the protocol. If the running Dart environment does
  /// not support the service protocol, this is 0.
  final int majorVersion = _getServiceMajorVersion();

  /// The minor version of the protocol. If the running Dart environment does
  /// not support the service protocol, this is 0.
  final int minorVersion = _getServiceMinorVersion();

  /// The Uri to connect to the debugger client hosted by the service. If the
  /// web server is not running, this will be null.
  final Uri? serverUri;

  /// The Uri to connect to the service via web socket. If the web server is
  /// not running, this will be null.
  @Since('2.14')
  Uri? get serverWebSocketUri {
    Uri? uri = serverUri;
    if (uri != null) {
      final pathSegments = <String>[];
      if (uri.pathSegments.isNotEmpty) {
        pathSegments.addAll(
          uri.pathSegments.where(
            // Strip out the empty string that appears at the end of path segments.
            // Empty string elements will result in an extra '/' being added to the
            // URI.
            (s) => s.isNotEmpty,
          ),
        );
      }
      pathSegments.add('ws');
      uri = uri.replace(scheme: 'ws', pathSegments: pathSegments);
    }
    return uri;
  }

  ServiceProtocolInfo(this.serverUri);

  String toString() {
    if (serverUri != null) {
      return 'Dart VM Service Protocol v$majorVersion.$minorVersion '
          'listening on $serverUri';
    } else {
      return 'Dart VM Service Protocol v$majorVersion.$minorVersion';
    }
  }
}

/// Access information about the service protocol and control the web server
/// that provides access to the services provided by the Dart VM for
/// debugging and inspecting Dart programs.
final class Service {
  /// Get information about the service protocol (version number and
  /// Uri to access the service).
  static Future<ServiceProtocolInfo> getInfo() async {
    // Port to receive response from service isolate.
    final RawReceivePort receivePort = new RawReceivePort(
      null,
      'Service.getInfo',
    );
    final Completer<String?> completer = new Completer<String?>();
    receivePort.handler = (String? uriString) => completer.complete(uriString);
    // Request the information from the service isolate.
    _getServerInfo(receivePort.sendPort);
    // Await the response from the service isolate.
    String? uriString = await completer.future;
    Uri? uri = uriString == null ? null : Uri.parse(uriString);
    // Close the port.
    receivePort.close();
    return new ServiceProtocolInfo(uri);
  }

  /// Control the web server that the service protocol is accessed through.
  /// [enable] is used as a toggle to enable or disable the web server
  /// servicing requests. If [silenceOutput] is provided and is true,
  /// the server will not output information to the console.
  static Future<ServiceProtocolInfo> controlWebServer({
    bool enable = false,
    bool? silenceOutput,
  }) async {
    // TODO: When NNBD is complete, delete the following line.
    ArgumentError.checkNotNull(enable, 'enable');
    // Port to receive response from service isolate.
    final RawReceivePort receivePort = new RawReceivePort(
      null,
      'Service.controlWebServer',
    );
    final Completer<String?> completer = new Completer<String?>();
    receivePort.handler = (String? uriString) => completer.complete(uriString);
    // Request the information from the service isolate.
    _webServerControl(receivePort.sendPort, enable, silenceOutput);
    // Await the response from the service isolate.
    String? uriString = await completer.future;
    Uri? uri = uriString == null ? null : Uri.parse(uriString);
    // Close the port.
    receivePort.close();
    return new ServiceProtocolInfo(uri);
  }

  /// Returns a [String] token representing the ID of [isolate].
  ///
  /// Returns null if the running Dart environment does not support the service
  /// protocol.
  ///
  /// To get the isolate id of the current isolate, pass [Isolate.current] as
  /// the [isolate] parameter.
  @Since('3.2')
  static String? getIsolateId(Isolate isolate) {
    // TODO: When NNBD is complete, delete the following line.
    ArgumentError.checkNotNull(isolate, 'isolate');
    return _getIsolateIdFromSendPort(isolate.controlPort);
  }

  /// Returns a [String] token representing the ID of [isolate].
  ///
  /// Returns null if the running Dart environment does not support the service
  /// protocol.
  @Deprecated("Use getIsolateId instead")
  static String? getIsolateID(Isolate isolate) {
    // TODO: When NNBD is complete, delete the following line.
    ArgumentError.checkNotNull(isolate, 'isolate');
    return _getIsolateIdFromSendPort(isolate.controlPort);
  }

  /// Returns a [String] token representing the ID of [object].
  ///
  /// Returns null if the running Dart environment does not support the service
  /// protocol.
  @Since('3.2')
  static String? getObjectId(Object object) {
    return _getObjectId(object);
  }
}

/// [sendPort] will receive a Uri or null.
external void _getServerInfo(SendPort sendPort);

/// [sendPort] will receive a Uri or null.
external void _webServerControl(
  SendPort sendPort,
  bool enable,
  bool? silenceOutput,
);

/// Returns the major version of the service protocol.
external int _getServiceMajorVersion();

/// Returns the minor version of the service protocol.
external int _getServiceMinorVersion();

/// Returns the service id for the isolate that owns [sendPort].
external String? _getIsolateIdFromSendPort(SendPort sendPort);

/// Returns the service id of [object].
external String? _getObjectId(Object object);
