// Copyright (c) 2014, 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:stream_channel/stream_channel.dart';

import '../error_code.dart' as error_code;
import 'exception.dart';

typedef ZeroArgumentFunction = FutureOr Function();

/// A regular expression to match the exception prefix that some exceptions'
/// [Object.toString] values contain.
final _exceptionPrefix = RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): ');

/// Get a string description of an exception.
///
/// Many exceptions include the exception class name at the beginning of their
/// `toString`, so we remove that if it exists.
String getErrorMessage(Object error) =>
    error.toString().replaceFirst(_exceptionPrefix, '');

/// Like `try`/`finally`, run [body] and ensure that [whenComplete] runs
/// afterwards, regardless of whether [body] succeeded.
///
/// This is synchronicity-agnostic relative to [body]. If [body] returns a
/// [Future], this wil run asynchronously; otherwise it will run synchronously.
void tryFinally(dynamic Function() body, void Function() whenComplete) {
  dynamic result;
  try {
    result = body();
  } catch (_) {
    whenComplete();
    rethrow;
  }

  if (result is! Future) {
    whenComplete();
  } else {
    result.whenComplete(whenComplete);
  }
}

/// A transformer that silently drops [FormatException]s.
final ignoreFormatExceptions = StreamTransformer<Object?, Object?>.fromHandlers(
    handleError: (error, stackTrace, sink) {
  if (error is FormatException) return;
  sink.addError(error, stackTrace);
});

/// A transformer that sends error responses on [FormatException]s.
final StreamChannelTransformer<Object?, Object?> respondToFormatExceptions =
    _RespondToFormatExceptionsTransformer();

class _RespondToFormatExceptionsTransformer
    implements StreamChannelTransformer<Object?, Object?> {
  @override
  StreamChannel<Object?> bind(StreamChannel<Object?> channel) {
    return channel.changeStream((stream) {
      return stream.handleError((dynamic error) {
        final formatException = error as FormatException;
        var exception = RpcException(
            error_code.PARSE_ERROR, 'Invalid JSON: ${formatException.message}');
        channel.sink.add(exception.serialize(formatException.source));
      }, test: (error) => error is FormatException);
    });
  }
}
