// 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:flutter/foundation.dart';

import '../common.dart';

const int _kNumIterations = 65536;
const int _kNumWarmUp = 100;
const int _kScale = 1000;

void main() {
  assert(false, "Don't run benchmarks in checked mode! Use 'flutter run --release'.");

  // In the following benchmarks, we won't remove the listeners when we don't
  // want to measure removeListener because we know that everything will be
  // GC'ed in the end.
  // Not removing listeners would cause memory leaks in a real application.

  final BenchmarkResultPrinter printer = BenchmarkResultPrinter();

  void runAddListenerBenchmark(int iteration, {bool addResult = true}) {
    const String name = 'add';
    for (int listenerCount = 1; listenerCount <= 5; listenerCount += 1) {
      final List<_Notifier> notifiers = List<_Notifier>.generate(
        iteration,
        (_) => _Notifier(),
        growable: false,
      );

      final Stopwatch watch = Stopwatch();
      watch.start();
      for (int i = 0; i < iteration; i += 1) {
        for (int l = 0; l < listenerCount; l += 1) {
          notifiers[i].addListener(() {});
        }
      }
      watch.stop();
      final int elapsed = watch.elapsedMicroseconds;
      final double averagePerIteration = elapsed / iteration;
      if (addResult)
        printer.addResult(
          description: '$name ($listenerCount listeners)',
          value: averagePerIteration * _kScale,
          unit: 'ns per iteration',
          name: '$name$listenerCount',
        );
    }
  }

  void runNotifyListenerBenchmark(int iteration, {bool addResult = true}) {
    const String name = 'notify';

    for (int listenerCount = 0; listenerCount <= 5; listenerCount += 1) {
      final _Notifier notifier = _Notifier();
      for (int i = 1; i <= listenerCount; i += 1) {
        notifier.addListener(() {});
      }
      final Stopwatch watch = Stopwatch();
      watch.start();
      for (int i = 0; i < iteration; i += 1) {
        notifier.notify();
      }
      watch.stop();
      final int elapsed = watch.elapsedMicroseconds;
      final double averagePerIteration = elapsed / iteration;
      if (addResult)
        printer.addResult(
          description: '$name ($listenerCount listeners)',
          value: averagePerIteration * _kScale,
          unit: 'ns per iteration',
          name: '$name$listenerCount',
        );
    }
  }

  void runRemoveListenerBenchmark(int iteration, {bool addResult = true}) {
    const String name = 'remove';
    final List<VoidCallback> listeners = <VoidCallback>[
      () {},
      () {},
      () {},
      () {},
      () {},
    ];
    for (int listenerCount = 1; listenerCount <= 5; listenerCount += 1) {
      final List<_Notifier> notifiers = List<_Notifier>.generate(
        iteration,
        (_) {
          final _Notifier notifier = _Notifier();
          for (int l = 0; l < listenerCount; l += 1) {
            notifier.addListener(listeners[l]);
          }
          return notifier;
        },
        growable: false,
      );

      final Stopwatch watch = Stopwatch();
      watch.start();
      for (int i = 0; i < iteration; i += 1) {
        for (int l = 0; l < listenerCount; l += 1) {
          notifiers[i].removeListener(listeners[l]);
        }
      }
      watch.stop();
      final int elapsed = watch.elapsedMicroseconds;
      final double averagePerIteration = elapsed / iteration;
      if (addResult)
        printer.addResult(
          description: '$name ($listenerCount listeners)',
          value: averagePerIteration * _kScale,
          unit: 'ns per iteration',
          name: '$name$listenerCount',
        );
    }
  }

  void runRemoveListenerWhileNotifyingBenchmark(int iteration,
      {bool addResult = true}) {
    const String name = 'removeWhileNotify';

    final List<VoidCallback> listeners = <VoidCallback>[
      () {},
      () {},
      () {},
      () {},
      () {},
    ];
    for (int listenerCount = 1; listenerCount <= 5; listenerCount += 1) {
      final List<_Notifier> notifiers = List<_Notifier>.generate(
        iteration,
        (_) {
          final _Notifier notifier = _Notifier();
          notifier.addListener(() {
            // This listener will remove all other listeners. So that only this
            // one is called and measured.
            for (int l = 0; l < listenerCount; l += 1) {
              notifier.removeListener(listeners[l]);
            }
          });
          for (int l = 0; l < listenerCount; l += 1) {
            notifier.addListener(listeners[l]);
          }
          return notifier;
        },
        growable: false,
      );

      final Stopwatch watch = Stopwatch();
      watch.start();
      for (int i = 0; i < iteration; i += 1) {
        notifiers[i].notify();
      }
      watch.stop();
      final int elapsed = watch.elapsedMicroseconds;
      final double averagePerIteration = elapsed / iteration;
      if (addResult)
        printer.addResult(
          description: '$name ($listenerCount listeners)',
          value: averagePerIteration * _kScale,
          unit: 'ns per iteration',
          name: '$name$listenerCount',
        );
    }
  }

  runAddListenerBenchmark(_kNumWarmUp, addResult: false);
  runAddListenerBenchmark(_kNumIterations);

  runNotifyListenerBenchmark(_kNumWarmUp, addResult: false);
  runNotifyListenerBenchmark(_kNumIterations);

  runRemoveListenerBenchmark(_kNumWarmUp, addResult: false);
  runRemoveListenerBenchmark(_kNumIterations);

  runRemoveListenerWhileNotifyingBenchmark(_kNumWarmUp, addResult: false);
  runRemoveListenerWhileNotifyingBenchmark(_kNumIterations);

  printer.printToStdout();
}

class _Notifier extends ChangeNotifier {
  void notify() => notifyListeners();
}
