// Copyright 2014 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 '../base/file_system.dart';
import '../globals.dart' as globals;

/// A class for representing depfile formats.
class Depfile {
  /// Create a [Depfile] from a list of [input] files and [output] files.
  const Depfile(this.inputs, this.outputs);

  /// Parse the depfile contents from [file].
  ///
  /// If the syntax is invalid, returns an empty [Depfile].
  factory Depfile.parse(File file) {
    final String contents = file.readAsStringSync();
    final List<String> colonSeparated = contents.split(': ');
    if (colonSeparated.length != 2) {
      globals.printError('Invalid depfile: ${file.path}');
      return const Depfile(<File>[], <File>[]);
    }
    final List<File> inputs = _processList(colonSeparated[1].trim());
    final List<File> outputs = _processList(colonSeparated[0].trim());
    return Depfile(inputs, outputs);
  }

  /// Parse the output of dart2js's used dependencies.
  ///
  /// The [file] contains a list of newline separated file URIs. The output
  /// file must be manually specified.
  factory Depfile.parseDart2js(File file, File output) {
    final List<File> inputs = <File>[];
    for (final String rawUri in file.readAsLinesSync()) {
      if (rawUri.trim().isEmpty) {
        continue;
      }
      final Uri fileUri = Uri.tryParse(rawUri);
      if (fileUri == null) {
        continue;
      }
      if (fileUri.scheme != 'file') {
        continue;
      }
      inputs.add(globals.fs.file(fileUri));
    }
    return Depfile(inputs, <File>[output]);
  }

  /// The input files for this depfile.
  final List<File> inputs;

  /// The output files for this depfile.
  final List<File> outputs;

  /// Given an [depfile] File, write the depfile contents.
  ///
  /// If either [inputs] or [outputs] is empty, ensures the file does not
  /// exist.
  void writeToFile(File depfile) {
    if (inputs.isEmpty || outputs.isEmpty) {
      if (depfile.existsSync()) {
        depfile.deleteSync();
      }
      return;
    }
    final StringBuffer buffer = StringBuffer();
    _writeFilesToBuffer(outputs, buffer);
    buffer.write(': ');
    _writeFilesToBuffer(inputs, buffer);
    depfile.writeAsStringSync(buffer.toString());
  }

  void _writeFilesToBuffer(List<File> files, StringBuffer buffer) {
    for (final File outputFile in files) {
      if (globals.platform.isWindows) {
        // Paths in a depfile have to be escaped on windows.
        final String escapedPath = outputFile.path.replaceAll(r'\', r'\\');
        buffer.write(' $escapedPath');
      } else {
        buffer.write(' ${outputFile.path}');
      }
    }
  }

  static final RegExp _separatorExpr = RegExp(r'([^\\]) ');
  static final RegExp _escapeExpr = RegExp(r'\\(.)');

  static List<File> _processList(String rawText) {
    return rawText
    // Put every file on right-hand side on the separate line
        .replaceAllMapped(_separatorExpr, (Match match) => '${match.group(1)}\n')
        .split('\n')
    // Expand escape sequences, so that '\ ', for example,ß becomes ' '
        .map<String>((String path) => path.replaceAllMapped(_escapeExpr, (Match match) => match.group(1)).trim())
        .where((String path) => path.isNotEmpty)
    // The tool doesn't write duplicates to these lists. This call is an attempt to
    // be resillient to the outputs of other tools which write or user edits to depfiles.
        .toSet()
        .map((String path) => globals.fs.file(path))
        .toList();
  }
}