// 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 'package:analysis_server/src/plugin/plugin_locator.dart';
import 'package:analysis_server/src/plugin/plugin_manager.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/context/context_root.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:front_end/src/base/source.dart';
import 'package:path/src/context.dart';

/**
 * An object that watches the results produced by analysis drivers to identify
 * references to previously unseen packages and, if those packages have plugins
 * associated with them, causes the plugin to be associated with the driver's
 * context root (which in turn might cause the plugin to be started).
 */
class PluginWatcher implements DriverWatcher {
  /**
   * The resource provider used to access the file system.
   */
  final ResourceProvider resourceProvider;

  /**
   * The object managing the execution of plugins.
   */
  final PluginManager manager;

  /**
   * The object used to locate plugins within packages.
   */
  final PluginLocator _locator;

  /**
   * A table mapping analysis drivers to information related to the driver.
   */
  final Map<AnalysisDriver, _DriverInfo> _driverInfo =
      <AnalysisDriver, _DriverInfo>{};

  /**
   * Initialize a newly created plugin watcher.
   */
  PluginWatcher(this.resourceProvider, this.manager)
      : _locator = new PluginLocator(resourceProvider);

  /**
   * The context manager has just added the given analysis [driver]. This method
   * must be called before the driver has been allowed to perform any analysis.
   */
  void addedDriver(AnalysisDriver driver, ContextRoot contextRoot) {
    _driverInfo[driver] = new _DriverInfo(
        contextRoot, <String>[contextRoot.root, _getSdkPath(driver)]);
    List<String> enabledPlugins = driver.analysisOptions.enabledPluginNames;
    for (String hostPackageName in enabledPlugins) {
      //
      // Determine whether the package exists and defines a plugin.
      //
      String uri = 'package:$hostPackageName/$hostPackageName.dart';
      Source source = driver.sourceFactory.forUri(uri);
      if (source == null) {
        manager.recordPluginFailure(hostPackageName,
            'Could not resolve "$uri" in ${contextRoot.root}.');
      } else {
        Context context = resourceProvider.pathContext;
        String packageRoot = context.dirname(context.dirname(source.fullName));
        String pluginPath = _locator.findPlugin(packageRoot);
        if (pluginPath == null) {
          manager.recordPluginFailure(
              hostPackageName, 'Could not find plugin in "$packageRoot".');
        } else {
          //
          // Add the plugin to the context root.
          //
          // TODO(brianwilkerson) Do we need to wait for the plugin to be added?
          // If we don't, then tests don't have any way to know when to expect
          // that the list of plugins has been updated.
          manager.addPluginToContextRoot(contextRoot, pluginPath);
        }
      }
    }
  }

  /**
   * The context manager has just removed the given analysis [driver].
   */
  void removedDriver(AnalysisDriver driver) {
    _DriverInfo info = _driverInfo[driver];
    if (info == null) {
      throw new StateError('Cannot remove a driver that was not added');
    }
    manager.removedContextRoot(info.contextRoot);
    _driverInfo.remove(driver);
  }

  /**
   * Return the path to the root of the SDK being used by the given analysis
   * [driver].
   */
  String _getSdkPath(AnalysisDriver driver) {
    String sdkRoot = driver.sourceFactory.forUri('dart:core').fullName;
    while (resourceProvider.pathContext.basename(sdkRoot) != 'lib') {
      String parent = resourceProvider.pathContext.dirname(sdkRoot);
      if (parent == sdkRoot) {
        break;
      }
      sdkRoot = parent;
    }
    return sdkRoot;
  }
}

/**
 * Information related to an analysis driver.
 */
class _DriverInfo {
  /**
   * The context root representing the context being analyzed by the driver.
   */
  final ContextRoot contextRoot;

  /**
   * A list of the absolute paths of directories inside of which we have already
   * searched for a plugin.
   */
  final List<String> packageRoots;

  /**
   * Initialize a newly created information holder.
   */
  _DriverInfo(this.contextRoot, this.packageRoots);
}
