// 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 kernel.transformations.method_call;

import 'dart:math' as math;

import '../ast.dart';
import '../class_hierarchy.dart';
import '../core_types.dart';
import '../kernel.dart';
import '../visitor.dart';

/// Problems with the method rewrite transformation:
///
/// * Cannot rewrite invocations to things called "call" because of tear-offs
///   and whatnot that when invoked turns into variableName.call(...).
///
/// * Cannot rewrite invocations to things sharing a name with a field because
///   one could have called clazz.fieldName(...).
///
/// * Rewrites will make stacktraces look weird.
///
/// * Rewrites will make noSuchMethod look weird --- e.g. calling a non-existing
///   function foo(a: 42) turns into foo%0%a(42), i.e. the method name has
///   changed ("foo" vs "foo%0%a") and the arguments has changed (named "a" vs
///   positional).
///   NOTE: At least for now this can be fixed by changing the
///   invocation_mirror_patch file. Doing this I can make all dill, language
///   and co19 tests pass!
///
/// Somewhat weird:
///
/// * Inserts methods that redirect to the correct noSuchMethod invocation
///   so that program #1 example below will work.
///   The reason it otherwise wouldn't is that b.foo(499, named1: 88) is
///   rewritten to b.foo%1%named1(499, 88) which is not legal for class B
///   (thus the method would not be create there) but IS legal for Bs parent
///   class (A), so it would be created there. The call will thus go to the
///   super (A) but shouldn't as foo was overwritten in B.
///
/// Program #1 example:
/// class A {
///   foo(required1, { named1: 499}) => print("Hello from class A");
/// }
///
/// class B extends A {
///   foo(required1) => print("Hello from class B");
/// }
///
/// main() {
///   var b = new B();
///   b.foo(499, named1: 88);
/// }
Program transformProgram(Program program, [debug = false]) {
  new MethodCallTransformer(debug).visitProgram(program);
  return program;
}

class MethodCallTransformer extends Transformer {
  /// Keep track of "visited" procedures and constructors to not visit already
  /// visited stuff, nor visit newly created stubs.
  Set<Member> _visited = new Set<Member>();

  /// Some things currently cannot be rewritten. Calls to methods called "call"
  /// (because invoking tear-offs and closures and whatnot becomes .call)
  /// as well as clashes with field names as a field can contain a function
  /// and one can then do a clazz.fieldName(...).
  Set<String> blacklistedSelectors = new Set<String>.from(["call"]);

  /// Map from a "originally named" procedure to the "%original" procedure
  /// for procedures that was moved.
  Map<Procedure, Procedure> _movedBodies = {};

  /// Map from a "originally named" constructor to the "%original"
  /// constructor for constructors that was moved.
  Map<Constructor, Constructor> _movedConstructors = {};

  /// For static method transformations:
  /// Maps a procedure to the mapping of argument signature to procedure stub.
  Map<Procedure, Map<String, Procedure>> _staticProcedureCalls = {};

  /// For constructor transformations:
  /// Maps a constructor to a mapping of argument signature to constructor stub.
  Map<Constructor, Map<String, Constructor>> _constructorCalls = {};

  /// For non-static method transformations:
  /// Maps from method name to the set of legal number of positional arguments.
  Map<String, Set<int>> _methodToLegalPositionalArgumentCount = {};

  /// For non-static method transformations:
  /// Maps from name of method to the set of new target names seen for at least
  /// one instance (i.e. rewriting has been performed from key to all values in
  /// the mapped to set at least once).
  Map<Name, Set<String>> _rewrittenMethods = {};

  /// For non-static method transformations:
  /// Maps a procedure to the mapping of argument signature to procedure stub.
  Map<Procedure, Map<String, Procedure>> _superProcedureCalls = {};

  /// Whether in debug mode, i.e. if we can insert extra print statements for
  /// debugging purposes.
  bool _debug;

  /// For noSuchMethod calls.
  ClassHierarchy hierarchy;
  CoreTypes coreTypes;
  Constructor _invocationMirrorConstructor; // cached
  Procedure _listFrom; // cached

  MethodCallTransformer(this._debug);

  @override
  TreeNode visitProgram(Program node) {
    hierarchy = new ClassHierarchy(node);
    coreTypes = new CoreTypes(node);

    // First move body of all procedures that takes optional positional or named
    // parameters and record which non-static procedure names have optional
    // positional arguments.
    // Do the same for constructors. Then also rewrite constructor initializers
    // using LocalInitializer and sort named arguments in those initializers
    for (final library in node.libraries) {
      for (final procedure in new List<Procedure>.from(library.procedures)) {
        _moveAndTransformProcedure(procedure);
      }

      for (final clazz in library.classes) {
        for (final field in clazz.fields) {
          blacklistedSelectors.add(field.name.name);
        }

        for (final procedure in new List<Procedure>.from(clazz.procedures)) {
          // This call creates new procedures
          _moveAndTransformProcedure(procedure);
          _recordNonStaticProcedureAndVariableArguments(procedure);
        }

        for (final constructor
            in new List<Constructor>.from(clazz.constructors)) {
          // This call creates new constructors
          _moveAndTransformConstructor(constructor);
        }

        for (final constructor in clazz.constructors) {
          _rewriteConstructorInitializations(constructor);
        }
      }
    }

    // Rewrite calls
    node.transformChildren(this);

    // Now for all method calls that was rewritten, make sure those call
    // destinations actually exist, i.e. for each method with a matching name
    // where the called-with-arguments is legal, create a stub
    for (final library in node.libraries) {
      for (final clazz in library.classes) {
        for (final procedure in new List<Procedure>.from(clazz.procedures)) {
          // This call creates new procedures
          _createNeededNonStaticStubs(procedure);
        }
      }
    }

    return node;
  }

  @override
  TreeNode visitProcedure(Procedure node) {
    if (!_visited.contains(node)) {
      _visited.add(node);
      node.transformChildren(this);
    }
    return node;
  }

  @override
  TreeNode visitConstructor(Constructor node) {
    if (!_visited.contains(node)) {
      _visited.add(node);
      node.transformChildren(this);
    }
    return node;
  }

  @override
  TreeNode visitStaticInvocation(StaticInvocation node) {
    node.transformChildren(this);
    if (!_isMethod(node.target)) return node;
    if (!_hasAnyOptionalParameters(node.target.function)) return node;
    if (!_callIsLegal(node.target.function, node.arguments)) return node;

    // Rewrite with let if needed (without named arguments it won't do anything)
    Expression rewrittenNode = _rewriteWithLetAndSort(node, node.arguments);

    // Create/lookup target and set it as the new target
    node.target = _getNewTargetForStaticLikeInvocation(
        node.target, node.arguments, _staticProcedureCalls);

    // Now turn any named parameters into positional parameters
    _turnNamedArgumentsIntoPositional(node.arguments);
    return rewrittenNode;
  }

  @override
  TreeNode visitDirectMethodInvocation(DirectMethodInvocation node) {
    node.transformChildren(this);
    if (!_isMethod(node.target)) return node;
    if (!_hasAnyOptionalParameters(node.target.function)) return node;
    if (!_callIsLegal(node.target.function, node.arguments)) return node;

    // Rewrite with let if needed (without named arguments it won't do anything)
    Expression rewrittenNode = _rewriteWithLetAndSort(node, node.arguments);

    // Create/lookup target and set it as the new target
    node.target = _getNewTargetForStaticLikeInvocation(
        node.target, node.arguments, _superProcedureCalls);

    // Now turn any named parameters into positional parameters instead
    _turnNamedArgumentsIntoPositional(node.arguments);
    return rewrittenNode;
  }

  @override
  TreeNode visitSuperMethodInvocation(SuperMethodInvocation node) {
    // SuperMethodInvocation was changed since I originally wrote this,
    // and now it seems to never be called anyway.
    throw "visitSuperMethodInvocation is not implemented!";
  }

  @override
  TreeNode visitMethodInvocation(MethodInvocation node) {
    node.transformChildren(this);
    final name = node.name.name;

    // Don't renamed calls to methods that clashes in name with a field
    // or is called "call".
    if (blacklistedSelectors.contains(name)) return node;

    // Rewrite with let if needed (without named arguments it won't do anything)
    Expression rewrittenNode = _rewriteWithLetAndSort(node, node.arguments);

    String argumentsSignature = _createArgumentsSignature(node.arguments);
    if (node.arguments.named.isEmpty) {
      // Positional: Don't rewrite if no procedure with that name can be called
      // with a variable number of arguments, or where the number of arguments
      // called with here isn't a legal number of arguments to any such
      // procedure.
      // Note for named arguments: Named arguments are always rewritten
      // (except for 'call' methods) so there's no such check
      final okCounts = _methodToLegalPositionalArgumentCount[name];

      if (okCounts == null ||
          !okCounts.contains(node.arguments.positional.length)) {
        return node;
      }
    }

    // Rewrite this call
    final originalName = node.name;
    node.name = _createName(node.name, argumentsSignature);

    // Remember that we rewrote this call
    _rewrittenMethods
        .putIfAbsent(originalName, () => new Set<String>())
        .add(argumentsSignature);

    // Now turn any named parameters into positional parameters instead
    _turnNamedArgumentsIntoPositional(node.arguments);
    return rewrittenNode;
  }

  @override
  TreeNode visitConstructorInvocation(ConstructorInvocation node) {
    node.transformChildren(this);
    if (!_callIsLegal(node.target.function, node.arguments)) return node;

    Expression rewrittenNode;
    if (node.isConst) {
      // Sort named arguments by name => it's const so there's no side-effects!
      // but DO NOT rewrite with let!
      node.arguments.named.sort((a, b) => a.name.compareTo(b.name));
      rewrittenNode = node;
    } else {
      rewrittenNode = _rewriteWithLetAndSort(node, node.arguments);
    }
    node.target = _getNewTargetForConstructor(node.target, node.arguments);

    // Now turn named parameters into positional parameters instead
    _turnNamedArgumentsIntoPositional(node.arguments);
    return rewrittenNode;
  }

  @override
  TreeNode visitSuperInitializer(SuperInitializer node) {
    // Note that sorting was done in _rewriteConstructorInitializations
    node.transformChildren(this);
    if (!_callIsLegal(node.target.function, node.arguments)) return node;

    node.target = _getNewTargetForConstructor(node.target, node.arguments);

    // Now turn named parameters into positional parameters instead
    _turnNamedArgumentsIntoPositional(node.arguments);
    return node;
  }

  @override
  TreeNode visitRedirectingInitializer(RedirectingInitializer node) {
    // Note that sorting was done in _rewriteConstructorInitializations
    node.transformChildren(this);
    if (!_callIsLegal(node.target.function, node.arguments)) return node;

    node.target = _getNewTargetForConstructor(node.target, node.arguments);

    // Now turn named parameters into positional parameters instead
    _turnNamedArgumentsIntoPositional(node.arguments);
    return node;
  }

  /// Gets the new target for an invocation, using cache or creating a new one.
  ///
  /// Assumes that any let-rewrite, named argument sorting etc has been done
  /// already.
  Procedure _getNewTargetForStaticLikeInvocation(Procedure target,
      Arguments arguments, Map<Procedure, Map<String, Procedure>> cache) {
    final createdProcedures = cache.putIfAbsent(target, () => {});

    // Rewrite target
    final argumentsSignature = _createArgumentsSignature(arguments);
    return createdProcedures[argumentsSignature] ??
        _createAndCacheInvocationProcedure(
            argumentsSignature,
            arguments.positional.length,
            arguments.named.map((e) => e.name).toList(),
            target,
            _movedBodies[target],
            createdProcedures,
            true);
  }

  /// Rewrite the [Argument]s turning named arguments into positional arguments.
  ///
  /// Note that if the [Argument]s does not take any named parameters this
  /// method does nothing.
  void _turnNamedArgumentsIntoPositional(Arguments arguments) {
    for (final named in arguments.named) {
      arguments.positional.add(named.value..parent = arguments);
    }
    arguments.named.clear();
  }

  /// Gets the new target for an invocation, using cache or creating a new one.
  ///
  /// Assumes that any let-rewrite, named argument sorting etc has been done
  /// already.
  Constructor _getNewTargetForConstructor(
      Constructor target, Arguments arguments) {
    if (!_isNotExternal(target)) return target;
    if (!_hasAnyOptionalParameters(target.function)) return target;

    final argumentsSignature = _createArgumentsSignature(arguments);
    final createdConstructor = _constructorCalls.putIfAbsent(target, () => {});
    return createdConstructor[argumentsSignature] ??
        _createAndCacheInvocationConstructor(
            argumentsSignature,
            arguments.positional.length,
            arguments.named.map((e) => e.name).toList(),
            target,
            _movedConstructors[target],
            createdConstructor,
            true);
  }

  /// Create a signature for the [Arguments].
  ///
  /// Assumes that any needed sorting etc has already been done.
  ///
  /// Looks like x%positionalCount%named --- but it shouldn't matter if always
  /// using these methods
  String _createArgumentsSignature(Arguments arguments) {
    String namedString = arguments.named.map((e) => e.name).join("%");
    return "${arguments.positional.length}%$namedString";
  }

  /// Parse the argument signature.
  ///
  /// First element will be the string representation of the number of
  /// positional arguments used.
  /// The rest will be the named arguments, except that with no named arguments
  /// there still is a 2nd entry: the empty string...
  List<String> _parseArgumentsSignature(String argumentsSignature) {
    return argumentsSignature.split("%");
  }

  /// Rewrites an expression with let, replacing expressions in the [Arguments].
  ///
  /// Sorts the named arguments after rewriting with let.
  ///
  /// Note that this method does nothing if there are no named arguments, or the
  /// named arguments list contain only a single named argument as any sorting
  /// would have no effect. As such, the let-rewrite will also have no effect.
  /// In such a case the return value is [original].
  Expression _rewriteWithLetAndSort(Expression original, Arguments arguments) {
    final named = arguments.named;

    // Only bother if names can be unordered
    if (named.length < 2) return original;

    // Rewrite named with let in given order
    Let let;
    for (int i = named.length - 1; i >= 0; i--) {
      VariableDeclaration letDeclaration =
          new VariableDeclaration.forValue(named[i].value);
      named[i].value = new VariableGet(letDeclaration)..parent = arguments;
      let = new Let(letDeclaration, let ?? original);
    }

    // Sort named arguments by name
    named.sort((a, b) => a.name.compareTo(b.name));

    // Now also add the given positional arguments into the let
    final expressions = arguments.positional;
    for (int i = expressions.length - 1; i >= 0; i--) {
      VariableDeclaration letDeclaration =
          new VariableDeclaration.forValue(expressions[i]);
      expressions[i] = new VariableGet(letDeclaration)..parent = arguments;
      let = new Let(letDeclaration, let ?? original);
    }

    return let;
  }

  /// Creates all needed stubs for non static procedures.
  ///
  /// More specifically: If calls have been made to a procedure with the same
  /// name as this procedure with both 1 and 2 arguments, where both of these
  /// are legal inputs to this procedure, create stubs for both of them,
  /// each of which calls with whatever default parameter values are defined for
  /// the non-given arguments.
  void _createNeededNonStaticStubs(Procedure procedure) {
    final incomingCalls = _rewrittenMethods[procedure.name];
    if (incomingCalls != null &&
        procedure.kind == ProcedureKind.Method &&
        !procedure.isStatic) {
      final createdOnSuper = _superProcedureCalls[procedure];
      final names =
          procedure.function.namedParameters.map((e) => e.name).toSet();

      // A procedure with this name was called on at least one object with
      // an argument signature like any in [incomingCalls]
      nextArgumentSignature:
      for (final argumentsSignature in incomingCalls) {
        // Skip if it was created in a super call already
        if (createdOnSuper != null &&
            createdOnSuper.containsKey(argumentsSignature)) {
          continue;
        }

        final elements = _parseArgumentsSignature(argumentsSignature);
        int positional = int.parse(elements[0]);

        if (positional < procedure.function.requiredParameterCount ||
            positional > procedure.function.positionalParameters.length) {
          // We don't take that number of positional parameters!
          // Call noSuchMethod in case anyone called on object with wrong
          // parameters, but where superclass does take these parameters.
          _createNoSuchMethodStub(
              argumentsSignature, positional, elements.sublist(1), procedure);
          continue;
        }

        if (elements.length > 2 || elements[1] != "") {
          // Named: Could the call be for this method?
          for (int i = 1; i < elements.length; i++) {
            String name = elements[i];
            // Using a name that we don't have?
            if (!names.contains(name)) {
              // Call noSuchMethod in case anyone called on object with wrong
              // parameters, but where superclass does take these parameters.
              _createNoSuchMethodStub(argumentsSignature, positional,
                  elements.sublist(1), procedure);
              continue nextArgumentSignature;
            }
          }
        }

        // Potential legal call => make stub
        // Note the ?? here: E.g. contains on list doesn't take optionals so it
        // wasn't moved, but calls were rewritten because contains on string
        // takes either 1 or 2 arguments.
        final destination = _movedBodies[procedure] ?? procedure;
        _createAndCacheInvocationProcedure(argumentsSignature, positional,
            elements.sublist(1), procedure, destination, {}, false);
      }
    }
  }

  /// Records how this procedure can be called (if it is non-static).
  ///
  /// More specifically: Assuming that the procedure given is non-static taking
  /// a variable number of positional parameters, record all number of arguments
  /// that is legal, e.g. foo(int a, [int b]) is legal for 1 and 2 parameters.
  /// If it takes named parameters, remember how many positional there is so
  /// we also know to rewrite calls without the named arguments.
  void _recordNonStaticProcedureAndVariableArguments(Procedure procedure) {
    if (_isMethod(procedure) &&
        !procedure.isStatic &&
        _hasAnyOptionalParameters(procedure.function)) {
      final name = procedure.name.name;
      final okCounts = _methodToLegalPositionalArgumentCount.putIfAbsent(
          name, () => new Set<int>());
      for (int i = procedure.function.requiredParameterCount;
          i <= procedure.function.positionalParameters.length;
          i++) {
        okCounts.add(i);
      }
    }
  }

  /// Move body of procedure to new procedure and call that from this procedure.
  ///
  /// More specifically: For all procedures with optional positional parameters,
  /// or named parameters, create a new procedure without optional positional
  /// parameters and named parameters and move the body of the original
  /// procedure into this new procedure.
  /// Then make the body of the original procedure call the new procedure.
  ///
  /// The idea is that all rewrites should call the moved procedure instead,
  /// bypassing the optional/named arguments entirely.
  void _moveAndTransformProcedure(Procedure procedure) {
    if (_isMethod(procedure) && _hasAnyOptionalParameters(procedure.function)) {
      final function = procedure.function;

      // Create variable lists
      final newParameterDeclarations = <VariableDeclaration>[];
      final newNamedParameterDeclarations = <VariableDeclaration>[];
      final newParameterVariableGets = <Expression>[];
      final targetParameters = function.positionalParameters;
      final targetNamedParameters = function.namedParameters;
      _moveVariableInitialization(
          targetParameters,
          targetNamedParameters,
          newParameterDeclarations,
          newNamedParameterDeclarations,
          newParameterVariableGets,
          procedure.function);

      // Create new procedure looking like the old one
      // (with the old body and parameters)
      FunctionNode functionNode = _createShallowFunctionCopy(function);
      final newProcedure = new Procedure(
          _createOriginalName(procedure), ProcedureKind.Method, functionNode,
          isAbstract: procedure.isAbstract,
          isStatic: procedure.isStatic,
          isConst: procedure.isConst,
          fileUri: procedure.fileUri);

      // Add procedure to the code
      _addMember(procedure, newProcedure);

      // Map moved body
      _movedBodies[procedure] = newProcedure;

      // Transform original procedure
      if (procedure.isAbstract && procedure.function.body == null) {
        // do basically nothing then
        procedure.function.positionalParameters = newParameterDeclarations;
        procedure.function.namedParameters = newNamedParameterDeclarations;
      } else if (procedure.isStatic) {
        final expression = new StaticInvocation(
            newProcedure, new Arguments(newParameterVariableGets));
        final statement = new ReturnStatement(expression)
          ..parent = procedure.function;
        procedure.function.body = statement;
        procedure.function.positionalParameters = newParameterDeclarations;
        procedure.function.namedParameters = newNamedParameterDeclarations;
      } else {
        final expression = new DirectMethodInvocation(new ThisExpression(),
            newProcedure, new Arguments(newParameterVariableGets));
        final statement = new ReturnStatement(expression)
          ..parent = procedure.function;
        procedure.function.body = statement;
        procedure.function.positionalParameters = newParameterDeclarations;
        procedure.function.namedParameters = newNamedParameterDeclarations;
      }

      if (_debug) {
        // Debug flag set: Print something to the terminal before returning to
        // easily detect if rewrites are missing.
        Expression debugPrint = _getPrintExpression(
            "DEBUG! Procedure shouldn't have been called...", procedure);
        procedure.function.body = new Block(
            [new ExpressionStatement(debugPrint), procedure.function.body])
          ..parent = procedure.function;
      }

      // Mark original procedure as seen (i.e. don't transform it further)
      _visited.add(procedure);
    }
  }

  /// Rewrite constructor initializers by introducing variables and sorting.
  ///
  /// For any* [SuperInitializer] or [RedirectingInitializer], extract the
  /// parameters, put them into variables, then sorting the named parameters.
  /// The idea is to sort the named parameters without changing any invocation
  /// order.
  ///
  /// * only with at least 2 named arguments, otherwise sorting would do nothing
  void _rewriteConstructorInitializations(Constructor constructor) {
    if (_isNotExternal(constructor)) {
      // Basically copied from "super_calls.dart"
      List<Initializer> initializers = constructor.initializers;
      int foundIndex = -1;
      Arguments arguments;
      for (int i = initializers.length - 1; i >= 0; --i) {
        Initializer initializer = initializers[i];
        if (initializer is SuperInitializer) {
          foundIndex = i;
          arguments = initializer.arguments;
          break;
        } else if (initializer is RedirectingInitializer) {
          foundIndex = i;
          arguments = initializer.arguments;
          break;
        }
      }
      if (foundIndex == -1) return;

      // Rewrite using variables if using named parameters (so we can sort them)
      // (note that with 1 named it cannot be unsorted so we don't bother)
      if (arguments.named.length < 2) return;

      int argumentCount = arguments.positional.length + arguments.named.length;

      // Make room for [argumentCount] [LocalInitializer]s before the
      // super/redirector call.
      initializers.length += argumentCount;
      initializers.setRange(
          foundIndex + argumentCount, // destination start (inclusive)
          initializers.length, // destination end (exclusive)
          initializers, // source list
          foundIndex); // source start index

      // Fill in the [argumentCount] reserved slots with the evaluation
      // expressions of the arguments to the super/redirector constructor call
      int storeIndex = foundIndex;
      for (int i = 0; i < arguments.positional.length; ++i) {
        var variable =
            new VariableDeclaration.forValue(arguments.positional[i]);
        arguments.positional[i] = new VariableGet(variable)..parent = arguments;
        initializers[storeIndex++] = new LocalInitializer(variable)
          ..parent = constructor;
      }
      for (int i = 0; i < arguments.named.length; ++i) {
        NamedExpression argument = arguments.named[i];
        var variable = new VariableDeclaration.forValue(argument.value);
        arguments.named[i].value = new VariableGet(variable)..parent = argument;
        initializers[storeIndex++] = new LocalInitializer(variable)
          ..parent = constructor;
      }

      // Sort the named arguments
      arguments.named.sort((a, b) => a.name.compareTo(b.name));
    }
  }

  /// Move body of constructor to new one and call that from this one.
  ///
  /// More specifically: For all constructors with optional positional
  /// parameters, or named parameters, create a new constructor without optional
  /// positional parameters and named parameters, and move the body of the
  /// original constructor into this new constructor.
  /// Then make the original constructor redirect to the new constructor.
  ///
  /// The idea is that all rewrites should call the moved constructor instead,
  /// bypassing the optional/named arguments entirely.
  ///
  /// This method is very similar to _moveAndTransformProcedure
  void _moveAndTransformConstructor(Constructor constructor) {
    if (_isNotExternal(constructor) &&
        _hasAnyOptionalParameters(constructor.function)) {
      final function = constructor.function;

      // Create variable lists
      final newParameterDeclarations = <VariableDeclaration>[];
      final newNamedParameterDeclarations = <VariableDeclaration>[];
      final newParameterVariableGets = <Expression>[];
      final targetParameters = function.positionalParameters;
      final targetNamedParameters = function.namedParameters;
      _moveVariableInitialization(
          targetParameters,
          targetNamedParameters,
          newParameterDeclarations,
          newNamedParameterDeclarations,
          newParameterVariableGets,
          constructor.function);

      // Create new constructor looking like the old one
      // (with the old body, parameters and initializers)
      FunctionNode functionNode = _createShallowFunctionCopy(function);
      final newConstructor = new Constructor(functionNode,
          name: _createOriginalName(constructor),
          isConst: constructor.isConst,
          isExternal: constructor.isExternal,
          initializers: constructor.initializers);

      // Add constructor to the code
      _addMember(constructor, newConstructor);

      // Map moved body
      _movedConstructors[constructor] = newConstructor;

      // Transform original constructor
      constructor.function.body = null;
      constructor.function.positionalParameters = newParameterDeclarations;
      constructor.function.namedParameters = newNamedParameterDeclarations;
      constructor.initializers = [
        new RedirectingInitializer(
            newConstructor, new Arguments(newParameterVariableGets))
          ..parent = constructor
      ];

      if (_debug) {
        // Debug flag set: Print something to the terminal before returning to
        // easily detect if rewrites are missing.
        Expression debugPrint = _getPrintExpression(
            "DEBUG! Constructor shouldn't have been called...", constructor);
        var variable = new VariableDeclaration.forValue(debugPrint);
        final debugInitializer = new LocalInitializer(variable)
          ..parent = constructor;
        final redirector = constructor.initializers[0];
        constructor.initializers = [debugInitializer, redirector];
      }

      // Mark original procedure as seen (i.e. don't transform it further)
      _visited.add(constructor);
    }
  }

  /// Creates a new [FunctionNode] based on the given one.
  ///
  /// Parameters are taken directly (i.e. after returning the parameters will
  /// have a new parent (the returned value), but still be referenced in the
  /// original [FunctionNode].
  /// The same goes for the body of the function.
  /// The caller should take steps to remedy this after this call.
  ///
  /// The parameters are no longer optional and named parameters have been
  /// sorted and turned into regular parameters in the returned [FunctionNode].
  FunctionNode _createShallowFunctionCopy(FunctionNode function) {
    final newParameters =
        new List<VariableDeclaration>.from(function.positionalParameters);
    final named = new List<VariableDeclaration>.from(function.namedParameters);
    named.sort((a, b) => a.name.compareTo(b.name));
    newParameters.addAll(named);
    final functionNode = new FunctionNode(function.body,
        positionalParameters: newParameters,
        namedParameters: [],
        requiredParameterCount: newParameters.length,
        returnType: function.returnType,
        asyncMarker: function.asyncMarker,
        dartAsyncMarker: function.dartAsyncMarker);
    return functionNode;
  }

  /// Creates new variables, moving old initializers into them
  ///
  /// Specifically: Given lists for output, create new variables based on
  /// original parameters. Any new variable will receive the original variables
  /// initializer, and the original variable will have its initializer set to
  /// null.
  /// Named parameters have been sorted in [newParameterVariableGets].
  void _moveVariableInitialization(
      List<VariableDeclaration> originalParameters,
      List<VariableDeclaration> originalNamedParameters,
      List<VariableDeclaration> newParameterDeclarations,
      List<VariableDeclaration> newNamedParameterDeclarations,
      List<Expression> newParameterVariableGets,
      TreeNode newStuffParent) {
    for (final orgVar in originalParameters) {
      final variableDeclaration = new VariableDeclaration(orgVar.name,
          initializer: orgVar.initializer,
          type: orgVar.type,
          isFinal: orgVar.isFinal,
          isConst: orgVar.isConst)..parent = newStuffParent;
      variableDeclaration.initializer?.parent = variableDeclaration;
      newParameterDeclarations.add(variableDeclaration);
      orgVar.initializer = null;
      newParameterVariableGets.add(new VariableGet(variableDeclaration));
    }

    // Named expressions in newParameterVariableGets should be sorted
    final tmp = new List<_Pair<String, Expression>>();
    for (final orgVar in originalNamedParameters) {
      final variableDeclaration = new VariableDeclaration(orgVar.name,
          initializer: orgVar.initializer,
          type: orgVar.type,
          isFinal: orgVar.isFinal,
          isConst: orgVar.isConst)..parent = newStuffParent;
      variableDeclaration.initializer?.parent = variableDeclaration;
      newNamedParameterDeclarations.add(variableDeclaration);
      orgVar.initializer = null;
      tmp.add(new _Pair(orgVar.name, new VariableGet(variableDeclaration)));
    }
    tmp.sort((a, b) => a.key.compareTo(b.key));
    for (final item in tmp) {
      newParameterVariableGets.add(item.value);
    }
  }

  /// Creates a stub redirecting to noSuchMethod.
  ///
  /// Needed because if B extends A, both have a foo method, but taking
  /// different optional parameters, a call on an instance of B with parameters
  /// for A should actually result in a noSuchMethod call, but if only A has
  /// the rewritten method name, that method will be called...
  /// TODO: We only have to create these stubs for arguments that a procedures
  /// super allows, otherwise it will become a noSuchMethod automatically!
  Procedure _createNoSuchMethodStub(
      String argumentsSignature,
      int positionalCount,
      List<String> givenNamedParameters,
      Procedure existing) {
    // Build parameter lists
    final newParameterDeclarations = <VariableDeclaration>[];
    final newParameterVariableGets = <Expression>[];
    for (int i = 0; i < positionalCount + givenNamedParameters.length; i++) {
      final variableDeclaration = new VariableDeclaration("v%$i");
      newParameterDeclarations.add(variableDeclaration);
      newParameterVariableGets.add(new VariableGet(variableDeclaration));
    }

    var procedureName = _createName(existing.name, argumentsSignature);

    // Find noSuchMethod to call
    Member noSuchMethod = hierarchy.getDispatchTarget(
        existing.enclosingClass, new Name("noSuchMethod"));
    Arguments argumentsToNoSuchMethod;

    if (noSuchMethod.function.positionalParameters.length == 1 &&
        noSuchMethod.function.namedParameters.isEmpty) {
      // We have a correct noSuchMethod method.
      ConstructorInvocation invocation = _createInvocation(
          procedureName.name, new Arguments(newParameterVariableGets));
      argumentsToNoSuchMethod = new Arguments([invocation]);
    } else {
      // Get noSuchMethod on Object then...
      noSuchMethod = hierarchy.getDispatchTarget(
          hierarchy.rootClass, new Name("noSuchMethod"));
      ConstructorInvocation invocation = _createInvocation(
          procedureName.name, new Arguments(newParameterVariableGets));
      ConstructorInvocation invocationPrime =
          _createInvocation("noSuchMethod", new Arguments([invocation]));
      argumentsToNoSuchMethod = new Arguments([invocationPrime]);
    }

    // Create return statement to call noSuchMethod
    ReturnStatement statement;
    final expression = new DirectMethodInvocation(
        new ThisExpression(), noSuchMethod, argumentsToNoSuchMethod);
    statement = new ReturnStatement(expression);

    // Build procedure
    final functionNode = new FunctionNode(statement,
        positionalParameters: newParameterDeclarations,
        namedParameters: [],
        requiredParameterCount: newParameterDeclarations.length,
        returnType: existing.function.returnType,
        asyncMarker: existing.function.asyncMarker,
        dartAsyncMarker: existing.function.dartAsyncMarker);
    final procedure = new Procedure(
        procedureName, ProcedureKind.Method, functionNode,
        isStatic: existing.isStatic, fileUri: existing.fileUri);

    // Add procedure to the code
    _addMember(existing, procedure);

    // Mark the new procedure as visited already (i.e. don't rewrite it again!)
    _visited.add(procedure);

    return procedure;
  }

  /// Creates an "new _InvocationMirror(...)" invocation.
  ConstructorInvocation _createInvocation(
      String methodName, Arguments callArguments) {
    if (_invocationMirrorConstructor == null) {
      Class clazz = coreTypes.getClass('dart:core', '_InvocationMirror');
      _invocationMirrorConstructor = clazz.constructors[0];
    }

    // The _InvocationMirror constructor takes the following arguments:
    // * Method name (a string).
    // * An arguments descriptor - a list consisting of:
    //   - number of arguments (including receiver).
    //   - number of positional arguments (including receiver).
    //   - pairs (2 entries in the list) of
    //     * named arguments name.
    //     * index of named argument in arguments list.
    // * A list of arguments, where the first ones are the positional arguments.
    // * Whether it's a super invocation or not.

    int numPositionalArguments = callArguments.positional.length + 1;
    int numArguments = numPositionalArguments + callArguments.named.length;
    List<Expression> argumentsDescriptor = [
      new IntLiteral(numArguments),
      new IntLiteral(numPositionalArguments)
    ];
    List<Expression> arguments = [];
    arguments.add(new ThisExpression());
    for (Expression pos in callArguments.positional) {
      arguments.add(pos);
    }
    for (NamedExpression named in callArguments.named) {
      argumentsDescriptor.add(new StringLiteral(named.name));
      argumentsDescriptor.add(new IntLiteral(arguments.length));
      arguments.add(named.value);
    }

    return new ConstructorInvocation(
        _invocationMirrorConstructor,
        new Arguments([
          new StringLiteral(methodName),
          _fixedLengthList(argumentsDescriptor),
          _fixedLengthList(arguments),
          new BoolLiteral(false)
        ]));
  }

  /// Create a fixed length list containing given expressions.
  Expression _fixedLengthList(List<Expression> list) {
    _listFrom ??= coreTypes.getMember('dart:core', 'List', 'from');
    return new StaticInvocation(
        _listFrom,
        new Arguments([new ListLiteral(list)],
            named: [new NamedExpression("growable", new BoolLiteral(false))],
            types: [const DynamicType()]));
  }

  /// Creates a new procedure taking given arguments, caching it.
  ///
  /// Copies any non-given default values for parameters into the new procedure
  /// to be able to call the [realTarget] without using optionals and named
  /// parameters.
  Procedure _createAndCacheInvocationProcedure(
      String argumentsSignature,
      int positionalCount,
      List<String> givenNamedParameters,
      Procedure target,
      Procedure realTarget,
      Map<String, Procedure> createdProcedures,
      bool doSpecialCaseForAllParameters) {
    // Special case: Calling with all parameters
    if (doSpecialCaseForAllParameters &&
        positionalCount == target.function.positionalParameters.length &&
        givenNamedParameters.length == target.function.namedParameters.length) {
      // We don't cache this procedure as this could make it look like
      // something with name argumentsSignature actually exists
      // while it doesn't (which is bad as we could then decide that we don't
      // need to create a stub even though we do!)
      return realTarget;
    }

    // Create and cache (save) constructor

    // Build parameter lists
    final newParameterDeclarations = <VariableDeclaration>[];
    final newParameterVariableGets = <Expression>[];
    _extractAndCreateParameters(positionalCount, newParameterDeclarations,
        newParameterVariableGets, target, givenNamedParameters);

    // Create return statement to call real target
    ReturnStatement statement;
    if (target.isAbstract && target.function?.body == null) {
      // statement should just be null then
    } else if (target.isStatic) {
      final expression = new StaticInvocation(
          realTarget, new Arguments(newParameterVariableGets));
      statement = new ReturnStatement(expression);
    } else {
      final expression = new DirectMethodInvocation(new ThisExpression(),
          realTarget, new Arguments(newParameterVariableGets));
      statement = new ReturnStatement(expression);
    }

    // Build procedure
    final functionNode = new FunctionNode(statement,
        positionalParameters: newParameterDeclarations,
        namedParameters: [],
        requiredParameterCount: newParameterDeclarations.length,
        returnType: target.function.returnType,
        asyncMarker: target.function.asyncMarker,
        dartAsyncMarker: target.function.dartAsyncMarker);
    final procedure = new Procedure(
        _createName(target.name, argumentsSignature),
        ProcedureKind.Method,
        functionNode,
        isAbstract: target.isAbstract,
        isStatic: target.isStatic,
        isConst: target.isConst,
        fileUri: target.fileUri);

    // Add procedure to the code
    _addMember(target, procedure);

    // Cache it for future reference
    createdProcedures[argumentsSignature] = procedure;

    // Mark the new procedure as visited already (i.e. don't rewrite it again!)
    _visited.add(procedure);

    return procedure;
  }

  /// Creates a new constructor taking given arguments, caching it.
  ///
  /// Copies any non-given default values for parameters into the new
  /// constructor to be able to call the [realTarget] without using optionals
  /// and named parameters.
  Constructor _createAndCacheInvocationConstructor(
      String argumentsSignature,
      int positionalCount,
      List<String> givenNamedParameters,
      Constructor target,
      Constructor realTarget,
      Map<String, Constructor> createdConstructor,
      bool doSpecialCaseForAllParameters) {
    // Special case: Calling with all parameters
    if (doSpecialCaseForAllParameters &&
        positionalCount == target.function.positionalParameters.length &&
        givenNamedParameters.length == target.function.namedParameters.length) {
      createdConstructor[argumentsSignature] = realTarget;
      return realTarget;
    }

    // Create and cache (save) constructor

    // Build parameter lists
    final newParameterDeclarations = <VariableDeclaration>[];
    final newParameterVariableGets = <Expression>[];
    _extractAndCreateParameters(positionalCount, newParameterDeclarations,
        newParameterVariableGets, target, givenNamedParameters);

    // Build constructor
    final functionNode = new FunctionNode(null,
        positionalParameters: newParameterDeclarations,
        namedParameters: [],
        requiredParameterCount: newParameterDeclarations.length,
        returnType: target.function.returnType,
        asyncMarker: target.function.asyncMarker,
        dartAsyncMarker: target.function.dartAsyncMarker);
    final constructor = new Constructor(functionNode,
        name: _createName(target.name, argumentsSignature),
        isConst: target.isConst,
        isExternal: target.isExternal,
        initializers: [
          new RedirectingInitializer(
              realTarget, new Arguments(newParameterVariableGets))
        ]);

    // Add procedure to the code
    _addMember(target, constructor);

    // Cache it for future reference
    createdConstructor[argumentsSignature] = constructor;

    // Mark the new procedure as visited already (i.e. don't rewrite it again!)
    _visited.add(constructor);

    return constructor;
  }

  /// Extracts and creates parameters into the first two given lists.
  ///
  /// What is done:
  /// Step 1: Re-create the parameters given (i.e. the non-optional positional
  /// ones) - i.e. create a new variable with the same name etc, put it in
  /// [newParameterDeclarations]; create VariableGet for that and put it in
  /// [newParameterVariableGets]
  /// Step 2: Re-create the positional parameters NOT given, i.e. insert
  /// defaults and add to [newParameterVariableGets] only.
  /// Step 3: Re-create the named arguments (in sorted order). For actually
  /// given named parameters, do as in step 1, for not-given named parameters
  /// do as in step 2.
  ///
  /// NOTE: [newParameterDeclarations] and [newParameterVariableGets] are OUTPUT
  /// lists.
  void _extractAndCreateParameters(
      int positionalCount,
      List<VariableDeclaration> newParameterDeclarations,
      List<Expression> newParameterVariableGets,
      Member target,
      List<String> givenNamedParameters) {
    // First re-create the parameters given (i.e. the non-optional positional ones)
    final targetParameters = target.function.positionalParameters;
    positionalCount = math.min(positionalCount, targetParameters.length);
    for (int i = 0; i < positionalCount; i++) {
      final orgVar = targetParameters[i];
      final variableDeclaration = new VariableDeclaration(orgVar.name,
          type: orgVar.type, isFinal: orgVar.isFinal, isConst: orgVar.isConst);
      newParameterDeclarations.add(variableDeclaration);
      newParameterVariableGets.add(new VariableGet(variableDeclaration));
    }

    // Default parameters for the rest of them
    _fillInPositionalParameters(
        positionalCount, target, newParameterVariableGets);

    // Then all named parameters (given here or not)
    final orgNamed =
        new List<VariableDeclaration>.from(target.function.namedParameters);
    orgNamed.sort((a, b) => a.name.compareTo(b.name));
    final givenArgumentsIterator = givenNamedParameters.iterator;
    givenArgumentsIterator.moveNext();
    for (VariableDeclaration named in orgNamed) {
      if (givenArgumentsIterator.current == named.name) {
        // We have that one: Use it and move the iterator
        final variableDeclaration = new VariableDeclaration(named.name);
        newParameterDeclarations.add(variableDeclaration);
        newParameterVariableGets.add(new VariableGet(variableDeclaration));
        givenArgumentsIterator.moveNext();
      } else {
        // We don't have that one: Fill it in
        _fillInSingleParameter(named, newParameterVariableGets, target);
      }
    }
  }

  /// Adds the new member the same place as the existing member
  void _addMember(Member existingMember, Member newMember) {
    if (existingMember.enclosingClass != null) {
      existingMember.enclosingClass.addMember(newMember);
    } else {
      existingMember.enclosingLibrary.addMember(newMember);
    }
  }

  /// Create expressions based on the default values from the given [Member].
  ///
  /// More specifically: static gets and nulls will be "copied" whereas other
  /// things (e.g. literals or things like "a+b") will be moved from the
  /// original member as argument initializers to const fields and both the
  /// original member and the expression-copy will use static gets to these.
  void _fillInPositionalParameters(
      int startFrom, Member copyFrom, List<Expression> fillInto) {
    final targetParameters = copyFrom.function.positionalParameters;
    for (int i = startFrom; i < targetParameters.length; i++) {
      final parameter = targetParameters[i];
      _fillInSingleParameter(parameter, fillInto, copyFrom);
    }
  }

  /// Create expression based on the default values from the given variable.
  ///
  /// More specifically: a static get or null will be "copied" whereas other
  /// things (e.g. literals or things like "a+b") will be moved from the
  /// original member as an argument initializer to a const field and both the
  /// original member and the expression-copy will use a static get to it.
  void _fillInSingleParameter(VariableDeclaration parameter,
      List<Expression> fillInto, Member copyFrom) {
    if (parameter.initializer is StaticGet) {
      // Reference to const => recreate it
      StaticGet staticGet = parameter.initializer;
      fillInto.add(new StaticGet(staticGet.target));
    } else if (parameter.initializer == null) {
      // No default given => output null
      fillInto.add(new NullLiteral());
    } else if (parameter.initializer is IntLiteral) {
      // Int literal => recreate (or else class ByteBuffer in typed_data will
      // get 2 fields and the C++ code will complain!)
      IntLiteral value = parameter.initializer;
      fillInto.add(new IntLiteral(value.value));
    } else {
      // Advanced stuff => move to static const field and reference that
      final initializer = parameter.initializer;
      final f = new Field(
          new Name('${copyFrom.name.name}%_${parameter.name}',
              copyFrom.enclosingLibrary),
          type: parameter.type,
          initializer: initializer,
          isFinal: false,
          isConst: true,
          isStatic: true,
          fileUri: copyFrom.enclosingClass?.fileUri ??
              copyFrom.enclosingLibrary.fileUri);
      initializer.parent = f;

      // Add field to the code
      if (copyFrom.enclosingClass != null) {
        copyFrom.enclosingClass.addMember(f);
      } else {
        copyFrom.enclosingLibrary.addMember(f);
      }

      // Use it at the call site
      fillInto.add(new StaticGet(f));

      // Now replace the initializer in the method to a StaticGet
      parameter.initializer = new StaticGet(f)..parent = parameter;
    }
  }

  /// Create an "original name" for a member.
  ///
  /// Specifically, for a member "x" just returns "x%original";
  Name _createOriginalName(Member member) {
    return new Name("${member.name.name}%original", member.enclosingLibrary);
  }

  /// Create a [Name] based on current name and argument signature.
  Name _createName(Name name, String argumentsSignature) {
    String nameString = '${name.name}%$argumentsSignature';
    return new Name(nameString, name.library);
  }

  /// Is the procedure a method?
  bool _isMethod(Procedure procedure) => procedure.kind == ProcedureKind.Method;

  /// Is the procedure NOT marked as external?
  bool _isNotExternal(Constructor constructor) => !constructor.isExternal;

  /// Does the target function have any optional arguments? (positional/named)
  bool _hasAnyOptionalParameters(FunctionNode targetFunction) =>
      _hasOptionalParameters(targetFunction) ||
      _hasNamedParameters(targetFunction);

  /// Does the target function have optional positional arguments?
  bool _hasOptionalParameters(FunctionNode targetFunction) =>
      targetFunction.positionalParameters.length >
      targetFunction.requiredParameterCount;

  /// Does the target function have named parameters?
  bool _hasNamedParameters(FunctionNode targetFunction) =>
      targetFunction.namedParameters.isNotEmpty;

  bool _callIsLegal(FunctionNode targetFunction, Arguments arguments) {
    if ((targetFunction.requiredParameterCount > arguments.positional.length) ||
        (targetFunction.positionalParameters.length <
            arguments.positional.length)) {
      // Given too few or too many positional arguments
      return false;
    }

    // Do we give named that we don't take?
    Set<String> givenNamed = arguments.named.map((v) => v.name).toSet();
    Set<String> takenNamed =
        targetFunction.namedParameters.map((v) => v.name).toSet();
    givenNamed.removeAll(takenNamed);
    return givenNamed.isEmpty;
  }

  // Below methods used to add debug prints etc

  Library _getDartCoreLibrary(Program program) {
    if (program == null) return null;
    return program.libraries.firstWhere((lib) =>
        lib.importUri.scheme == 'dart' && lib.importUri.path == 'core');
  }

  Procedure _getProcedureInLib(Library lib, String name) {
    if (lib == null) return null;
    return lib.procedures
        .firstWhere((procedure) => procedure.name.name == name);
  }

  Procedure _getProcedureInClassInLib(
      Library lib, String className, String procedureName) {
    if (lib == null) return null;
    Class clazz = lib.classes.firstWhere((clazz) => clazz.name == className);
    return clazz.procedures
        .firstWhere((procedure) => procedure.name.name == procedureName);
  }

  Expression _getPrintExpression(String msg, TreeNode treeNode) {
    TreeNode program = treeNode;
    while (program is! Program) program = program.parent;
    var finalMsg = msg;
    if (treeNode is Member) {
      finalMsg += " [ ${treeNode.name.name} ]";
      if (treeNode.enclosingClass != null) {
        finalMsg += " [ class ${treeNode.enclosingClass.name} ]";
      }
      if (treeNode.enclosingLibrary != null) {
        finalMsg += " [ lib ${treeNode.enclosingLibrary.name} ]";
      }
    }

    var stacktrace = new StaticGet(_getProcedureInClassInLib(
        _getDartCoreLibrary(program), 'StackTrace', 'current'));
    var printStackTrace = new StaticInvocation(
        _getProcedureInLib(_getDartCoreLibrary(program), 'print'),
        new Arguments([
          new StringConcatenation([
            new StringLiteral(finalMsg),
            new StringLiteral("\n"),
            stacktrace,
            new StringLiteral("\n")
          ])
        ]));

    return printStackTrace;
  }
}

class _Pair<K, V> {
  final K key;
  final V value;

  _Pair(this.key, this.value);
}
