// Copyright (c) 2013, 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:path/path.dart' as p;

import '../watcher.dart';

/// A wrapper for [ManuallyClosedWatcher] that encapsulates support for closing
/// the watcher when it has no subscribers and re-opening it when it's
/// re-subscribed.
///
/// It's simpler to implement watchers without worrying about this behavior.
/// This class wraps a watcher class which can be written with the simplifying
/// assumption that it can continue emitting events until an explicit `close`
/// method is called, at which point it will cease emitting events entirely. The
/// [ManuallyClosedWatcher] interface is used for these watchers.
///
/// This would be more cleanly implemented as a function that takes a class and
/// emits a new class, but Dart doesn't support that sort of thing. Instead it
/// takes a factory function that produces instances of the inner class.
abstract class ResubscribableWatcher implements Watcher {
  /// The factory function that produces instances of the inner class.
  final ManuallyClosedWatcher Function(String path) _factory;

  @override
  final String path;

  @override
  Stream<WatchEvent> get events => _eventsController.stream;
  late StreamController<WatchEvent> _eventsController;

  @override
  bool get isReady => _readyCompleter.isCompleted;

  @override
  Future<void> get ready => _readyCompleter.future;
  var _readyCompleter = Completer<void>();

  /// Creates a new [ResubscribableWatcher] wrapping the watchers
  /// emitted by [_factory].
  ResubscribableWatcher(String path, this._factory)
      : path = _normalizeAndStripTrailingSeparator(path) {
    late ManuallyClosedWatcher watcher;
    late StreamSubscription<WatchEvent> subscription;

    _eventsController = StreamController<WatchEvent>.broadcast(
        onListen: () async {
          final completer = _readyCompleter;
          watcher = _factory(this.path);
          subscription = watcher.events.listen(_eventsController.add,
              onError: _eventsController.addError,
              onDone: _eventsController.close);

          // It's important that we complete the value of [_readyCompleter] at
          // the time [onListen] is called, as opposed to the value when
          // [watcher.ready] fires. A new completer may be created by that time.
          await watcher.ready;
          completer.complete();
        },
        onCancel: () {
          // Cancel the subscription before closing the watcher so that the
          // watcher's `onDone` event doesn't close [events].
          subscription.cancel();
          watcher.close();
          _readyCompleter = Completer();
        },
        sync: true);
  }
}

/// An interface for watchers with an explicit, manual [close] method.
///
/// See [ResubscribableWatcher].
abstract class ManuallyClosedWatcher implements Watcher {
  /// Closes the watcher.
  ///
  /// Subclasses should close their [events] stream and release any internal
  /// resources.
  void close();
}

/// Normalizes [path] then strips any extra trailing separator.
///
/// Removes adjacent separators, `.` and `..` but does not convert to absolute.
String _normalizeAndStripTrailingSeparator(String path) {
  path = p.normalize(path);
  return path.length > 1 && path.endsWith(p.separator)
      ? path.substring(0, path.length - 1)
      : path;
}
