// Copyright (c) 2019, 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.

// Benchmark of efficiency of grapheme cluster operations.

import 'package:characters/characters.dart';

import '../test/src/text_samples.dart';

double bench(int Function() action, int ms) {
  var elapsed = 0;
  var count = 0;
  var stopwatch = Stopwatch()..start();
  do {
    count += action();
    elapsed = stopwatch.elapsedMilliseconds;
  } while (elapsed < ms);
  return count / elapsed;
}

int iterateIndicesOnly() {
  var graphemeClusters = 0;
  var char = Characters(hangul).iterator;
  while (char.moveNext()) {
    graphemeClusters++;
  }
  char = Characters(genesis).iterator;
  while (char.moveNext()) {
    graphemeClusters++;
  }
  return graphemeClusters;
}

int iterateStrings() {
  var codeUnits = 0;
  var char = Characters(hangul).iterator;
  while (char.moveNext()) {
    codeUnits += char.current.length;
  }
  char = Characters(genesis).iterator;
  while (char.moveNext()) {
    codeUnits += char.current.length;
  }
  return codeUnits;
}

int reverseStrings() {
  var revHangul = reverse(hangul);
  var rev2Hangul = reverse(revHangul);
  if (hangul != rev2Hangul || hangul == revHangul) {
    throw AssertionError('Bad reverse');
  }
  var revGenesis = reverse(genesis);
  var rev2Genesis = reverse(revGenesis);
  if (genesis != rev2Genesis || genesis == revGenesis) {
    throw AssertionError('Bad reverse');
  }

  return (hangul.length + genesis.length) * 2;
}

int replaceStrings() {
  var count = 0;
  {
    const language = '한글';
    assert(language.length == 6);
    var chars = Characters(hangul);
    var replaced =
        chars.replaceAll(Characters(language), Characters('Hangul!'));
    count += replaced.string.length - hangul.length;
  }
  {
    var chars = Characters(genesis);
    var replaced = chars.replaceAll(Characters('And'), Characters('Also'));
    count += replaced.string.length - genesis.length;
  }
  return count;
}

String reverse(String input) {
  var chars = Characters(input);
  var buffer = StringBuffer();
  for (var it = chars.iteratorAtEnd; it.moveBack();) {
    buffer.write(it.current);
  }
  return buffer.toString();
}

void main(List<String> args) {
  var count = 1;
  if (args.isNotEmpty) count = int.tryParse(args[0]) ?? 1;

  // Warmup.
  bench(iterateIndicesOnly, 250);
  bench(iterateStrings, 250);
  bench(reverseStrings, 250);
  bench(replaceStrings, 250);

  var bestIterateIndices = 0.0;
  var bestIterateStrings = 0.0;
  var bestReverseStrings = 0.0;
  var bestReplaceStrings = 0.0;

  String toDigits(double d) {
    const n = 5;
    var s = d.round().toString();
    if (s.length >= n) return s;
    return d.toStringAsFixed(n - s.length);
  }

  for (var i = 0; i < count; i++) {
    var performance = bench(iterateIndicesOnly, 2000);
    print('Index Iteration: ${toDigits(performance)} gc/ms');
    if (performance > bestIterateIndices) bestIterateIndices = performance;

    performance = bench(iterateStrings, 2000);
    print('String Iteration: ${toDigits(performance)} cu/ms');
    if (performance > bestIterateStrings) bestIterateStrings = performance;

    performance = bench(reverseStrings, 2000);
    print('String Reversing: ${toDigits(performance)} cu/ms');
    if (performance > bestReverseStrings) bestReverseStrings = performance;

    performance = bench(replaceStrings, 2000);
    print('String Replacing: ${toDigits(performance)} changes/ms');
    if (performance > bestReplaceStrings) bestReplaceStrings = performance;
  }

  if (count > 1) {
    print('Best: ');
    print('Index Iteration: ${toDigits(bestIterateIndices)} gc/ms');
    print('String Iteration: ${toDigits(bestIterateStrings)} cu/ms');
    print('String Reversing: ${toDigits(bestReverseStrings)} cu/ms');
    print('String Replacing: ${toDigits(bestReplaceStrings)} changes/ms');
  }
}
