// 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:async';

import 'package:file/file.dart';
import 'package:file/src/io.dart' as io;
import 'package:meta/meta.dart';

import 'common.dart';
import 'mutable_recording.dart';
import 'recording_directory.dart';
import 'recording_file.dart';
import 'recording_file_system.dart';
import 'recording_link.dart';
import 'recording_proxy_mixin.dart';

/// [FileSystemEntity] implementation that records all invocation activity to
/// its file system's recording.
abstract class RecordingFileSystemEntity<T extends FileSystemEntity>
    extends RecordingProxyMixin implements FileSystemEntity {
  /// Creates a new `RecordingFileSystemEntity`.
  RecordingFileSystemEntity(this.fileSystem, this.delegate) {
    methods.addAll(<Symbol, Function>{
      #exists: delegate.exists,
      #existsSync: delegate.existsSync,
      #rename: _rename,
      #renameSync: _renameSync,
      #resolveSymbolicLinks: delegate.resolveSymbolicLinks,
      #resolveSymbolicLinksSync: delegate.resolveSymbolicLinksSync,
      #stat: delegate.stat,
      #statSync: delegate.statSync,
      #delete: _delete,
      #deleteSync: delegate.deleteSync,
      #watch: delegate.watch,
    });

    properties.addAll(<Symbol, Function>{
      #path: () => delegate.path,
      #uri: () => delegate.uri,
      #isAbsolute: () => delegate.isAbsolute,
      #absolute: _getAbsolute,
      #parent: _getParent,
      #basename: () => delegate.basename,
      #dirname: () => delegate.dirname,
    });
  }

  /// A unique entity id.
  final int uid = newUid();

  @override
  String get identifier => '$runtimeType@$uid';

  @override
  final RecordingFileSystemImpl fileSystem;

  @override
  MutableRecording get recording => fileSystem.recording;

  @override
  Stopwatch get stopwatch => fileSystem.stopwatch;

  /// The entity to which this entity delegates its functionality while
  /// recording.
  @protected
  final T delegate;

  /// Returns an entity with the same file system and same type as this
  /// entity but backed by the specified delegate.
  ///
  /// This base implementation checks to see if the specified delegate is the
  /// same as this entity's delegate, and if so, it returns this entity.
  /// Otherwise it returns `null`. Subclasses should override this method to
  /// instantiate the correct wrapped type only if this super implementation
  /// returns `null`.
  @protected
  @mustCallSuper
  T wrap(T delegate) => delegate == this.delegate ? this as T : null;

  /// Returns a directory with the same file system as this entity but backed
  /// by the specified delegate directory.
  @protected
  Directory wrapDirectory(io.Directory delegate) =>
      new RecordingDirectory(fileSystem, delegate);

  /// Returns a file with the same file system as this entity but backed
  /// by the specified delegate file.
  @protected
  File wrapFile(io.File delegate) => new RecordingFile(fileSystem, delegate);

  /// Returns a link with the same file system as this entity but backed
  /// by the specified delegate link.
  @protected
  Link wrapLink(io.Link delegate) => new RecordingLink(fileSystem, delegate);

  Future<T> _rename(String newPath) => delegate
      .rename(newPath)
      .then((io.FileSystemEntity entity) => wrap(entity as T));

  T _renameSync(String newPath) => wrap(delegate.renameSync(newPath) as T);

  Future<T> _delete({bool recursive: false}) => delegate
      .delete(recursive: recursive)
      .then((io.FileSystemEntity entity) => wrap(entity as T));

  T _getAbsolute() => wrap(delegate.absolute as T);

  Directory _getParent() => wrapDirectory(delegate.parent);
}
