// 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.

import 'dart:async';

import 'package:front_end/compiler_options.dart';
import 'package:front_end/file_system.dart';
import 'package:front_end/src/fasta/translate_uri.dart';
import 'package:front_end/src/base/performace_logger.dart';
import 'package:front_end/src/incremental/byte_store.dart';
import 'package:front_end/src/simple_error.dart';
import 'package:package_config/packages_file.dart' as package_config;
import 'package:kernel/kernel.dart' show Program, loadProgramFromBytes;

/// Wrapper around [CompilerOptions] which exposes the options in a form useful
/// to the front end implementation.
///
/// The intent is that the front end should immediately wrap any incoming
/// [CompilerOptions] object in this class before doing further processing, and
/// should thereafter access all options via the wrapper.  This ensures that
/// options are interpreted in a consistent way and that data derived from
/// options is not unnecessarily recomputed.
class ProcessedOptions {
  /// The raw [CompilerOptions] which this class wraps.
  final CompilerOptions _raw;

  /// The package map derived from the options, or `null` if the package map has
  /// not been computed yet.
  Map<String, Uri> _packages;

  /// The object that knows how to resolve "package:" and "dart:" URIs,
  /// or `null` if it has not been computed yet.
  TranslateUri _uriTranslator;

  /// The SDK summary, or `null` if it has not been read yet.
  Program _sdkSummaryProgram;

  /// The summary for each uri in `options.inputSummaries`.
  List<Program> _inputSummariesPrograms;

  /// The location of the SDK, or `null` if the location hasn't been determined
  /// yet.
  Uri _sdkRoot;

  Uri get sdkRoot => _sdkRoot ??= _normalizeSdkRoot();

  /// Initializes a [ProcessedOptions] object wrapping the given [rawOptions].
  ProcessedOptions(CompilerOptions rawOptions) : this._raw = rawOptions;

  /// The logger to report compilation progress.
  PerformanceLog get logger {
    return _raw.logger;
  }

  /// The byte storage to get and put serialized data.
  ByteStore get byteStore {
    return _raw.byteStore;
  }

  /// Runs various validations checks on the input options. For instance,
  /// if an option is a path to a file, it checks that the file exists.
  Future<bool> validateOptions() async {
    var fs = _raw.fileSystem;
    var root = _raw.sdkRoot;

    bool _report(String msg) {
      _raw.onError(new SimpleError(msg));
      return false;
    }

    if (root != null && !await fs.entityForUri(root).exists()) {
      return _report("SDK root directory not found: ${_raw.sdkRoot}");
    }

    var summary = _raw.sdkSummary;
    if (summary != null && !await fs.entityForUri(summary).exists()) {
      return _report("SDK summary not found: ${_raw.sdkSummary}");
    }

    // TODO(sigmund): add checks for options that are meant to be disjoint (like
    // sdkRoot and sdkSummary).
    return true;
  }

  /// Determine whether to generate code for the SDK when compiling a
  /// whole-program.
  bool get compileSdk => _raw.compileSdk;

  /// Get the [FileSystem] which should be used by the front end to access
  /// files.
  ///
  /// If the client supplied roots using [CompilerOptions.multiRoots], the
  /// returned [FileSystem] will automatically perform the appropriate mapping.
  FileSystem get fileSystem {
    // TODO(paulberry): support multiRoots.
    assert(_raw.multiRoots.isEmpty);
    return _raw.fileSystem;
  }

  /// Whether to interpret Dart sources in strong-mode.
  bool get strongMode => _raw.strongMode;

  /// Get an outline program that summarizes the SDK.
  Future<Program> get sdkSummaryProgram async {
    if (_sdkSummaryProgram == null) {
      if (_raw.sdkSummary == null) return null;
      _sdkSummaryProgram = await _loadProgram(_raw.sdkSummary);
    }
    return _sdkSummaryProgram;
  }

  /// Get the summary programs for each of the underlying `inputSummaries`
  /// provided via [CompilerOptions].
  Future<List<Program>> get inputSummariesPrograms async {
    if (_inputSummariesPrograms == null) {
      var uris = _raw.inputSummaries;
      if (uris == null || uris.isEmpty) return const <Program>[];
      _inputSummariesPrograms = await Future.wait(uris.map(_loadProgram));
    }
    return _inputSummariesPrograms;
  }

  Future<Program> _loadProgram(Uri uri) async {
    var bytes = await fileSystem.entityForUri(uri).readAsBytes();
    return loadProgramFromBytes(bytes)..unbindCanonicalNames();
  }

  /// Get the [TranslateUri] which resolves "package:" and "dart:" URIs.
  ///
  /// This is an asynchronous method since file system operations may be
  /// required to locate/read the packages file as well as SDK metadata.
  Future<TranslateUri> getUriTranslator() async {
    if (_uriTranslator == null) {
      await _getPackages();
      // TODO(scheglov) Load SDK libraries from whatever format we decide.
      // TODO(scheglov) Remove the field "_raw.dartLibraries".
      _uriTranslator = new TranslateUri(_packages, _raw.dartLibraries);
      _uriTranslator.dartLibraries.addAll(_raw.dartLibraries);
    }
    return _uriTranslator;
  }

  /// Get the package map which maps package names to URIs.
  ///
  /// This is an asynchronous getter since file system operations may be
  /// required to locate/read the packages file.
  Future<Map<String, Uri>> _getPackages() async {
    if (_packages == null) {
      if (_raw.packagesFileUri == null) {
        throw new UnimplementedError(); // TODO(paulberry): search for .packages
      } else if (_raw.packagesFileUri.path.isEmpty) {
        _packages = {};
      } else {
        var contents =
            await fileSystem.entityForUri(_raw.packagesFileUri).readAsBytes();
        _packages = package_config.parse(contents, _raw.packagesFileUri);
      }
    }
    return _packages;
  }

  /// Get the location of the SDK.
  ///
  /// This is an asynchronous getter since file system operations may be
  /// required to locate the SDK.
  Uri _normalizeSdkRoot() {
    // If an SDK summary location was provided, the SDK itself should not be
    // needed.
    assert(_raw.sdkSummary == null);
    if (_raw.sdkRoot == null) {
      // TODO(paulberry): implement the algorithm for finding the SDK
      // automagically.
      throw new UnimplementedError();
    }
    var root = _raw.sdkRoot;
    if (!root.path.endsWith('/')) {
      root = root.replace(path: _sdkRoot.path + '/');
    }
    return root;
  }
}
