// Copyright (c) 2020, 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:convert";
import "dart:io";

import "package:expect/expect.dart";
import "package:path/path.dart" as path;

import "use_flag_test_helper.dart";

main(List<String> args) async {
  if (!isAOTRuntime) {
    return; // Running in JIT: AOT binaries not available.
  }

  if (Platform.isAndroid) {
    return; // SDK tree not available on the test device.
  }

  // These are the tools we need to be available to run on a given platform:
  if (!File(platformDill).existsSync()) {
    throw "Cannot run test as $platformDill does not exist";
  }
  if (!await testExecutable(genSnapshot)) {
    throw "Cannot run test as $genSnapshot not available";
  }

  sanitizedPartitioning(manifest) {
    // Filter core libraries, relativize URIs, and sort to make the results less
    // sensitive to compiler or test harness changes.
    print(manifest);
    var units = <List<String>>[];
    for (var unit in manifest['loadingUnits']) {
      var uris = <String>[];
      for (var uri in unit['libraries']) {
        if (uri.startsWith("dart:")) continue;
        uris.add(Uri.parse(uri).pathSegments.last);
      }
      uris.sort((a, b) => a.compareTo(b));
      units.add(uris);
    }
    units.sort((a, b) => a.first.compareTo(b.first));
    print(units);
    return units;
  }

  await withTempDir("incompatible-loading-unit-test", (String tempDir) async {
    final source1 = path.join(
      sdkDir,
      "runtime/tests/vm/dart/incompatible_loading_unit_1.dart",
    );
    final source2 = path.join(
      sdkDir,
      "runtime/tests/vm/dart/incompatible_loading_unit_2.dart",
    );
    final dill1 = path.join(tempDir, "incompatible_loading_unit_1.dart.dill");
    final dill2 = path.join(tempDir, "incompatible_loading_unit_2.dart.dill");
    final snapshot1 = path.join(tempDir, "incompatible_loading_unit_1.so");
    final snapshot2 = path.join(tempDir, "incompatible_loading_unit_2.so");
    final manifest1 = path.join(tempDir, "incompatible_loading_unit_1.txt");
    final manifest2 = path.join(tempDir, "incompatible_loading_unit_2.txt");
    final deferredSnapshot1 = snapshot1 + "-2.part.so";
    final deferredSnapshot2 = snapshot2 + "-2.part.so";

    // Compile source to kernel.
    await run(genKernel, <String>[
      "--aot",
      "--platform=$platformDill",
      "-o",
      dill1,
      source1,
    ]);
    await run(genKernel, <String>[
      "--aot",
      "--platform=$platformDill",
      "-o",
      dill2,
      source2,
    ]);

    // Compile kernel to ELF.
    await run(genSnapshot, <String>[
      if (Platform.isMacOS) ...[
        "--snapshot-kind=app-aot-macho-dylib",
        "--macho=$snapshot1",
      ] else ...[
        "--snapshot-kind=app-aot-elf",
        "--elf=$snapshot1",
      ],
      "--loading-unit-manifest=$manifest1",
      dill1,
    ]);
    var manifest = jsonDecode(await new File(manifest1).readAsString());
    Expect.equals(2, manifest["loadingUnits"].length);
    // Note package:expect doesn't do deep equals on collections.
    Expect.equals(
      "[[incompatible_loading_unit_1.dart],"
      " [incompatible_loading_unit_1_deferred.dart]]",
      sanitizedPartitioning(manifest).toString(),
    );
    Expect.isTrue(await new File(deferredSnapshot1).exists());

    await run(genSnapshot, <String>[
      if (Platform.isMacOS) ...[
        "--snapshot-kind=app-aot-macho-dylib",
        "--macho=$snapshot2",
      ] else ...[
        "--snapshot-kind=app-aot-elf",
        "--elf=$snapshot2",
      ],
      "--loading-unit-manifest=$manifest2",
      dill2,
    ]);
    manifest = jsonDecode(await new File(manifest2).readAsString());
    Expect.equals(2, manifest["loadingUnits"].length);
    Expect.equals(
      "[[incompatible_loading_unit_2.dart],"
      " [incompatible_loading_unit_2_deferred.dart]]",
      sanitizedPartitioning(manifest).toString(),
    );
    Expect.isTrue(await new File(deferredSnapshot2).exists());

    // Works when used normally.
    var lines = await runOutput(dartPrecompiledRuntime, <String>[snapshot1]);
    Expect.listEquals(["One!"], lines);

    lines = await runOutput(dartPrecompiledRuntime, <String>[snapshot2]);
    Expect.listEquals(["Two!"], lines);

    // Fails gracefully when mixing snapshot parts.
    await new File(deferredSnapshot2).rename(deferredSnapshot1);
    lines = await runError(dartPrecompiledRuntime, <String>[snapshot1]);
    Expect.equals(
      "DeferredLoadException: 'Deferred loading unit is from a different program than the main loading unit'",
      lines[1],
    );
  });
}
