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

part of "core_patch.dart";

// NOTE: When making changes to this class, please also update
// `VmTarget.instantiateInvocation` and `VmTarget._invocationType` in
// `pkg/kernel/lib/target/vm.dart`.
@pragma('dyn-module:language-impl:callable')
class _InvocationMirror implements Invocation {
  // Constants describing the invocation kind.
  // _FIELD cannot be generated by regular invocation mirrors.
  static const int _UNINITIALIZED = -1;
  static const int _METHOD = 0;
  static const int _GETTER = 1;
  static const int _SETTER = 2;
  static const int _FIELD = 3;
  static const int _LOCAL_VAR = 4;
  static const int _KIND_SHIFT = 0;
  static const int _KIND_BITS = 3;
  static const int _KIND_MASK = (1 << _KIND_BITS) - 1;

  // These values, except _DYNAMIC and _SUPER, are only used when throwing
  // NoSuchMethodError for compile-time resolution failures.
  static const int _DYNAMIC = 0;
  static const int _SUPER = 1;
  static const int _STATIC = 2;
  static const int _CONSTRUCTOR = 3;
  static const int _TOP_LEVEL = 4;
  static const int _LEVEL_SHIFT = _KIND_BITS;
  static const int _LEVEL_BITS = 3;
  static const int _LEVEL_MASK = (1 << _LEVEL_BITS) - 1;

  // ArgumentsDescriptor layout. Keep in sync with enum in dart_entry.h.
  static const int _TYPE_ARGS_LEN = 0;
  static const int _COUNT = 1;
  static const int _SIZE = 2;
  static const int _POSITIONAL_COUNT = 3;
  static const int _FIRST_NAMED_ENTRY = 4;

  // Internal representation of the invocation mirror.
  String? _functionName;
  List<Object?>? _argumentsDescriptor;
  List<Object?>? _arguments;
  bool _isSuperInvocation = false;
  int _delayedTypeArgumentsLen = 0;

  // External representation of the invocation mirror; populated on demand.
  Symbol? _memberName;
  int _type = _UNINITIALIZED;
  List<Type>? _typeArguments;
  List<Object?>? _positionalArguments;
  Map<Symbol, Object?>? _namedArguments;

  @pragma('dyn-module:language-impl:callable')
  _InvocationMirror._withType(
    this._memberName,
    int? type,
    this._typeArguments,
    this._positionalArguments,
    this._namedArguments,
  ) : _type = type ?? _UNINITIALIZED {
    _typeArguments ??= const <Type>[];
    _positionalArguments ??= const [];
    _namedArguments ??= const {};
  }

  void _setMemberNameAndType() {
    final funcName = _functionName!;
    if (_type == _UNINITIALIZED) {
      _type = 0;
    }
    if (funcName.startsWith("get:")) {
      _type |= _GETTER;
      _memberName = internal.Symbol.unvalidated(funcName.substring(4));
    } else if (funcName.startsWith("set:")) {
      _type |= _SETTER;
      _memberName = internal.Symbol.unvalidated(funcName.substring(4) + "=");
    } else {
      _type |=
          _isSuperInvocation ? (_SUPER << _LEVEL_SHIFT) | _METHOD : _METHOD;
      _memberName = internal.Symbol.unvalidated(funcName);
    }
  }

  Symbol get memberName {
    if (_memberName == null) {
      _setMemberNameAndType();
    }
    return _memberName!;
  }

  int get _typeArgsLen {
    var typeArgsLen = _argumentsDescriptor![_TYPE_ARGS_LEN] as int;
    return typeArgsLen == 0 ? _delayedTypeArgumentsLen : typeArgsLen;
  }

  List<Type> get typeArguments {
    if (_typeArguments == null) {
      if (_typeArgsLen == 0) {
        return _typeArguments = const <Type>[];
      }
      // A TypeArguments object does not have a corresponding Dart class and
      // cannot be accessed as an array in Dart. Therefore, we need a native
      // call to unpack the individual types into a list.
      _typeArguments = _unpackTypeArguments(_arguments![0], _typeArgsLen);
    }
    return _typeArguments!;
  }

  // Unpack the given TypeArguments object into a new list of individual types.
  @pragma("vm:external-name", "InvocationMirror_unpackTypeArguments")
  external static List<Type> _unpackTypeArguments(
    typeArguments,
    int numTypeArguments,
  );

  List get positionalArguments {
    if (_positionalArguments == null) {
      // The argument descriptor counts the receiver, but not the type arguments
      // as positional arguments.
      var numPositionalArguments =
          (_argumentsDescriptor![_POSITIONAL_COUNT] as int) - 1;
      if (numPositionalArguments == 0) {
        return _positionalArguments = const [];
      }
      // Exclude receiver and type args in the returned list.
      var receiverIndex = _typeArgsLen > 0 ? 1 : 0;
      var args = _arguments!;
      _positionalArguments = _ImmutableList._from(
        args,
        receiverIndex + 1,
        numPositionalArguments,
      );
    }
    return _positionalArguments!;
  }

  Map<Symbol, dynamic> get namedArguments {
    if (_namedArguments == null) {
      final argsDescriptor = _argumentsDescriptor!;
      var numArguments =
          (argsDescriptor[_COUNT] as int) - 1; // Exclude receiver.
      var numPositionalArguments =
          (argsDescriptor[_POSITIONAL_COUNT] as int) - 1;
      var numNamedArguments = numArguments - numPositionalArguments;
      if (numNamedArguments == 0) {
        return _namedArguments = const {};
      }
      var receiverIndex = _typeArgsLen > 0 ? 1 : 0;
      final namedArguments = Map<Symbol, Object?>();
      for (var i = 0; i < numNamedArguments; i++) {
        var namedEntryIndex = _FIRST_NAMED_ENTRY + 2 * i;
        var pos = argsDescriptor[namedEntryIndex + 1] as int;
        var arg_name = argsDescriptor[namedEntryIndex] as String;
        var arg_value = _arguments![receiverIndex + pos];
        namedArguments[internal.Symbol.unvalidated(arg_name)] = arg_value;
      }
      _namedArguments = Map.unmodifiable(namedArguments);
    }
    return _namedArguments!;
  }

  bool get isMethod {
    if (_type == _UNINITIALIZED) {
      _setMemberNameAndType();
    }
    return (_type & _KIND_MASK) == _METHOD;
  }

  bool get isAccessor {
    if (_type == _UNINITIALIZED) {
      _setMemberNameAndType();
    }
    return (_type & _KIND_MASK) != _METHOD;
  }

  bool get isGetter {
    if (_type == _UNINITIALIZED) {
      _setMemberNameAndType();
    }
    return (_type & _KIND_MASK) == _GETTER;
  }

  bool get isSetter {
    if (_type == _UNINITIALIZED) {
      _setMemberNameAndType();
    }
    return (_type & _KIND_MASK) == _SETTER;
  }

  _InvocationMirror(
    this._functionName,
    this._argumentsDescriptor,
    this._arguments,
    this._isSuperInvocation,
    this._type, [
    this._delayedTypeArgumentsLen = 0,
  ]);

  _InvocationMirror._withoutType(
    this._functionName,
    this._typeArguments,
    this._positionalArguments,
    this._namedArguments,
    this._isSuperInvocation, [
    this._delayedTypeArgumentsLen = 0,
  ]);

  @pragma("vm:entry-point", "call")
  static _allocateInvocationMirror(
    String functionName,
    List argumentsDescriptor,
    List arguments,
    bool isSuperInvocation, [
    int type = _UNINITIALIZED,
  ]) {
    return _InvocationMirror(
      functionName,
      argumentsDescriptor,
      arguments,
      isSuperInvocation,
      type,
    );
  }

  // This factory is used when creating an `Invocation` for a closure call which
  // may have delayed type arguments. In that case, the arguments descriptor will
  // indicate 0 type arguments, but the actual number of type arguments are
  // passed in `delayedTypeArgumentsLen`. If any type arguments are available,
  // the type arguments vector will be the first entry in `arguments`.
  @pragma("vm:entry-point", "call")
  static _allocateInvocationMirrorForClosure(
    String functionName,
    List argumentsDescriptor,
    List arguments,
    int? type,
    int delayedTypeArgumentsLen,
  ) {
    return _InvocationMirror(
      functionName,
      argumentsDescriptor,
      arguments,
      false,
      type ?? _UNINITIALIZED,
      delayedTypeArgumentsLen,
    );
  }
}
