// 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:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/standard_ast_factory.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/context/context.dart' show AnalysisContextImpl;
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/handle.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/generated/engine.dart' show AnalysisContext;
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
import 'package:analyzer/src/generated/testing/ast_test_factory.dart';
import 'package:analyzer/src/summary/summary_sdk.dart';
import 'package:front_end/src/base/resolve_relative_uri.dart';
import 'package:front_end/src/base/source.dart';
import 'package:front_end/src/fasta/kernel/kernel_shadow_ast.dart' as kernel;
import 'package:front_end/src/fasta/kernel/redirecting_factory_body.dart';
import 'package:kernel/kernel.dart' as kernel;
import 'package:kernel/type_algebra.dart' as kernel;
import 'package:kernel/type_environment.dart' as kernel;
import 'package:path/path.dart' as pathos;

/**
 * Object that can resynthesize analyzer [LibraryElement] from Kernel.
 */
class KernelResynthesizer implements ElementResynthesizer {
  final AnalysisContextImpl _analysisContext;
  final Map<String, LineInfo> _lineInfoMap;
  final Map<String, kernel.Library> _kernelMap;
  final Map<String, bool> _libraryExistMap;
  final Map<String, LibraryElementImpl> _libraryMap = {};

  /**
   * Cache of [Source] objects that have already been converted from URIs.
   */
  final Map<String, Source> _sources = <String, Source>{};

  /// The type provider for this resynthesizer.
  SummaryTypeProvider _typeProvider;

  KernelResynthesizer(this._analysisContext, this._lineInfoMap, this._kernelMap,
      this._libraryExistMap) {
    _buildTypeProvider();
    _analysisContext.typeProvider = _typeProvider;
  }

  @override
  AnalysisContext get context => _analysisContext;

  /**
   * TODO(brianwilkerson) Remove this getter.
   */
  bool get strongMode => true;

  /**
   * Return the `Type` type.
   */
  DartType get typeType => getLibrary('dart:core').getType('Type').type;

  @override
  Element getElement(ElementLocation location) {
    List<String> components = location.components;

    LibraryElementImpl library = getLibrary(components[0]);
    if (components.length == 1) {
      return library;
    }

    CompilationUnitElement unit;
    for (var libraryUnit in library.units) {
      if (libraryUnit.source.uri.toString() == components[1]) {
        unit = libraryUnit;
        break;
      }
    }
    if (unit == null) {
      throw new ArgumentError('Unable to find unit: $location');
    }
    if (components.length == 2) {
      return unit;
    }

    ElementImpl element = unit as ElementImpl;
    for (int i = 2; i < components.length; i++) {
      if (element == null) {
        throw new ArgumentError('Unable to find element: $location');
      }
      element = element.getChild(components[i]);
    }
    return element;
  }

  /**
   * Return the [ElementImpl] that corresponds to the given [name], or `null`
   * if the corresponding element cannot be found.
   */
  ElementImpl getElementFromCanonicalName(kernel.CanonicalName name) {
    if (name == null) return null;

    var components = new List<String>(5);
    var componentPtr = 0;
    for (var namePart = name;
        namePart != null && !namePart.isRoot;
        namePart = namePart.parent) {
      components[componentPtr++] = namePart.name;
    }

    String libraryUri = components[--componentPtr];
    LibraryElementImpl library = getLibrary(libraryUri);
    if (library == null) return null;

    if (componentPtr == 0) {
      return library;
    }

    String topKindOrClassName = components[--componentPtr];

    String takeElementName() {
      String publicNameOrLibraryUri = components[--componentPtr];
      if (publicNameOrLibraryUri == libraryUri) {
        return components[--componentPtr];
      } else {
        return publicNameOrLibraryUri;
      }
    }

    // Top-level element other than class.
    if (topKindOrClassName == '@fields' ||
        topKindOrClassName == '@methods' ||
        topKindOrClassName == '@getters' ||
        topKindOrClassName == '@setters' ||
        topKindOrClassName == '@typedefs') {
      String elementName = takeElementName();
      for (CompilationUnitElement unit in library.units) {
        CompilationUnitElementImpl unitImpl = unit;
        ElementImpl child = unitImpl.getChild(elementName);
        if (child != null) {
          return child;
        }
      }
      return null;
    }

    AbstractClassElementImpl classElement;
    for (CompilationUnitElement unit in library.units) {
      CompilationUnitElementImpl unitImpl = unit;
      classElement = unitImpl.getChild(topKindOrClassName);
      if (classElement != null) {
        break;
      }
    }
    if (classElement == null) return null;

    // If no more component, the class is the element.
    if (componentPtr == 0) return classElement;

    String kind = components[--componentPtr];
    String elementName = takeElementName();
    if (kind == '@methods') {
      return classElement.getMethod(elementName) as ElementImpl;
    } else if (kind == '@getters') {
      return classElement.getGetter(elementName) as ElementImpl;
    } else if (kind == '@setters') {
      return classElement.getSetter(elementName) as ElementImpl;
    } else if (kind == '@fields') {
      return classElement.getField(elementName) as ElementImpl;
    } else if (kind == '@constructors' || kind == '@factories') {
      if (elementName.isEmpty) {
        return classElement.unnamedConstructor as ElementImpl;
      }
      return classElement.getNamedConstructor(elementName) as ElementImpl;
    } else {
      throw new UnimplementedError('Internal error: $kind unexpected.');
    }
  }

  /**
   * Return the [LibraryElementImpl] for the given [uriStr], or `null` if
   * the library is not part of the Kernel libraries bundle.
   */
  LibraryElementImpl getLibrary(String uriStr) {
    return _libraryMap.putIfAbsent(uriStr, () {
      var kernel = _kernelMap[uriStr];
      if (kernel == null) return null;

      if (_libraryExistMap[uriStr] != true) {
        return _newSyntheticLibrary(uriStr);
      }

      var libraryContext =
          new _KernelLibraryResynthesizerContextImpl(this, kernel);

      // Build the library.
      LibraryElementImpl libraryElement = libraryContext._buildLibrary(uriStr);
      if (libraryElement == null) return null;

      // Build the defining unit.
      var definingUnit = libraryContext._buildUnit(null).unit;
      definingUnit.lineInfo = _lineInfoMap[uriStr];
      libraryElement.definingCompilationUnit = definingUnit;

      // Build units for parts.
      var parts = new List<CompilationUnitElementImpl>(kernel.parts.length);
      for (int i = 0; i < kernel.parts.length; i++) {
        var fileUriStr =
            kernel.fileUri.resolve(kernel.parts[i].partUri).toString();
        var unitContext = libraryContext._buildUnit(fileUriStr);
        var partUnit = unitContext.unit;
        partUnit.lineInfo = _lineInfoMap[fileUriStr];
        parts[i] = partUnit;
      }
      libraryElement.parts = parts;

      // Create the required `loadLibrary` function.
      if (uriStr != 'dart:core' && uriStr != 'dart:async') {
        libraryElement.createLoadLibraryFunction(_typeProvider);
      }

      return libraryElement;
    });
  }

  DartType getType(ElementImpl context, kernel.DartType kernelType) {
    if (kernelType is kernel.DynamicType) return DynamicTypeImpl.instance;
    if (kernelType is kernel.InvalidType) return UndefinedTypeImpl.instance;
    if (kernelType is kernel.BottomType) return BottomTypeImpl.instance;
    if (kernelType is kernel.VoidType) return VoidTypeImpl.instance;

    if (kernelType is kernel.InterfaceType) {
      var name = kernelType.className.canonicalName;
      if (!strongMode &&
          name.name == 'FutureOr' &&
          name.parent.name == 'dart:async') {
        return DynamicTypeImpl.instance;
      }
      return _getInterfaceType(context, name, kernelType.typeArguments);
    }

    if (kernelType is kernel.TypeParameterType) {
      kernel.TypeParameter kTypeParameter = kernelType.parameter;
      return getTypeParameter(context, kTypeParameter).type;
    }

    if (kernelType is kernel.FunctionType) {
      return _getFunctionType(context, kernelType);
    }

    // TODO(scheglov) Support other kernel types.
    throw new UnimplementedError('For ${kernelType.runtimeType}');
  }

  /// Return the [TypeParameterElement] for the given [kernelTypeParameter].
  TypeParameterElement getTypeParameter(
      ElementImpl context, kernel.TypeParameter kernelTypeParameter) {
    String name = kernelTypeParameter.name;
    for (var ctx = context; ctx != null; ctx = ctx.enclosingElement) {
      if (ctx is TypeParameterizedElementMixin) {
        for (var typeParameter in ctx.typeParameters) {
          if (typeParameter.name == name) {
            return typeParameter;
          }
        }
      }
    }
    throw new StateError('Not found $kernelTypeParameter in $context');
  }

  void _buildTypeProvider() {
    var coreLibrary = getLibrary('dart:core');
    var asyncLibrary = getLibrary('dart:async');
    _typeProvider = new SummaryTypeProvider();
    _typeProvider.initializeCore(coreLibrary);
    _typeProvider.initializeAsync(asyncLibrary);
    // Now, when TypeProvider is ready, we can finalize core/async.
    coreLibrary.createLoadLibraryFunction(_typeProvider);
    asyncLibrary.createLoadLibraryFunction(_typeProvider);
  }

  /// Return the [FunctionType] that corresponds to the given [kernelType].
  FunctionType _getFunctionType(
      ElementImpl context, kernel.FunctionType kernelType) {
    if (kernelType.typedef != null) {
      var translatedType = _getTypedefType(context, kernelType);
      if (translatedType != null) return translatedType;
    }

    var element = new FunctionElementImpl('', -1);
    context.encloseElement(element);

    // Set type parameters.
    {
      List<kernel.TypeParameter> typeParameters = kernelType.typeParameters;
      int count = typeParameters.length;
      var astTypeParameters = new List<TypeParameterElement>(count);
      for (int i = 0; i < count; i++) {
        astTypeParameters[i] =
            new TypeParameterElementImpl.forKernel(element, typeParameters[i]);
      }
      element.typeParameters = astTypeParameters;
    }

    // Set formal parameters.
    var parameters = _getFunctionTypeParameters(kernelType);
    var positionalParameters = parameters[0];
    var namedParameters = parameters[1];
    var astParameters = ParameterElementImpl.forKernelParameters(
        element,
        kernelType.requiredParameterCount,
        positionalParameters,
        namedParameters);
    element.parameters = astParameters;

    element.returnType = getType(element, kernelType.returnType);

    return new FunctionTypeImpl(element);
  }

  InterfaceType _getInterfaceType(ElementImpl context,
      kernel.CanonicalName className, List<kernel.DartType> kernelArguments) {
    var libraryName = className.parent;
    var libraryElement = getLibrary(libraryName.name);
    ClassElement classElement = libraryElement.getType(className.name);
    classElement ??= libraryElement.getEnum(className.name);

    if (kernelArguments.isEmpty) {
      return classElement.type;
    }

    return new InterfaceTypeImpl.elementWithNameAndArgs(
        classElement, classElement.name, () {
      List<DartType> arguments = kernelArguments
          .map((kernel.DartType k) => getType(context, k))
          .toList(growable: false);
      return arguments;
    });
  }

  /**
   * Get the [Source] object for the given [uri].
   */
  Source _getSource(String uri) {
    return _sources.putIfAbsent(
        uri, () => _analysisContext.sourceFactory.forUri(uri));
  }

  /// Return the [FunctionType] for the given typedef based [kernelType].
  FunctionType _getTypedefType(
      ElementImpl context, kernel.FunctionType kernelType) {
    kernel.Typedef typedef = kernelType.typedef;

    GenericTypeAliasElementImpl typedefElement =
        getElementFromCanonicalName(typedef.canonicalName);

    kernel.FunctionType typedefType = typedef.type;

    if (kernelType.typeParameters.length != typedefType.typeParameters.length) {
      // Type parameters don't match; just resynthesize as a synthetic function
      // type.
      return null;
    }

    // In the general case imagine the typedef is:
    //     typedef F<T, U> = ... Function<V, W>(...);
    // And kernelType is:
    //     ... Function<X, Y>(...);
    // So the outer type parameters of the typedef have been instantiated (or
    // there were none); the inner type parameters have not been instantiated
    // (or there were none).
    //
    // Now we have to figure out what substitution was used to instantiate
    // the typedef, since the kernel doesn't track that information.
    var substitution = kernel.unifyTypes(
        typedefType, kernelType, typedef.typeParameters.toSet());

    // Convert kernel type arguments into Analyzer types.
    var typeArguments = typedef.typeParameters
        .map((t) => substitution.containsKey(t)
            ? getType(context, substitution[t])
            : _typeProvider.nullType)
        .toList();
    return typedefElement.instantiate(typeArguments);
  }

  LibraryElementImpl _newSyntheticLibrary(String uriStr) {
    Source librarySource = _getSource(uriStr);
    if (librarySource == null) return null;

    LibraryElementImpl libraryElement =
        new LibraryElementImpl(context, '', -1, 0);
    libraryElement.isSynthetic = true;
    CompilationUnitElementImpl unitElement =
        new CompilationUnitElementImpl(librarySource.shortName);
    libraryElement.definingCompilationUnit = unitElement;
    unitElement.source = librarySource;
    unitElement.librarySource = librarySource;
    libraryElement.createLoadLibraryFunction(_typeProvider);
    libraryElement.publicNamespace = new Namespace({});
    libraryElement.exportNamespace = new Namespace({});
    return libraryElement;
  }

  /// Return the list with exactly two elements - positional and named
  /// parameter lists.
  static List<List<kernel.VariableDeclaration>> _getFunctionTypeParameters(
      kernel.FunctionType type) {
    int positionalCount = type.positionalParameters.length;
    var positionalParameters =
        new List<kernel.VariableDeclaration>(positionalCount);
    var knownPositionalParameters = const <kernel.VariableDeclaration>[];
    if (type.typedefReference != null) {
      knownPositionalParameters =
          type.typedefReference.asTypedef.positionalParameters;
    }
    for (int i = 0; i < positionalCount; i++) {
      String name = i < knownPositionalParameters.length
          ? (knownPositionalParameters[i].name ?? '')
          : 'arg_$i';
      positionalParameters[i] = new kernel.VariableDeclaration(name,
          type: type.positionalParameters[i]);
    }

    var namedParameters = type.namedParameters
        .map((k) => new kernel.VariableDeclaration(k.name, type: k.type))
        .toList(growable: false);

    return [positionalParameters, namedParameters];
  }
}

/**
 * Builder of [Expression]s from [kernel.Expression]s.
 */
class _ExprBuilder {
  final _KernelUnitResynthesizerContextImpl _context;
  final ElementImpl _contextElement;

  _ExprBuilder(this._context, this._contextElement);

  TypeProvider get typeProvider =>
      _context.libraryContext.resynthesizer._typeProvider;

  Expression build(kernel.Expression expr) {
    if (_hasInvalidExpression(expr)) {
      return AstTestFactory.identifier3('#invalidConst');
    }
    return _build(expr);
  }

  ConstructorInitializer buildInitializer(kernel.Initializer k) {
    if (k is kernel.FieldInitializer) {
      Expression value = build(k.value);
      ConstructorFieldInitializer initializer =
          AstTestFactory.constructorFieldInitializer(
              false, k.field.name.name, value);
      initializer.fieldName.staticElement = _getElement(k.fieldReference);
      return initializer;
    }

    if (k is kernel.AssertInitializer) {
      var body = k.statement;
      var condition = build(body.condition);
      var message = body.message != null ? build(body.message) : null;
      return AstTestFactory.assertInitializer(condition, message);
    }

    if (k is kernel.RedirectingInitializer) {
      ConstructorElementImpl redirect = _getElement(k.targetReference);
      var arguments = _toArguments(k.arguments);

      RedirectingConstructorInvocation invocation =
          AstTestFactory.redirectingConstructorInvocation(arguments);
      invocation.staticElement = redirect;

      String name = k.target.name.name;
      if (name.isNotEmpty) {
        invocation.constructorName = AstTestFactory.identifier3(name)
          ..staticElement = redirect;
      }

      return invocation;
    }

    if (k is kernel.SuperInitializer) {
      ConstructorElementImpl redirect = _getElement(k.targetReference);
      var arguments = _toArguments(k.arguments);

      SuperConstructorInvocation invocation =
          AstTestFactory.superConstructorInvocation(arguments);
      invocation.staticElement = redirect;

      String name = k.target.name.name;
      if (name.isNotEmpty) {
        invocation.constructorName = AstTestFactory.identifier3(name)
          ..staticElement = redirect;
      }

      return invocation;
    }

    if (k is kernel.ShadowInvalidFieldInitializer ||
        k is kernel.ShadowInvalidInitializer ||
        k is kernel.InvalidSuperInitializerJudgment) {
      return null;
    }

    throw new UnimplementedError('For ${k.runtimeType}');
  }

  Expression _build(kernel.Expression expr) {
    if (expr is kernel.NullLiteral) {
      return AstTestFactory.nullLiteral();
    }
    if (expr is kernel.BoolLiteral) {
      return AstTestFactory.booleanLiteral(expr.value);
    }
    if (expr is kernel.IntLiteral) {
      return AstTestFactory.integer(expr.value);
    }
    if (expr is kernel.DoubleLiteral) {
      return AstTestFactory.doubleLiteral(expr.value);
    }
    if (expr is kernel.StringLiteral) {
      return AstTestFactory.string2(expr.value);
    }

    if (expr is kernel.StringConcatenation) {
      List<InterpolationElement> elements = expr.expressions
          .map(_build)
          .map(_newInterpolationElement)
          .toList(growable: false);
      return AstTestFactory.string(elements);
    }

    if (expr is kernel.SymbolLiteral) {
      List<String> components = expr.value.split('.').toList();
      return AstTestFactory.symbolLiteral(components);
    }

    if (expr is kernel.ListLiteral) {
      Keyword keyword = expr.isConst ? Keyword.CONST : null;
      var typeArguments = _getTypes([expr.typeArgument]);
      var typeArgumentNodes = _buildTypeArgumentList(typeArguments);
      var elements = expr.expressions.map(_build).toList();
      return AstTestFactory.listLiteral2(keyword, typeArgumentNodes, elements)
        ..staticType = typeProvider.listType.instantiate(typeArguments);
    }

    if (expr is kernel.MapLiteral) {
      Keyword keyword = expr.isConst ? Keyword.CONST : null;
      var typeArguments = _getTypes([expr.keyType, expr.valueType]);
      var typeArgumentNodes = _buildTypeArgumentList(typeArguments);

      int numberOfEntries = expr.entries.length;
      var entries = new List<MapLiteralEntry>(numberOfEntries);
      for (int i = 0; i < numberOfEntries; i++) {
        var entry = expr.entries[i];
        Expression key = _build(entry.key);
        Expression value = _build(entry.value);
        entries[i] = AstTestFactory.mapLiteralEntry2(key, value);
      }

      return AstTestFactory.mapLiteral(keyword, typeArgumentNodes, entries);
    }

    if (expr is kernel.StaticGet) {
      return _buildIdentifier(expr.targetReference, isGet: true);
    }

    if (expr is kernel.ThisExpression) {
      return AstTestFactory.thisExpression();
    }

    if (expr is kernel.PropertyGet) {
      Expression target = _build(expr.receiver);
      kernel.Reference reference = expr.interfaceTargetReference;
      SimpleIdentifier identifier = _buildSimpleIdentifier(reference);
      return AstTestFactory.propertyAccess(target, identifier);
    }

    if (expr is kernel.VariableGet) {
      String name = expr.variable.name;
      Element contextConstructor = _contextElement;
      if (contextConstructor is ConstructorElement) {
        SimpleIdentifier identifier = AstTestFactory.identifier3(name);
        ParameterElement parameter = contextConstructor.parameters.firstWhere(
            (parameter) => parameter.name == name,
            orElse: () => null);
        identifier.staticElement = parameter;
        return identifier;
      }
    }

    if (expr is kernel.ConditionalExpression) {
      var condition = _build(expr.condition);
      var then = _build(expr.then);
      var otherwise = _build(expr.otherwise);
      return AstTestFactory.conditionalExpression(condition, then, otherwise);
    }

    if (expr is kernel.Not) {
      kernel.Expression kernelOperand = expr.operand;
      var operand = _build(kernelOperand);
      return AstTestFactory.prefixExpression(TokenType.BANG, operand);
    }

    if (expr is kernel.LogicalExpression) {
      var operator = _toBinaryOperatorTokenType(expr.operator);
      var left = _build(expr.left);
      var right = _build(expr.right);
      return AstTestFactory.binaryExpression(left, operator, right);
    }

    if (expr is kernel.AsExpression && expr.isTypeError) {
      return _build(expr.operand);
    }

    if (expr is kernel.Let) {
      var body = expr.body;
      if (body is kernel.ConditionalExpression) {
        var condition = body.condition;
        var otherwiseExpr = body.otherwise;
        if (condition is kernel.MethodInvocation) {
          var equalsReceiver = condition.receiver;
          if (equalsReceiver is kernel.VariableGet &&
              condition.name.name == '==' &&
              condition.arguments.positional.length == 1 &&
              condition.arguments.positional[0] is kernel.NullLiteral &&
              otherwiseExpr is kernel.VariableGet &&
              otherwiseExpr.variable == equalsReceiver.variable) {
            var left = _build(expr.variable.initializer);
            var right = _build(body.then);
            return AstTestFactory.binaryExpression(
                left, TokenType.QUESTION_QUESTION, right);
          }
        }
      }
    }

    if (expr is kernel.MethodInvocation) {
      var left = _build(expr.receiver);
      String operatorName = expr.name.name;
      List<kernel.Expression> args = expr.arguments.positional;
      if (args.isEmpty) {
        if (operatorName == 'unary-') {
          return AstTestFactory.prefixExpression(TokenType.MINUS, left);
        }
        if (operatorName == '~') {
          return AstTestFactory.prefixExpression(TokenType.TILDE, left);
        }
      } else if (args.length == 1) {
        var operator = _toBinaryOperatorTokenType(operatorName);
        var right = _build(args.single);
        return AstTestFactory.binaryExpression(left, operator, right);
      }
    }

    if (expr is kernel.StaticInvocation) {
      kernel.Procedure target = expr.target;
      String name = target.name.name;
      List<Expression> arguments = _toArguments(expr.arguments);
      MethodInvocation invocation =
          AstTestFactory.methodInvocation3(null, name, null, arguments);
      invocation.methodName.staticElement = _getElement(target.reference);
      return invocation;
    }

    if (expr is kernel.ConstructorInvocation) {
      ConstructorElementImpl element = _getElement(expr.targetReference);

      // It's safe to pass null for the TypeEnvironment because it isn't
      // needed to compute the type of a constructor invocation.
      var kernelType = expr.getStaticType(null);
      var type = _getType(kernelType);
      TypeName typeName = _buildType(type);

      var constructorName = AstTestFactory.constructorName(
          typeName, element.name.isNotEmpty ? element.name : null);
      constructorName?.name?.staticElement = element;

      var keyword = expr.isConst ? Keyword.CONST : Keyword.NEW;
      var arguments = _toArguments(expr.arguments);
      var creation = AstTestFactory.instanceCreationExpression(
          keyword, constructorName, arguments);
      creation.staticElement = ConstructorMember.from(element, type);
      creation.staticType = type;
      return creation;
    }

    if (expr is kernel.Instantiation) {
      return _build(expr.expression);
    }

    if (expr is kernel.TypeLiteral) {
      ElementImpl element;
      var kernelType = expr.type;
      if (kernelType is kernel.FunctionType) {
        element = _getElement(kernelType.typedefReference);
      } else {
        var type = _getType(kernelType);
        element = type.element;
      }
      var identifier = AstTestFactory.identifier3(element.name);
      identifier.staticElement = element;
      identifier.staticType = _context.libraryContext.resynthesizer.typeType;
      return identifier;
    }

    // TODO(scheglov): complete getExpression
    throw new UnimplementedError('kernel: (${expr.runtimeType}) $expr');
  }

  Identifier _buildIdentifier(kernel.Reference reference, {bool isGet: false}) {
    Element element = _getElement(reference);
    if (isGet && element is PropertyInducingElement) {
      element = (element as PropertyInducingElement).getter;
    }
    SimpleIdentifier property = AstTestFactory.identifier3(element.displayName)
      ..staticElement = element;
    Element enclosingElement = element.enclosingElement;
    if (enclosingElement is ClassElement) {
      SimpleIdentifier classRef =
          AstTestFactory.identifier3(enclosingElement.name)
            ..staticElement = enclosingElement;
      return AstTestFactory.identifier(classRef, property);
    } else {
      return property;
    }
  }

  SimpleIdentifier _buildSimpleIdentifier(kernel.Reference reference) {
    String name = reference.canonicalName.name;
    SimpleIdentifier identifier = AstTestFactory.identifier3(name);
    Element element = _getElement(reference);
    identifier.staticElement = element;
    return identifier;
  }

  TypeAnnotation _buildType(DartType type) {
    List<TypeAnnotation> argumentNodes;
    if (type is ParameterizedType) {
      argumentNodes = _buildTypeArguments(type.typeArguments);
    }
    TypeName node = AstTestFactory.typeName4(type.name, argumentNodes);
    node.type = type;
    (node.name as SimpleIdentifier).staticElement = type.element;
    return node;
  }

  TypeArgumentList _buildTypeArgumentList(List<DartType> types) {
    int length = types.length;
    var typeAnnotations = new List<TypeAnnotation>(length);
    for (int i = 0; i < length; i++) {
      DartType type = types[i];
      TypeAnnotation typeAnnotation = _buildType(type);
      typeAnnotations[i] = typeAnnotation;
    }
    return AstTestFactory.typeArgumentList(typeAnnotations);
  }

  List<TypeAnnotation> _buildTypeArguments(List<DartType> types) {
    if (types.every((t) => t.isDynamic)) return null;
    return types.map(_buildType).toList();
  }

  ElementImpl _getElement(kernel.Reference reference) {
    return _context.libraryContext.resynthesizer
        .getElementFromCanonicalName(reference?.canonicalName);
  }

  DartType _getType(kernel.DartType kernel) {
    return _context.getType(_contextElement, kernel);
  }

  List<DartType> _getTypes(List<kernel.DartType> kernels) {
    int length = kernels.length;
    var types = new List<DartType>(length);
    for (int i = 0; i < length; i++) {
      DartType type = _getType(kernels[i]);
      types[i] = type;
    }
    return types;
  }

  InterpolationElement _newInterpolationElement(Expression expr) {
    if (expr is SimpleStringLiteral) {
      return astFactory.interpolationString(expr.literal, expr.value);
    } else {
      return AstTestFactory.interpolationExpression(expr);
    }
  }

  /// Return [Expression]s for the given [kernelArguments].
  List<Expression> _toArguments(kernel.Arguments kernelArguments) {
    int numPositional = kernelArguments.positional.length;
    int numNamed = kernelArguments.named.length;
    var arguments = new List<Expression>(numPositional + numNamed);

    int i = 0;
    for (kernel.Expression k in kernelArguments.positional) {
      arguments[i++] = _build(k);
    }

    for (kernel.NamedExpression k in kernelArguments.named) {
      var value = _build(k.value);
      arguments[i++] = AstTestFactory.namedExpression2(k.name, value);
    }

    return arguments;
  }

  /// Return the [TokenType] for the given operator [name].
  TokenType _toBinaryOperatorTokenType(String name) {
    if (name == '==') return TokenType.EQ_EQ;
    if (name == '&&') return TokenType.AMPERSAND_AMPERSAND;
    if (name == '||') return TokenType.BAR_BAR;
    if (name == '^') return TokenType.CARET;
    if (name == '&') return TokenType.AMPERSAND;
    if (name == '|') return TokenType.BAR;
    if (name == '>>') return TokenType.GT_GT;
    if (name == '<<') return TokenType.LT_LT;
    if (name == '+') return TokenType.PLUS;
    if (name == '-') return TokenType.MINUS;
    if (name == '*') return TokenType.STAR;
    if (name == '/') return TokenType.SLASH;
    if (name == '~/') return TokenType.TILDE_SLASH;
    if (name == '%') return TokenType.PERCENT;
    if (name == '>') return TokenType.GT;
    if (name == '<') return TokenType.LT;
    if (name == '>=') return TokenType.GT_EQ;
    if (name == '<=') return TokenType.LT_EQ;
    if (name == 'unary-') return TokenType.MINUS;
    throw new ArgumentError(name);
  }

  static bool _hasInvalidExpression(kernel.Expression expr) {
    var visitor = new _HasInvalidExpressionVisitor();
    expr.accept(visitor);
    return visitor.result;
  }
}

class _HasInvalidExpressionVisitor extends kernel.RecursiveVisitor<void> {
  bool result = false;

  @override
  void visitInvalidExpression(kernel.InvalidExpression node) {
    result = true;
  }
}

/**
 * Implementation of [KernelLibraryResynthesizerContext].
 */
class _KernelLibraryResynthesizerContextImpl
    implements KernelLibraryResynthesizerContext {
  final KernelResynthesizer resynthesizer;

  @override
  final kernel.Library library;

  /**
   * The relative URI of the directory with the [library] file.
   * E.g. `sdk/lib/core` for `sdk/lib/core/core.dart`.
   */
  String libraryDirectoryUri;

  Source librarySource;
  LibraryElementImpl libraryElement;

  _KernelLibraryResynthesizerContextImpl(this.resynthesizer, this.library) {
    libraryDirectoryUri = pathos.url.dirname("${library.fileUri}");
  }

  @override
  kernel.Library get coreLibrary => resynthesizer._kernelMap['dart:core'];

  @override
  bool get hasExtUri {
    for (var dependency in library.dependencies) {
      if (dependency.isImport &&
          dependency.targetLibrary.importUri.isScheme('dart-ext')) {
        return true;
      }
    }
    return false;
  }

  @override
  Namespace buildExportNamespace() {
    Namespace publicNamespace = buildPublicNamespace();
    if (library.additionalExports.isEmpty) {
      return publicNamespace;
    }

    Map<String, Element> definedNames = publicNamespace.definedNames;
    for (kernel.Reference additionalExport in library.additionalExports) {
      var element = resynthesizer
          .getElementFromCanonicalName(additionalExport.canonicalName);
      if (element != null) {
        definedNames[element.name] = element;
      }
    }

    return new Namespace(definedNames);
  }

  @override
  Namespace buildPublicNamespace() {
    return new NamespaceBuilder()
        .createPublicNamespaceForLibrary(libraryElement);
  }

  @override
  LibraryElementImpl getLibrary(String uriStr) {
    return resynthesizer.getLibrary(uriStr);
  }

  LibraryElementImpl _buildLibrary(String uriStr) {
    librarySource = resynthesizer._getSource(uriStr);
    if (librarySource == null) return null;
    return libraryElement =
        new LibraryElementImpl.forKernel(resynthesizer._analysisContext, this);
  }

  _KernelUnitResynthesizerContextImpl _buildUnit(String fileUri) {
    var unitContext = new _KernelUnitResynthesizerContextImpl(
        this, fileUri ?? "${library.fileUri}");
    var unitElement = new CompilationUnitElementImpl.forKernel(
        libraryElement, unitContext, '<no name>');
    unitContext.unit = unitElement;
    unitElement.librarySource = librarySource;

    if (fileUri != null) {
      String absoluteUriStr;
      if (fileUri.startsWith('file://')) {
        // Compute the URI relative to the library directory.
        // E.g. when the library directory URI is `sdk/lib/core`, and the unit
        // URI is `sdk/lib/core/bool.dart`, the result is `bool.dart`.
        var relativeUri =
            pathos.url.relative(fileUri, from: libraryDirectoryUri);
        // Compute the absolute URI.
        // When the absolute library URI is `dart:core`, and the relative
        // URI is `bool.dart`, the result is `dart:core/bool.dart`.
        Uri absoluteUri =
            resolveRelativeUri(librarySource.uri, Uri.parse(relativeUri));
        absoluteUriStr = absoluteUri.toString();
      } else {
        // File URIs must have the "file" scheme.
        // But for invalid URIs, which cannot be even parsed, FrontEnd returns
        // URIs with the "org-dartlang-malformed-uri" scheme, and does not
        // resolve them to file URIs.
        // We don't have anything better than to use these URIs as is.
        absoluteUriStr = fileUri;
      }
      unitElement.source = resynthesizer._getSource(absoluteUriStr);
    } else {
      unitElement.source = librarySource;
    }

    unitContext.unit = unitElement;
    return unitContext;
  }
}

/**
 * Implementation of [KernelUnit].
 */
class _KernelUnitImpl implements KernelUnit {
  final _KernelUnitResynthesizerContextImpl context;

  List<kernel.Expression> _annotations;
  List<kernel.Class> _classes;
  List<kernel.Field> _fields;
  List<kernel.Procedure> _procedures;
  List<kernel.Typedef> _typedefs;

  _KernelUnitImpl(this.context);

  @override
  List<kernel.Expression> get annotations {
    if (_annotations == null) {
      for (var part in context.libraryContext.library.parts) {
        if ("${context.libraryContext.library.fileUri.resolve(part.partUri)}" ==
            context.fileUri) {
          return _annotations = part.annotations;
        }
      }
    }
    return _annotations ?? const <kernel.Expression>[];
  }

  @override
  List<kernel.Class> get classes =>
      _classes ??= context.libraryContext.library.classes
          .where((n) => "${n.fileUri}" == context.fileUri)
          .toList(growable: false);

  @override
  List<kernel.Field> get fields =>
      _fields ??= context.libraryContext.library.fields
          .where((n) => "${n.fileUri}" == context.fileUri)
          .toList(growable: false);

  @override
  List<kernel.Procedure> get procedures =>
      _procedures ??= context.libraryContext.library.procedures
          .where((n) => "${n.fileUri}" == context.fileUri)
          .toList(growable: false);

  @override
  List<kernel.Typedef> get typedefs =>
      _typedefs ??= context.libraryContext.library.typedefs
          .where((n) => "${n.fileUri}" == context.fileUri)
          .toList(growable: false);
}

/**
 * Implementation of [KernelUnitResynthesizerContext].
 */
class _KernelUnitResynthesizerContextImpl
    implements KernelUnitResynthesizerContext {
  static final Uri dartInternalUri = Uri.parse('dart:_internal');

  final _KernelLibraryResynthesizerContextImpl libraryContext;
  final String fileUri;

  CompilationUnitElementImpl unit;

  _KernelUnitResynthesizerContextImpl(this.libraryContext, this.fileUri);

  @override
  KernelUnit get kernelUnit => new _KernelUnitImpl(this);

  @override
  List<ElementAnnotation> buildAnnotations(
      List<kernel.Expression> expressions) {
    int length = expressions.length;
    if (length != 0) {
      var annotations = <ElementAnnotation>[];
      for (var expression in expressions) {
        if (_isSyntheticExternalNameAnnotation(expression)) continue;
        var annotation = _buildAnnotation(unit, expression);
        annotations.add(annotation);
      }
      return annotations;
    } else {
      return const <ElementAnnotation>[];
    }
  }

  @override
  UnitExplicitTopLevelAccessors buildTopLevelAccessors() {
    var accessorsData = new UnitExplicitTopLevelAccessors();
    var implicitVariables = <String, TopLevelVariableElementImpl>{};
    // Build explicit property accessors and implicit fields.
    for (var procedure in kernelUnit.procedures) {
      bool isGetter = procedure.kind == kernel.ProcedureKind.Getter;
      bool isSetter = procedure.kind == kernel.ProcedureKind.Setter;
      if (isGetter || isSetter) {
        var accessor =
            new PropertyAccessorElementImpl.forKernel(unit, procedure);
        accessorsData.accessors.add(accessor);

        // Create or update the implicit variable.
        String name = accessor.displayName;
        TopLevelVariableElementImpl variable = implicitVariables[name];
        if (variable == null) {
          variable = new TopLevelVariableElementImpl(name, -1);
          implicitVariables[name] = variable;
          variable.enclosingElement = unit;
          variable.isSynthetic = true;
          variable.isFinal = isGetter;
        } else {
          variable.isFinal = false;
        }

        // Attach the accessor to the variable.
        accessor.variable = variable;
        if (isGetter) {
          variable.getter = accessor;
        } else {
          variable.setter = accessor;
        }
      }
    }
    accessorsData.implicitVariables.addAll(implicitVariables.values);
    return accessorsData;
  }

  @override
  UnitExplicitTopLevelVariables buildTopLevelVariables() {
    List<kernel.Field> kernelFields = kernelUnit.fields;
    int numberOfVariables = kernelFields.length;
    var variablesData = new UnitExplicitTopLevelVariables(numberOfVariables);
    for (int i = 0; i < numberOfVariables; i++) {
      kernel.Field field = kernelFields[i];

      // Add the explicit variables.
      TopLevelVariableElementImpl variable;
      if (field.isConst && field.initializer != null) {
        variable = new ConstTopLevelVariableElementImpl.forKernel(unit, field);
      } else {
        variable = new TopLevelVariableElementImpl.forKernel(unit, field);
      }
      variablesData.variables[i] = variable;

      // Add the implicit accessors.
      variablesData.implicitAccessors
          .add(new PropertyAccessorElementImpl_ImplicitGetter(variable));
      if (!(variable.isConst || variable.isFinal)) {
        variablesData.implicitAccessors
            .add(new PropertyAccessorElementImpl_ImplicitSetter(variable));
      }
    }
    return variablesData;
  }

  @override
  ConstructorInitializer getConstructorInitializer(
      ConstructorElementImpl constructor, kernel.Initializer k) {
    if (k is kernel.FieldInitializer && k.isSynthetic ||
        k is kernel.SuperInitializer && k.isSynthetic) {
      return null;
    }
    return new _ExprBuilder(this, constructor).buildInitializer(k);
  }

  @override
  Expression getExpression(ElementImpl context, kernel.Expression expression) {
    return new _ExprBuilder(this, context).build(expression);
  }

  @override
  List<List<kernel.VariableDeclaration>> getFunctionTypeParameters(
      kernel.FunctionType type) {
    return KernelResynthesizer._getFunctionTypeParameters(type);
  }

  @override
  InterfaceType getInterfaceType(
      ElementImpl context, kernel.Supertype kernelType) {
    if (kernelType.classNode.isEnum) {
      return null;
    }
    return libraryContext.resynthesizer._getInterfaceType(
        context, kernelType.className.canonicalName, kernelType.typeArguments);
  }

  @override
  List<InterfaceType> getInterfaceTypes(
      ElementImpl context, List<kernel.Supertype> types) {
    var interfaceTypes = <InterfaceType>[];
    for (kernel.Supertype kernelType in types) {
      InterfaceType interfaceType = getInterfaceType(context, kernelType);
      if (interfaceType != null) {
        interfaceTypes.add(interfaceType);
      }
    }
    return interfaceTypes;
  }

  @override
  ConstructorElement getRedirectedConstructor(ElementImpl context,
      kernel.Constructor kernelConstructor, kernel.Procedure kernelFactory) {
    if (kernelConstructor != null) {
      for (var initializer in kernelConstructor.initializers) {
        if (initializer is kernel.RedirectingInitializer) {
          return libraryContext.resynthesizer.getElementFromCanonicalName(
                  initializer.targetReference.canonicalName)
              as ConstructorElementImpl;
        }
      }
    }
    if (kernelFactory != null) {
      kernel.Statement body = kernelFactory.function.body;
      if (body is RedirectingFactoryBody) {
        kernel.Member target = body.target;
        if (target != null) {
          ConstructorElementImpl constructorElement = libraryContext
              .resynthesizer
              .getElementFromCanonicalName(target.reference.canonicalName);
          var typeArguments = body.typeArguments;
          if (typeArguments != null) {
            var classElement = constructorElement.enclosingElement;

            var instantiatedType = new InterfaceTypeImpl.elementWithNameAndArgs(
                classElement, classElement.name, () {
              List<DartType> arguments = typeArguments
                  .map((kernel.DartType k) => getType(context, k))
                  .toList(growable: false);
              return arguments;
            });

            return ConstructorMember.from(constructorElement, instantiatedType);
          } else {
            return constructorElement;
          }
        }
      }
    }
    return null;
  }

  @override
  DartType getType(ElementImpl context, kernel.DartType type) {
    return libraryContext.resynthesizer.getType(context, type);
  }

  ElementAnnotationImpl _buildAnnotation(
      CompilationUnitElementImpl unit, kernel.Expression expression) {
    ElementAnnotationImpl elementAnnotation = new ElementAnnotationImpl(unit);
    Expression constExpr = getExpression(unit, expression);
    if (constExpr is Identifier) {
      elementAnnotation.element = constExpr.staticElement;
      elementAnnotation.annotationAst = AstTestFactory.annotation(constExpr);
    } else if (constExpr is InstanceCreationExpression) {
      elementAnnotation.element = constExpr.staticElement;
      Identifier typeName = constExpr.constructorName.type.name;
      SimpleIdentifier constructorName = constExpr.constructorName.name;
      elementAnnotation.annotationAst = AstTestFactory.annotation2(
          typeName, constructorName, constExpr.argumentList)
        ..element = constExpr.staticElement;
    } else {
      throw new StateError(
          'Unexpected annotation type: ${constExpr.runtimeType}');
    }
    return elementAnnotation;
  }

  /// Fasta converts `native 'name'` clauses to `@ExternalName('name')`
  /// annotations. But we don't actually have these annotations in code. So,
  /// we need to skip them to avoid mismatch with AST.
  static bool _isSyntheticExternalNameAnnotation(kernel.Expression expr) {
    if (expr is kernel.ConstructorInvocation) {
      kernel.Constructor target = expr.target;
      return target != null &&
          target.enclosingClass.name == 'ExternalName' &&
          target.enclosingLibrary.importUri == dartInternalUri;
    }
    return false;
  }
}
