// Copyright (c) 2018, 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:async' show Future;

import 'dart:io' show File;

import 'package:expect/expect.dart' show Expect;

import 'package:front_end/src/api_prototype/compiler_options.dart'
    show CompilerOptions;

import "package:front_end/src/api_prototype/memory_file_system.dart"
    show MemoryFileSystem;

import 'package:front_end/src/api_prototype/diagnostic_message.dart'
    show DiagnosticMessage, getMessageCodeObject;

import 'package:front_end/src/base/processed_options.dart'
    show ProcessedOptions;

import 'package:front_end/src/compute_platform_binaries_location.dart'
    show computePlatformBinariesLocation;

import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext;

import 'package:front_end/src/fasta/fasta_codes.dart'
    show
        Code,
        codeInitializeFromDillNotSelfContained,
        codeInitializeFromDillUnknownProblem;

import 'package:front_end/src/fasta/incremental_compiler.dart'
    show IncrementalCompiler;

import 'package:front_end/src/fasta/kernel/utils.dart' show serializeComponent;

import 'package:front_end/src/fasta/severity.dart' show Severity;

import 'package:kernel/kernel.dart' show Component;

import 'incremental_load_from_dill_test.dart' show getOptions;

Future<Null> main() async {
  Tester tester = new Tester();
  await tester.initialize();
  await tester.test();
}

class Tester {
  Uri sdkRoot;
  Uri base;
  Uri sdkSummary;
  Uri initializeFrom;
  Uri helperFile;
  Uri entryPoint;
  Uri platformUri;
  List<int> sdkSummaryData;
  List<DiagnosticMessage> errorMessages;
  List<DiagnosticMessage> warningMessages;
  MemoryFileSystem fs;
  CompilerOptions options;

  compileExpectInitializeFailAndSpecificWarning(
      Code expectedWarningCode) async {
    errorMessages.clear();
    warningMessages.clear();
    IncrementalCompiler compiler = new IncrementalCompiler(
        new CompilerContext(
            new ProcessedOptions(options: options, inputs: [entryPoint])),
        initializeFrom);
    await compiler.computeDelta();
    if (compiler.initializedFromDill) {
      Expect.fail("Expected to not be able to initialized from dill, but did.");
    }
    if (errorMessages.isNotEmpty) {
      Expect.fail("Got unexpected errors: " + joinMessages(errorMessages));
    }
    if (warningMessages.length != 1) {
      Expect.fail("Got unexpected errors: Expected one, got this: " +
          joinMessages(warningMessages));
    }
    if (getMessageCodeObject(warningMessages[0]) != expectedWarningCode) {
      Expect.fail("Expected ${expectedWarningCode.name} but got " +
          joinMessages(warningMessages));
    }
  }

  initialize() async {
    sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
    base = Uri.parse("org-dartlang-test:///");
    sdkSummary = base.resolve("vm_platform.dill");
    initializeFrom = base.resolve("initializeFrom.dill");
    helperFile = base.resolve("helper.dart");
    entryPoint = base.resolve("small.dart");
    platformUri = sdkRoot.resolve("vm_platform_strong.dill");
    sdkSummaryData = await new File.fromUri(platformUri).readAsBytes();
    errorMessages = <DiagnosticMessage>[];
    warningMessages = <DiagnosticMessage>[];
    fs = new MemoryFileSystem(base);
    options = getOptions(true);

    options.fileSystem = fs;
    options.sdkRoot = null;
    options.sdkSummary = sdkSummary;
    options.onDiagnostic = (DiagnosticMessage message) {
      if (message.severity == Severity.error) {
        errorMessages.add(message);
      } else if (message.severity == Severity.warning) {
        warningMessages.add(message);
      }
    };

    fs.entityForUri(sdkSummary).writeAsBytesSync(sdkSummaryData);
    fs.entityForUri(helperFile).writeAsStringSync("""
foo() {
    print("hello from foo");
}
""");
    fs.entityForUri(entryPoint).writeAsStringSync("""
import "helper.dart" as helper;
main() {
    helper.foo();
}
""");
  }

  Future<Null> test() async {
    IncrementalCompiler compiler = new IncrementalCompiler(
        new CompilerContext(
            new ProcessedOptions(options: options, inputs: [entryPoint])),
        initializeFrom);

    Component componentGood = await compiler.computeDelta();
    List<int> dataGood = serializeComponent(componentGood);
    fs.entityForUri(initializeFrom).writeAsBytesSync(dataGood);

    // Initialize from good dill file should be ok.
    compiler = new IncrementalCompiler(
        new CompilerContext(
            new ProcessedOptions(options: options, inputs: [entryPoint])),
        initializeFrom);
    compiler.invalidate(entryPoint);
    Component component = await compiler.computeDelta();
    if (!compiler.initializedFromDill) {
      Expect.fail(
          "Expected to have sucessfully initialized from dill, but didn't.");
    }
    if (errorMessages.isNotEmpty) {
      Expect.fail("Got unexpected errors: " + joinMessages(errorMessages));
    }
    if (warningMessages.isNotEmpty) {
      Expect.fail("Got unexpected warnings: " + joinMessages(warningMessages));
    }

    // Create a partial dill file.
    compiler.invalidate(entryPoint);
    component = await compiler.computeDelta();
    if (component.libraries.length != 1) {
      Expect.fail("Expected 1 library, got ${component.libraries.length}: "
          "${component.libraries}");
    }
    List<int> data = serializeComponent(component);
    fs.entityForUri(initializeFrom).writeAsBytesSync(data);

    // Initializing from partial dill should not be ok.
    await compileExpectInitializeFailAndSpecificWarning(
        codeInitializeFromDillNotSelfContained);

    // Create a invalid dill file to load from: Should not be ok.
    data = new List<int>.filled(42, 42);
    fs.entityForUri(initializeFrom).writeAsBytesSync(data);
    await compileExpectInitializeFailAndSpecificWarning(
        codeInitializeFromDillUnknownProblem);
  }
}

String joinMessages(List<DiagnosticMessage> messages) {
  return messages.map((m) => m.plainTextFormatted.join("\n")).join("\n");
}
