// Copyright 2013 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 'dart:convert' show LineSplitter;
import 'dart:io';

import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';

/// Returns all indexed fields in [className].
///
/// Field names are expected to be of the form `kFooBarIndex`; prefixed with a
/// `k` and terminated in `Index`.
List<String> getDartClassFields({
  required String sourcePath,
  required String className,
}) {
  final List<String> includedPaths = <String>[sourcePath];
  final AnalysisContextCollection collection = AnalysisContextCollection(includedPaths: includedPaths);
  final AnalysisContext context = collection.contextFor(sourcePath);
  final AnalysisSession session = context.currentSession;

  final SomeParsedUnitResult result = session.getParsedUnit(sourcePath);
  if (result is! ParsedUnitResult) {
    return <String>[];
  }

  // Locate all fields matching the expression in the class.
  final RegExp fieldExp = RegExp(r'_k(\w*)Index');
  final List<String> fields = <String>[];
  for (final CompilationUnitMember unitMember in result.unit.declarations) {
    if (unitMember is ClassDeclaration && unitMember.name.lexeme == className) {
      for (final ClassMember classMember in unitMember.members) {
        if (classMember is FieldDeclaration) {
          for (final VariableDeclaration field in classMember.fields.variables) {
            final String fieldName = field.name.lexeme;
            final RegExpMatch? match = fieldExp.firstMatch(fieldName);
            if (match != null) {
              fields.add(match.group(1)!);
            }
          }
        }
      }
    }
  }
  return fields;
}

/// Returns all values in [enumName].
///
/// Enum values are expected to be of the form `kEnumNameFooBar`; prefixed with
/// `kEnumName`.
List<String> getCppEnumValues({
  required String sourcePath,
  required String enumName,
}) {
  final List<String> lines = File(sourcePath).readAsLinesSync();
  final int enumEnd = lines.indexOf('} $enumName;');
  if (enumEnd < 0) {
    return <String>[];
  }
  final int enumStart = lines.lastIndexOf('typedef enum {', enumEnd);
  if (enumStart < 0 || enumStart >= enumEnd) {
    return <String>[];
  }
  final RegExp valueExp = RegExp('^\\s*k$enumName(\\w*)');
  return _extractMatchingExpression(
    lines: lines.sublist(enumStart + 1, enumEnd),
    regexp: valueExp,
  );
}

/// Returns all values in [enumName].
///
/// Enum values are expected to be of the form `kFooBar`; prefixed with `k`.
List<String> getCppEnumClassValues({
  required String sourcePath,
  required String enumName,
}) {
  final List<String> lines = _getBlockStartingWith(
    source: File(sourcePath).readAsStringSync(),
    startExp: RegExp('enum class $enumName .* {'),
  );
  final RegExp valueExp = RegExp(r'^\s*k(\w*)');
  return _extractMatchingExpression(lines: lines, regexp: valueExp);
}

/// Returns all values in [enumName].
///
/// Enum value declarations are expected to be of the form `FOO_BAR(1 << N)`;
/// in all caps.
List<String> getJavaEnumValues({
  required String sourcePath,
  required String enumName,
}) {
  final List<String> lines = _getBlockStartingWith(
    source: File(sourcePath).readAsStringSync(),
    startExp: RegExp('enum $enumName {'),
  );
  final RegExp valueExp = RegExp(r'^\s*([A-Z_]*)\(');
  return _extractMatchingExpression(lines: lines, regexp: valueExp);
}

/// Returns all values in [lines] whose line of code matches [regexp].
///
/// The contents of the first match group in [regexp] is returned; therefore
/// it must contain a match group.
List<String> _extractMatchingExpression({
  required Iterable<String> lines,
  required RegExp regexp,
}) {
  final List<String> values = <String>[];
  for (final String line in lines) {
    final RegExpMatch? match = regexp.firstMatch(line);
    if (match != null) {
      values.add(match.group(1)!);
    }
  }
  return values;
}

/// Returns all lines of the block starting with [startString].
///
/// [startString] MUST end with '{'.
List<String> _getBlockStartingWith({
  required String source,
  required RegExp startExp,
}) {
  assert(startExp.pattern.endsWith('{'));

  final int blockStart = source.indexOf(startExp);
  if (blockStart < 0) {
    return <String>[];
  }
  // Find start of block.
  int pos = blockStart;
  while (pos < source.length && source[pos] != '{') {
    pos++;
  }
  int braceCount = 1;

  // Count braces until end of block.
  pos++;
  while (pos < source.length && braceCount > 0) {
    if (source[pos] == '{') {
      braceCount++;
    } else if (source[pos] == '}') {
      braceCount--;
    }
    pos++;
  }
  final int blockEnd = pos;
  return LineSplitter.split(source, blockStart, blockEnd).toList();
}

/// Apply a visitor to all compilation units in the dart:ui library.
void visitUIUnits(String flutterRoot, AstVisitor<void> visitor) {
  final String uiRoot = '$flutterRoot/lib/ui';
  final FeatureSet analyzerFeatures = FeatureSet.latestLanguageVersion();
  final ParseStringResult uiResult = parseFile(path: '$uiRoot/ui.dart', featureSet: analyzerFeatures);
  for (final PartDirective part in uiResult.unit.directives.whereType<PartDirective>()) {
    final String partPath = part.uri.stringValue!;
    final ParseStringResult partResult = parseFile(path: '$uiRoot/$partPath', featureSet: analyzerFeatures);

    for (final CompilationUnitMember unitMember in partResult.unit.declarations) {
      unitMember.accept(visitor);
    }
  }
}
