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

// VMOptions=--resolve-dwarf-paths --save-debugging-info=$TEST_COMPILATION_DIR/debug.so

import "dart:async";
import "dart:io";

import 'package:expect/expect.dart';
import 'package:native_stack_traces/native_stack_traces.dart';
import 'package:path/path.dart' as path;

final dwarfPath = path.join(
  Platform.environment['TEST_COMPILATION_DIR']!,
  'debug.so',
);
final usesObfuscation = const String.fromEnvironment(
  "test_runner.configuration",
).contains('obfuscate');
final usesDwarf = const String.fromEnvironment(
  "test_runner.configuration",
).contains('dwarf');

Future<void> main(List<String> args) async {
  if (Platform.isAndroid) return;

  final List<String> stack = await run(6);
  final o = usesObfuscation ? '!' : '';

  compareFrames([
    '${o}bottom',
    'KeepClass.keepMethod',
    '${o}NormalClass.normalMethod',
    'keepStatic',
    '${o}normalStatic',
    '${o}run',
  ], stack);
}

void compareFrames(List<String> patterns, List<String> stack) {
  if (patterns.length != stack.length) {
    throw 'Expected ${patterns.length} frames';
  }
  print('Comparing this pattern: \n  ${patterns.join('\n  ')}');
  print('Against these frames: \n  ${stack.join('\n  ')}');
  for (int i = 0; i < patterns.length; ++i) {
    final pattern = patterns[i];
    final frame = stack[i];
    if (pattern.startsWith('!')) {
      Expect.notEquals(pattern.substring(1), frame);
    } else {
      Expect.equals(pattern, frame);
    }
  }
  print('');
}

@pragma('vm:never-inline')
void bottom() {
  throw 'bad';
}

@pragma('vm:keep-name')
class KeepClass {
  @pragma('vm:never-inline')
  @pragma('vm:keep-name')
  void keepMethod() {
    bottom();
  }
}

class NormalClass {
  @pragma('vm:never-inline')
  void normalMethod() {
    keep.keepMethod();
  }
}

final normal = NormalClass();
final keep = KeepClass();

@pragma('vm:never-inline')
@pragma('vm:keep-name')
void keepStatic() {
  normal.normalMethod();
}

@pragma('vm:never-inline')
void normalStatic() {
  keepStatic();
}

Future<List<String>> run(int n) async {
  try {
    normalStatic();
  } catch (e, s) {
    List<String> lines = s.toString().split('\n');
    if (usesDwarf) {
      final dwarf = Dwarf.fromFile(dwarfPath)!;
      lines = await Stream<String>.fromIterable(
        lines,
      ).transform(DwarfStackTraceDecoder(dwarf)).toList();
    }
    final start = lines.indexWhere((line) => line.startsWith('#0'));
    lines = lines.skip(start).take(n).toList();
    return lines.map((String line) {
      line = line.substring(line.indexOf(' ')).trim();
      line = line.substring(0, line.indexOf(' ')).trim();
      return line;
    }).toList();
  }
  throw 'failed';
}
