// Copyright (c) 2024, 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:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:_fe_analyzer_shared/src/scanner/scanner.dart';

void main(List<String> args) {
  if (args.isEmpty || args.length > 2) {
    throw "Expected 1 or 2 argument: <dart file> and either "
        "--string or --bytes or --stringtobytes or --count";
  }
  File? f;
  ScanType scanType = ScanType.string;
  for (String arg in args) {
    if (arg == "--string") {
      scanType = ScanType.string;
    } else if (arg == "--bytes") {
      scanType = ScanType.bytes;
    } else if (arg == "--stringtobytes") {
      scanType = ScanType.stringAsBytes;
    } else if (arg == "--count") {
      scanType = ScanType.countLfs;
    } else if (arg.startsWith("--")) {
      throw "Unsupported setting: $arg";
    } else {
      f = new File(args[0]);
    }
  }
  if (f == null) {
    throw "No input file given.";
  }
  if (!f.existsSync()) {
    throw "File $f doesn't exist.";
  }
  String content = f.readAsStringSync();
  String contentZeroTerminated = content + '\x00';
  Uint8List contentBytes = f.readAsBytesSync();

  int numErrors = 0;
  Stopwatch stopwatch = new Stopwatch()..start();
  const int iterations = 1000;
  int lengthProcessed;
  bool hasErrors = false;

  switch (scanType) {
    case ScanType.string:
      lengthProcessed = content.length;
      for (int i = 0; i < iterations; i++) {
        hasErrors = scanString(
          content,
          configuration: new ScannerConfiguration(
            enableExtensionMethods: true,
            enableNonNullable: true,
            enableTripleShift: true,
          ),
          includeComments: true,
        ).hasErrors;
      }
    case ScanType.bytes:
      lengthProcessed = contentBytes.length;
      for (int i = 0; i < iterations; i++) {
        hasErrors = scan(
          contentBytes,
          configuration: new ScannerConfiguration(
            enableExtensionMethods: true,
            enableNonNullable: true,
            enableTripleShift: true,
          ),
          includeComments: true,
        ).hasErrors;
      }
    case ScanType.stringAsBytes:
      lengthProcessed = content.length;
      for (int i = 0; i < iterations; i++) {
        Uint8List tmp = utf8.encode(contentZeroTerminated);
        hasErrors = scan(
          tmp,
          configuration: new ScannerConfiguration(
            enableExtensionMethods: true,
            enableNonNullable: true,
            enableTripleShift: true,
          ),
          includeComments: true,
        ).hasErrors;
      }
    case ScanType.countLfs:
      lengthProcessed = contentBytes.length;
      for (int i = 0; i < iterations; i++) {
        hasErrors = false;
        int count = 0;
        for (int i = 0; i < contentBytes.length; i++) {
          if (contentBytes[i] == 10) count++;
        }
        // Make sure the above can't be optimized away.
        if (count == 42) print("Exactly 42 LFs");
      }
      if (hasErrors) {
        numErrors++;
      }
  }
  stopwatch.stop();
  print("Scanned $lengthProcessed ${scanType.what} $iterations times "
      "in ${stopwatch.elapsed}");
  print("Found errors $numErrors times");
  double lengthPerMicrosecond =
      (lengthProcessed * iterations) / stopwatch.elapsedMicroseconds;
  print("That's $lengthPerMicrosecond ${scanType.what} per microsecond");
  print("");
}

enum ScanType {
  string("string characters"),
  bytes("bytes"),
  stringAsBytes("string characters as bytes"),
  countLfs("bytes");

  final String what;

  const ScanType(this.what);
}
