// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:meta/meta.dart';

import '../base/logger.dart';

/// Utility class that can record time used in different phases of a test run.
class TestTimeRecorder {
  TestTimeRecorder(this.logger,
      {this.stopwatchFactory = const StopwatchFactory()})
      : _phaseRecords = List<TestTimeRecord>.generate(
          TestTimePhases.values.length,
          (_) => TestTimeRecord(stopwatchFactory),
        );

  final List<TestTimeRecord> _phaseRecords;
  final Logger logger;
  final StopwatchFactory stopwatchFactory;

  Stopwatch start(TestTimePhases phase) {
    return _phaseRecords[phase.index].start();
  }

  void stop(TestTimePhases phase, Stopwatch stopwatch) {
    _phaseRecords[phase.index].stop(stopwatch);
  }

  void print() {
    for (final TestTimePhases phase in TestTimePhases.values) {
      logger.printTrace(_getPrintStringForPhase(phase));
    }
  }

  @visibleForTesting
  List<String> getPrintAsListForTesting() {
    final List<String> result = <String>[];
    for (final TestTimePhases phase in TestTimePhases.values) {
      result.add(_getPrintStringForPhase(phase));
    }
    return result;
  }

  @visibleForTesting
  Stopwatch getPhaseWallClockStopwatchForTesting(final TestTimePhases phase) {
    return _phaseRecords[phase.index]._wallClockRuntime;
  }

  String _getPrintStringForPhase(final TestTimePhases phase) {
    assert(_phaseRecords[phase.index].isDone());
    return 'Runtime for phase ${phase.name}: ${_phaseRecords[phase.index]}';
  }
}

/// Utility class that can record time used in a specific phase of a test run.
class TestTimeRecord {
  TestTimeRecord(this.stopwatchFactory)
      : _wallClockRuntime = stopwatchFactory.createStopwatch();

  final StopwatchFactory stopwatchFactory;
  Duration _combinedRuntime = Duration.zero;
  final Stopwatch _wallClockRuntime;
  int _currentlyRunningCount = 0;

  Stopwatch start() {
    final Stopwatch stopwatch = stopwatchFactory.createStopwatch()..start();
    if (_currentlyRunningCount == 0) {
      _wallClockRuntime.start();
    }
    _currentlyRunningCount++;
    return stopwatch;
  }

  void stop(Stopwatch stopwatch) {
    _currentlyRunningCount--;
    if (_currentlyRunningCount == 0) {
      _wallClockRuntime.stop();
    }
    _combinedRuntime = _combinedRuntime + stopwatch.elapsed;
    assert(_currentlyRunningCount >= 0);
  }

  @override
  String toString() {
    return 'Wall-clock: ${_wallClockRuntime.elapsed}; combined: $_combinedRuntime.';
  }

  bool isDone() {
    return _currentlyRunningCount == 0;
  }
}

enum TestTimePhases {
  /// Covers entire TestRunner run, i.e. from the test runner was asked to `runTests` until it is done.
  ///
  /// This implicitly includes all the other phases.
  TestRunner,

  /// Covers time spent compiling, including subsequent copying of files.
  Compile,

  /// Covers time spent running, i.e. from starting the test device until the test has finished.
  Run,

  /// Covers all time spent collecting coverage.
  CoverageTotal,

  /// Covers collecting the coverage, but not parsing nor adding to hit map.
  ///
  /// This is included in [CoverageTotal]
  CoverageCollect,

  /// Covers parsing the json from the coverage collected.
  ///
  /// This is included in [CoverageTotal]
  CoverageParseJson,

  /// Covers adding the parsed coverage to the hitmap.
  ///
  /// This is included in [CoverageTotal]
  CoverageAddHitmap,

  /// Covers time spent in `collectCoverageData`, mostly formatting and writing coverage data to file.
  CoverageDataCollect,

  /// Covers time spend in the Watchers `handleFinishedTest` call. This is probably collecting coverage.
  WatcherFinishedTest,
}
