// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:ffi';
import 'dart:isolate';

import 'package:ffi/ffi.dart';
import 'package:meta/meta.dart' show internal;

import 'accessors.dart';
import 'jni.dart';
import 'jobject.dart';
import 'lang/jstring.dart';
import 'third_party/generated_bindings.dart';
import 'types.dart';
import 'util/jlist.dart';

/// A builder that builds proxy objects that implement one or more interfaces.
///
/// Example:
/// ```dart
/// final implementer = JImplemeneter();
/// Foo.implementIn(implementer, fooImpl);
/// Bar.implementIn(implementer, barImpl);
/// final foobar = implementer.build(Foo.type); // Or `Bar.type`.
/// ```
class JImplementer extends JObject {
  JImplementer.fromReference(super.reference) : super.fromReference();

  static final _class =
      JClass.forName(r'com/github/dart_lang/jni/PortProxyBuilder');

  static final _newId = _class.constructorId(r'(JJ)V');

  static final _new = ProtectedJniExtensions.lookup<
          NativeFunction<
              JniResult Function(Pointer<Void>, JMethodIDPtr,
                  VarArgs<(Int64, Int64)>)>>('globalEnv_NewObject')
      .asFunction<JniResult Function(Pointer<Void>, JMethodIDPtr, int, int)>();

  factory JImplementer() {
    ProtectedJniExtensions.ensureInitialized();
    return JImplementer.fromReference(_new(
            _class.reference.pointer,
            _newId.pointer,
            ProtectedJniExtensions.getCurrentIsolateId(),
            ProtectedJniExtensions.getMainPortId())
        .reference);
  }

  static final _addImplementationId = _class.instanceMethodId(
    r'addImplementation',
    r'(Ljava/lang/String;JJLjava/util/List;)V',
  );

  static final _addImplementation = ProtectedJniExtensions.lookup<
              NativeFunction<
                  JThrowablePtr Function(Pointer<Void>, JMethodIDPtr,
                      VarArgs<(Pointer<Void>, Int64, Int64, Pointer<Void>)>)>>(
          'globalEnv_CallVoidMethod')
      .asFunction<
          JThrowablePtr Function(Pointer<Void>, JMethodIDPtr, Pointer<Void>,
              int, int, Pointer<Void>)>();

  /// Should not be used directly.
  ///
  /// Use `implementIn` from the generated interface instead.
  @internal
  void add(
    String binaryName,
    RawReceivePort port,
    Pointer<NativeFunction<JObjectPtr Function(Int64, JObjectPtr, JObjectPtr)>>
        pointer,
    List<String> asyncMethods,
  ) {
    using((arena) {
      final binaryNameRef =
          (binaryName.toJString()..releasedBy(arena)).reference;
      _addImplementation(
        reference.pointer,
        _addImplementationId.pointer,
        binaryNameRef.pointer,
        port.sendPort.nativePort,
        pointer.address,
        (asyncMethods.map((m) => m.toJString()..releasedBy(arena)).toJList()
              ..releasedBy(arena))
            .reference
            .pointer,
      ).check();
    });
  }

  static final _buildId = _class.instanceMethodId(
    r'build',
    r'()Ljava/lang/Object;',
  );

  static final _build = ProtectedJniExtensions.lookup<
          NativeFunction<
              JniResult Function(
                Pointer<Void>,
                JMethodIDPtr,
              )>>('globalEnv_CallObjectMethod')
      .asFunction<
          JniResult Function(
            Pointer<Void>,
            JMethodIDPtr,
          )>();

  /// Builds an proxy object with the specified [type] that implements all the
  /// added interfaces with the given implementations.
  ///
  /// Releases this implementer.
  T implement<T extends JObject>() {
    return _build(reference.pointer, _buildId.pointer).object<T>();
  }
}
