// Copyright (c) 2014, 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 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/context/builder.dart' show locateEmbedderYamlFor;
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer_testing/resource_provider_mixin.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(EmbedderSdkTest);
    defineReflectiveTests(FolderBasedDartSdkTest);
    defineReflectiveTests(SdkLibrariesReaderTest);
  });
}

@reflectiveTest
class EmbedderSdkTest with ResourceProviderMixin {
  final String foxLibPath = '/home/.pub-cache/fox/lib';

  void setUp() {
    newFolder(foxLibPath);
    newFile('/home/.pub-cache/fox/lib/_embedder.yaml', r'''
embedded_libs:
  "dart:deep": "deep/directory/file.dart"
  "dart:core" : "core/core.dart"
  "dart:fox": "slippy.dart"
''');
  }

  void test_allowedExperimentsJson() {
    var foxLib = getFolder(foxLibPath);
    var embedderYaml = locateEmbedderYamlFor(foxLib);
    EmbedderSdk sdk = EmbedderSdk.new2(
      resourceProvider,
      foxLib,
      embedderYaml,
      languageVersion: Version.parse('2.10.0'),
    );

    expect(sdk.allowedExperimentsJson, isNull);

    newFile('$foxLibPath/_internal/allowed_experiments.json', 'foo bar');
    expect(sdk.allowedExperimentsJson, 'foo bar');
  }

  void test_creation() {
    var foxLib = getFolder(foxLibPath);
    var embedderYaml = locateEmbedderYamlFor(foxLib);
    EmbedderSdk sdk = EmbedderSdk.new2(
      resourceProvider,
      foxLib,
      embedderYaml,
      languageVersion: Version.parse('2.10.0'),
    );

    expect(sdk.urlMappings, hasLength(3));
  }

  void test_fromFileUri() {
    var foxLib = getFolder(foxLibPath);
    var embedderYaml = locateEmbedderYamlFor(foxLib);
    EmbedderSdk sdk = EmbedderSdk.new2(
      resourceProvider,
      foxLib,
      embedderYaml,
      languageVersion: Version.parse('2.10.0'),
    );

    expectSource(String posixPath, String dartUri) {
      Uri uri = toUri(posixPath);
      var source = sdk.fromFileUri(uri)!;
      expect(source, isNotNull, reason: posixPath);
      expect(source.uri.toString(), dartUri);
      expect(source.fullName, getFile(posixPath).path);
    }

    expectSource('$foxLibPath/slippy.dart', 'dart:fox');
    expectSource('$foxLibPath/deep/directory/file.dart', 'dart:deep');
    expectSource('$foxLibPath/deep/directory/part.dart', 'dart:deep/part.dart');
  }

  void test_getSdkLibrary() {
    var foxLib = getFolder(foxLibPath);
    var embedderYaml = locateEmbedderYamlFor(foxLib);
    EmbedderSdk sdk = EmbedderSdk.new2(
      resourceProvider,
      foxLib,
      embedderYaml,
      languageVersion: Version.parse('2.10.0'),
    );

    var lib = sdk.getSdkLibrary('dart:fox')!;
    expect(lib, isNotNull);
    expect(lib.path, getFile('$foxLibPath/slippy.dart').path);
    expect(lib.shortName, 'dart:fox');
  }

  void test_mapDartUri() {
    var foxLib = getFolder(foxLibPath);
    var embedderYaml = locateEmbedderYamlFor(foxLib);
    EmbedderSdk sdk = EmbedderSdk.new2(
      resourceProvider,
      foxLib,
      embedderYaml,
      languageVersion: Version.parse('2.10.0'),
    );

    void expectSource(String dartUri, String posixPath) {
      var source = sdk.mapDartUri(dartUri)!;
      expect(source, isNotNull, reason: posixPath);
      expect(source.uri.toString(), dartUri);
      expect(source.fullName, getFile(posixPath).path);
    }

    expectSource('dart:core', '$foxLibPath/core/core.dart');
    expectSource('dart:fox', '$foxLibPath/slippy.dart');
    expectSource('dart:deep', '$foxLibPath/deep/directory/file.dart');
    expectSource('dart:deep/part.dart', '$foxLibPath/deep/directory/part.dart');
  }
}

@reflectiveTest
class FolderBasedDartSdkTest with ResourceProviderMixin {
  void test_creation() {
    FolderBasedDartSdk sdk = _createDartSdk();
    expect(sdk, isNotNull);
  }

  void test_fromFile_invalid() {
    FolderBasedDartSdk sdk = _createDartSdk();
    expect(sdk.fromFileUri(getFile("/not/in/the/sdk.dart").toUri()), isNull);
  }

  void test_fromFile_library() {
    FolderBasedDartSdk sdk = _createDartSdk();
    var source = sdk.fromFileUri(
      sdk.libraryDirectory
          .getChildAssumingFolder("core")
          .getChildAssumingFile("core.dart")
          .toUri(),
    )!;
    expect(source, isNotNull);
    expect(source.uri.toString(), "dart:core");
  }

  void test_fromFile_library_firstExact() {
    FolderBasedDartSdk sdk = _createDartSdk();
    Folder dirHtml = sdk.libraryDirectory.getChildAssumingFolder("html");
    Folder dirDartium = dirHtml.getChildAssumingFolder("dart2js");
    File file = dirDartium.getChildAssumingFile("html_dart2js.dart");
    var source = sdk.fromFileUri(file.toUri())!;
    expect(source, isNotNull);
    expect(source.uri.toString(), "dart:html");
  }

  void test_fromFile_library_html_common_dart2js() {
    FolderBasedDartSdk sdk = _createDartSdk();
    Folder dirHtml = sdk.libraryDirectory.getChildAssumingFolder("html");
    Folder dirCommon = dirHtml.getChildAssumingFolder("html_common");
    File file = dirCommon.getChildAssumingFile("html_common_dart2js.dart");
    var source = sdk.fromFileUri(file.toUri())!;
    expect(source, isNotNull);
    expect(source.uri.toString(), "dart:html_common");
  }

  void test_fromFile_part() {
    FolderBasedDartSdk sdk = _createDartSdk();
    var source = sdk.fromFileUri(
      sdk.libraryDirectory
          .getChildAssumingFolder("core")
          .getChildAssumingFile("num.dart")
          .toUri(),
    )!;
    expect(source, isNotNull);
    expect(source.uri.toString(), "dart:core/num.dart");
  }

  void test_getDirectory() {
    FolderBasedDartSdk sdk = _createDartSdk();
    Folder directory = sdk.directory;
    expect(directory, isNotNull);
    expect(directory.exists, isTrue);
  }

  void test_getDocDirectory() {
    FolderBasedDartSdk sdk = _createDartSdk();
    Folder directory = sdk.docDirectory;
    expect(directory, isNotNull);
  }

  void test_getLibraryDirectory() {
    FolderBasedDartSdk sdk = _createDartSdk();
    Folder directory = sdk.libraryDirectory;
    expect(directory, isNotNull);
    expect(directory.exists, isTrue);
  }

  void test_getSdkVersion() {
    FolderBasedDartSdk sdk = _createDartSdk();
    String version = sdk.sdkVersion;
    expect(version, isNotNull);
    expect(version.isNotEmpty, isTrue);
  }

  /// The "part" format should result in the same source as the non-part format
  /// when the file is the library file.
  void test_mapDartUri_partFormatForLibrary() {
    FolderBasedDartSdk sdk = _createDartSdk();
    var normalSource = sdk.mapDartUri('dart:core')!;
    var partSource = sdk.mapDartUri('dart:core/core.dart')!;
    expect(partSource, normalSource);
  }

  FolderBasedDartSdk _createDartSdk() {
    Folder sdkDirectory = getFolder('/sdk');
    _createFile(sdkDirectory, [
      'lib',
      '_internal',
      'sdk_library_metadata',
      'lib',
      'libraries.dart',
    ], content: _librariesFileContent());
    _createFile(sdkDirectory, ['bin', 'dart']);
    _createFile(sdkDirectory, ['bin', 'dart2js']);
    _createFile(sdkDirectory, ['lib', 'async', 'async.dart']);
    _createFile(sdkDirectory, ['lib', 'core', 'core.dart']);
    _createFile(sdkDirectory, ['lib', 'core', 'num.dart']);
    _createFile(sdkDirectory, [
      'lib',
      'html',
      'html_common',
      'html_common.dart',
    ]);
    _createFile(sdkDirectory, [
      'lib',
      'html',
      'html_common',
      'html_common_dart2js.dart',
    ]);
    _createFile(sdkDirectory, ['lib', 'html', 'dart2js', 'html_dart2js.dart']);
    return FolderBasedDartSdk(resourceProvider, sdkDirectory);
  }

  void _createFile(
    Folder directory,
    List<String> segments, {
    String content = '',
  }) {
    Folder parent = directory;
    int last = segments.length - 1;
    for (int i = 0; i < last; i++) {
      parent = parent.getChildAssumingFolder(segments[i]);
    }
    File file = parent.getChildAssumingFile(segments[last]);
    newFile(file.path, content);
  }

  String _librariesFileContent() => '''
final Map<String, LibraryInfo> LIBRARIES = const <String, LibraryInfo> {
  "async": const LibraryInfo(
      "async/async.dart",
      categories: "Client,Server",
      maturity: Maturity.STABLE,
      dart2jsPatchPath: "_internal/js_runtime/lib/async_patch.dart"),

  "core": const LibraryInfo(
      "core/core.dart",
      categories: "Client,Server,Embedded",
      maturity: Maturity.STABLE,
      dart2jsPatchPath: "_internal/js_runtime/lib/core_patch.dart"),

  "html": const LibraryInfo(
      "html/dart2js/html_dart2js.dart",
      categories: "Client",
      maturity: Maturity.WEB_STABLE),

  "html_common": const LibraryInfo(
      "html/html_common/html_common.dart",
      categories: "Client",
      maturity: Maturity.WEB_STABLE,
      dart2jsPath: "html/html_common/html_common_dart2js.dart",
      documented: false,
      implementation: true),
};
''';
}

@reflectiveTest
class SdkLibrariesReaderTest with ResourceProviderMixin {
  void test_readFrom_empty() {
    LibraryMap libraryMap = SdkLibrariesReader().readFromFile(
      getFile("/libs.dart"),
      "",
    );
    expect(libraryMap, isNotNull);
    expect(libraryMap.size(), 0);
  }

  void test_readFrom_normal() {
    LibraryMap libraryMap = SdkLibrariesReader().readFromFile(
      getFile("/libs.dart"),
      r'''
final Map<String, LibraryInfo> LIBRARIES = const <String, LibraryInfo> {
  'first' : const LibraryInfo(
    'first/first.dart',
    categories: 'Client',
    documented: true,
    platforms: VM_PLATFORM),

  'second' : const LibraryInfo(
    'second/second.dart',
    categories: 'Server',
    documented: false,
    implementation: true,
    platforms: 0),
};''',
    );
    expect(libraryMap, isNotNull);
    expect(libraryMap.size(), 2);
    var first = libraryMap.getLibrary("dart:first")!;
    expect(first, isNotNull);
    expect(first.path, "first/first.dart");
    expect(first.shortName, "dart:first");
    expect(first.isDocumented, true);
    expect(first.isImplementation, false);
    var second = libraryMap.getLibrary("dart:second")!;
    expect(second, isNotNull);
    expect(second.path, "second/second.dart");
    expect(second.shortName, "dart:second");
    expect(second.isDocumented, false);
    expect(second.isImplementation, true);
  }
}
