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

import 'package:_fe_analyzer_shared/src/messages/codes.dart';
import 'package:_fe_analyzer_shared/src/parser/experimental_features.dart';
import 'package:_fe_analyzer_shared/src/parser/forwarding_listener.dart';
import 'package:_fe_analyzer_shared/src/parser/listener.dart';
import 'package:_fe_analyzer_shared/src/parser/parser_impl.dart';
import 'package:_fe_analyzer_shared/src/parser/stack_listener.dart';
import 'package:_fe_analyzer_shared/src/scanner/scanner.dart';

void main(List<String> args) {
  if (args.length != 1) {
    throw "Expected 1 argument: <dart file>.";
  }
  File f = new File(args.single);
  if (!f.existsSync()) {
    throw "File $f doesn't exist.";
  }
  Uint8List contentBytes = f.readAsBytesSync();
  ScannerResult scanResult = scan(
    contentBytes,
    configuration: new ScannerConfiguration(enableTripleShift: true),
    includeComments: true,
  );

  const int iterations = 1000;

  int countTokens = 0;
  {
    Token t = scanResult.tokens;
    while (!t.isEof) {
      countTokens++;
      t = t.next!;
    }
  }

  // TODO: Should we make 1000 copies of the tokens?
  // Or maybe scan 1000 times?
  /*
  List<Token> tokenStarts = [];
  tokenStarts.add(scanResult.tokens);
  while (tokenStarts.length < iterations) {
    tokenStarts.add(
      scan(
        contentBytes,
        configuration: new ScannerConfiguration(enableTripleShift: true),
        includeComments: true,
      ).tokens,
    );
  }
  */

  Listener listener = new NullListener();
  if (f.path == "42") {
    // Let's not let it optimize too many things away or do weird optimizations
    // that it can't do in practise.
    listener = new FooListener(f.uri);
  }

  int numErrors = 0;
  Stopwatch stopwatch = new Stopwatch()..start();
  int lengthProcessed = countTokens;

  for (int i = 0; i < iterations; i++) {
    Parser parser = new Parser(
      listener,
      experimentalFeatures: const DefaultExperimentalFeatures(),
    );
    Token after = parser.parseUnit(scanResult.tokens);
    if (!after.isEof) {
      throw "parsed returned before eof?!?";
    }
    if (listener is NullListener && listener.hasErrors) {
      numErrors++;
    }
    // Or maybe - maybe as an option - do another scan here?
  }

  stopwatch.stop();
  print(
    "Parsed $lengthProcessed tokens $iterations times "
    "in ${stopwatch.elapsed}",
  );
  print("Found errors $numErrors times");
  double lengthPerMicrosecond =
      (lengthProcessed * iterations) / stopwatch.elapsedMicroseconds;
  print("That's $lengthPerMicrosecond tokens per microsecond");
  print("");
}

class FooListener extends StackListener {
  List<Message> problems = [];

  @override
  final Uri uri;

  FooListener(this.uri);

  @override
  void addProblem(
    Message message,
    int charOffset,
    int length, {
    bool wasHandled = false,
    List<LocatedMessage>? context,
  }) {
    if (charOffset == 42) {
      throw "$message $charOffset $length $wasHandled $context";
    }
    problems.add(message);
  }

  @override
  Never internalProblem(Message message, int charOffset, Uri uri) {
    throw UnimplementedError();
  }

  @override
  bool get isDartLibrary => false;
}
