// Copyright (c) 2017, 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.

library fasta.compiler_context;

import 'dart:async' show Future, Zone, runZoned;

import 'package:kernel/ast.dart' show Source;

import '../api_prototype/compiler_options.dart' show CompilerOptions;

import '../api_prototype/file_system.dart' show FileSystem;

import '../base/processed_options.dart' show ProcessedOptions;

import 'scanner/token.dart' show StringToken;

import 'command_line_reporting.dart' as command_line_reporting;

import 'colors.dart' show computeEnableColors;

import 'fasta_codes.dart'
    show LocatedMessage, Message, messageInternalProblemMissingContext;

import 'severity.dart' show Severity;

final Object compilerContextKey = new Object();

/// Shared context used throughout the compiler.
///
/// The compiler works with a single instance of this class. To avoid
/// passing it around as an argument everywhere, it is stored as a zone-value.
///
/// For convenience the static getter [CompilerContext.current] retrieves the
/// context stored in the current zone.
class CompilerContext {
  // TODO(sigmund): Move here any method in ProcessedOptions that doesn't seem
  // appropriate as an "option", or consider merging ProcessedOptions entirely
  // within this class, and depend only on the raw options here.
  final ProcessedOptions options;

  /// Sources seen by the compiler.
  ///
  /// This is populated as the compiler reads files, and it is used for error
  /// reporting and to generate source location information in the compiled
  /// programs.
  final Map<Uri, Source> uriToSource = <Uri, Source>{};

  final List<Object> errors = <Object>[];

  final List<Uri> dependencies = <Uri>[];

  FileSystem get fileSystem => options.fileSystem;

  bool enableColorsCached = null;

  CompilerContext(this.options);

  void disableColors() {
    enableColorsCached = false;
  }

  /// Report [message], for example, by printing it.
  void report(LocatedMessage message, Severity severity,
      {List<LocatedMessage> context}) {
    options.report(message, severity, context: context);
  }

  /// Report [message], for example, by printing it.
  // TODO(askesc): Remove this and direct callers directly to report.
  void reportWithoutLocation(Message message, Severity severity) {
    options.reportWithoutLocation(message, severity);
  }

  /// Format [message] as a text string that can be included in generated code.
  String format(LocatedMessage message, Severity severity) {
    return command_line_reporting.format(message, severity);
  }

  /// Format [message] as a text string that can be included in generated code.
  // TODO(askesc): Remove this and direct callers directly to format.
  String formatWithoutLocation(Message message, Severity severity) {
    return command_line_reporting.format(message.withoutLocation(), severity);
  }

  void logError(Object message, Severity severity) {
    errors.add(message);
    errors.add(severity);
  }

  static void recordDependency(Uri uri) {
    if (uri.scheme != "file") {
      throw new ArgumentError("Expected a file-URI, but got: '$uri'.");
    }
    CompilerContext context = Zone.current[compilerContextKey];
    if (context != null) {
      context.dependencies.add(uri);
    }
  }

  static CompilerContext get current {
    CompilerContext context = Zone.current[compilerContextKey];
    if (context == null) {
      // Note: we throw directly and don't use internalProblem, because
      // internalProblem depends on having a compiler context available.
      var message = messageInternalProblemMissingContext.message;
      var tip = messageInternalProblemMissingContext.tip;
      throw "Internal problem: $message\nTip: $tip";
    }
    return context;
  }

  static bool get isActive => Zone.current[compilerContextKey] != null;

  /// Perform [action] in a [Zone] where [this] will be available as
  /// `CompilerContext.current`.
  Future<T> runInContext<T>(Future<T> action(CompilerContext c)) {
    return runZoned(
        () => new Future<T>.sync(() => action(this)).whenComplete(clear),
        zoneValues: {compilerContextKey: this});
  }

  /// Perform [action] in a [Zone] where [options] will be available as
  /// `CompilerContext.current.options`.
  static Future<T> runWithOptions<T>(
      ProcessedOptions options, Future<T> action(CompilerContext c)) {
    return new CompilerContext(options)
        .runInContext<T>((CompilerContext c) async {
      await options.validateOptions();
      return action(c);
    });
  }

  static Future<T> runWithDefaultOptions<T>(
      Future<T> action(CompilerContext c)) {
    return new CompilerContext(new ProcessedOptions(new CompilerOptions()))
        .runInContext<T>(action);
  }

  static bool get enableColors {
    return current.enableColorsCached ??= computeEnableColors(current);
  }

  void clear() {
    StringToken.canonicalizer.clear();
    errors.clear();
    dependencies.clear();
  }
}
