// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_ANDROID_CALLBACK_ANDROID_H_
#define BASE_ANDROID_CALLBACK_ANDROID_H_

#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "base/android/scoped_java_ref.h"
#include "base/base_export.h"
#include "base/functional/callback.h"
#include "base/time/time.h"
#include "base/types/optional_ref.h"
#include "third_party/jni_zero/default_conversions.h"
#include "third_party/jni_zero/jni_zero.h"

// Provides helper utility methods that run the given callback with the
// specified argument.
namespace base::android {

using JniOnceWrappedCallbackType =
    base::OnceCallback<void(const jni_zero::JavaRef<jobject>&)>;
using JniRepeatingWrappedCallbackType =
    base::RepeatingCallback<void(const jni_zero::JavaRef<jobject>&)>;
using JniOnceWrappedCallback2Type =
    base::OnceCallback<void(const jni_zero::JavaRef<jobject>&,
                            const jni_zero::JavaRef<jobject>&)>;
using JniRepeatingWrappedCallback2Type =
    base::RepeatingCallback<void(const jni_zero::JavaRef<jobject>&,
                                 const jni_zero::JavaRef<jobject>&)>;

BASE_EXPORT void RunObjectCallbackAndroid(const JavaRef<jobject>& callback,
                                          const JavaRef<jobject>& arg);

BASE_EXPORT void RunObjectCallbackAndroid2(const JavaRef<jobject>& callback,
                                           const JavaRef<jobject>& arg1,
                                           const JavaRef<jobject>& arg2);

BASE_EXPORT void RunBooleanCallbackAndroid(const JavaRef<jobject>& callback,
                                           bool arg);

BASE_EXPORT void RunIntCallbackAndroid(const JavaRef<jobject>& callback,
                                       int32_t arg);

BASE_EXPORT void RunLongCallbackAndroid(const JavaRef<jobject>& callback,
                                        int64_t arg);

BASE_EXPORT void RunTimeCallbackAndroid(const JavaRef<jobject>& callback,
                                        base::Time time);

BASE_EXPORT void RunStringCallbackAndroid(const JavaRef<jobject>& callback,
                                          const std::string& arg);

BASE_EXPORT void RunOptionalStringCallbackAndroid(
    const JavaRef<jobject>& callback,
    base::optional_ref<const std::string> optional_string_arg);

BASE_EXPORT void RunByteArrayCallbackAndroid(const JavaRef<jobject>& callback,
                                             const std::vector<uint8_t>& arg);

BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    base::OnceClosure&& callback);
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    const base::RepeatingClosure& callback);
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    base::RepeatingClosure&& callback);
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    JniOnceWrappedCallbackType&& callback);
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    JniRepeatingWrappedCallbackType&& callback);
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    const JniRepeatingWrappedCallbackType& callback);
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    JniOnceWrappedCallback2Type&& callback);
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    JniRepeatingWrappedCallback2Type&& callback);
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    const JniRepeatingWrappedCallback2Type& callback);

// Java Callbacks don't return a value so any return value by the passed in
// callback will be ignored.
template <typename R, typename Arg1, typename Arg2>
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    base::OnceCallback<R(Arg1, Arg2)>&& callback) {
  return ToJniCallback(
      env, base::BindOnce(
               [](base::OnceCallback<R(Arg1, Arg2)> captured_callback,
                  const jni_zero::JavaRef<jobject>& j_result1,
                  const jni_zero::JavaRef<jobject>& j_result2) {
                 JNIEnv* env = jni_zero::AttachCurrentThread();
                 std::move(captured_callback)
                     .Run(jni_zero::FromJniType<Arg1>(env, j_result1),
                          jni_zero::FromJniType<Arg2>(env, j_result2));
               },
               std::move(callback)));
}

// Java Callbacks don't return a value so any return value by the passed in
// callback will be ignored.
template <typename R, typename Arg1, typename Arg2>
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    const base::RepeatingCallback<R(Arg1, Arg2)>& callback) {
  return ToJniCallback(
      env,
      base::BindRepeating(
          [](const base::RepeatingCallback<R(Arg1, Arg2)>& captured_callback,
             const jni_zero::JavaRef<jobject>& j_result1,
             const jni_zero::JavaRef<jobject>& j_result2) {
            JNIEnv* env = jni_zero::AttachCurrentThread();
            captured_callback.Run(jni_zero::FromJniType<Arg1>(env, j_result1),
                                  jni_zero::FromJniType<Arg2>(env, j_result2));
          },
          callback));
}

// Java Callbacks don't return a value so any return value by the passed in
// callback will be ignored.
template <typename R, typename Arg>
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    base::OnceCallback<R(Arg)>&& callback) {
  return ToJniCallback(
      env, base::BindOnce(
               [](base::OnceCallback<R(Arg)> captured_callback,
                  const jni_zero::JavaRef<jobject>& j_result) {
                 JNIEnv* env = jni_zero::AttachCurrentThread();
                 std::move(captured_callback)
                     .Run(jni_zero::FromJniType<Arg>(env, j_result));
               },
               std::move(callback)));
}

// Java Callbacks don't return a value so any return value by the passed in
// callback will be ignored.
template <typename R>
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    base::OnceCallback<R()>&& callback) {
  return ToJniCallback(env, base::BindOnce(
                                [](base::OnceCallback<R()> captured_callback,
                                   const jni_zero::JavaRef<jobject>& j_result) {
                                  std::move(captured_callback).Run();
                                },
                                std::move(callback)));
}

// Java Callbacks don't return a value so any return value by the passed in
// callback will be ignored.
template <typename R, typename Arg>
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    const base::RepeatingCallback<R(Arg)>& callback) {
  return ToJniCallback(
      env,
      base::BindRepeating(
          [](const base::RepeatingCallback<R(Arg)>& captured_callback,
             const jni_zero::JavaRef<jobject>& j_result) {
            JNIEnv* env = jni_zero::AttachCurrentThread();
            captured_callback.Run(jni_zero::FromJniType<Arg>(env, j_result));
          },
          callback));
}

// Java Callbacks don't return a value so any return value by the passed in
// callback will be ignored.
template <typename R>
BASE_EXPORT ScopedJavaLocalRef<jobject> ToJniCallback(
    JNIEnv* env,
    const base::RepeatingCallback<R()>& callback) {
  return ToJniCallback(
      env, base::BindRepeating(
               [](const base::RepeatingCallback<R()>& captured_callback,
                  const jni_zero::JavaRef<jobject>& j_result) {
                 captured_callback.Run();
               },
               callback));
}
}  // namespace base::android

namespace base::android::internal {

template <typename T>
struct IsOnceCallback : std::false_type {};

template <>
struct IsOnceCallback<base::OnceCallback<void()>> : std::true_type {};

template <typename T>
struct IsOnceCallback<base::OnceCallback<void(T)>> : std::true_type {
  using ArgType = T;
};

template <typename T1, typename T2>
struct IsOnceCallback<base::OnceCallback<void(T1, T2)>> : std::true_type {
  using Arg1Type = T1;
  using Arg2Type = T2;
};

template <typename T>
struct IsRepeatingCallback : std::false_type {};

template <>
struct IsRepeatingCallback<base::RepeatingCallback<void()>> : std::true_type {};

template <typename T>
struct IsRepeatingCallback<base::RepeatingCallback<void(T)>> : std::true_type {
  using ArgType = T;
};

template <typename T1, typename T2>
struct IsRepeatingCallback<base::RepeatingCallback<void(T1, T2)>>
    : std::true_type {
  using Arg1Type = T1;
  using Arg2Type = T2;
};

template <typename T>
void RunJavaCallback(const jni_zero::ScopedJavaGlobalRef<jobject>& callback,
                     T arg) {
  JNIEnv* env = jni_zero::AttachCurrentThread();
  base::android::RunObjectCallbackAndroid(
      callback, jni_zero::ToJniType(env, std::move(arg)));
}

template <typename T1, typename T2>
void RunJavaCallback2(const jni_zero::ScopedJavaGlobalRef<jobject>& callback,
                      T1 arg1,
                      T2 arg2) {
  JNIEnv* env = jni_zero::AttachCurrentThread();
  base::android::RunObjectCallbackAndroid2(
      callback, jni_zero::ToJniType(env, std::move(arg1)),
      jni_zero::ToJniType(env, std::move(arg2)));
}

}  // namespace base::android::internal

namespace jni_zero {

// @JniType("base::OnceCallback<void(NativeFoo)>") Callback<JavaFoo>
template <typename T>
  requires(base::android::internal::IsOnceCallback<T>::value)
inline T FromJniType(JNIEnv* env, const JavaRef<jobject>& obj) {
  namespace internal = base::android::internal;
  if constexpr (std::same_as<std::remove_cvref_t<T>, base::OnceClosure>) {
    return base::BindOnce(&jni_zero::RunRunnable,
                          jni_zero::ScopedJavaGlobalRef<jobject>(env, obj));
  } else if constexpr (requires {
                         typename internal::IsOnceCallback<T>::ArgType;
                       }) {
    using ArgType = typename internal::IsOnceCallback<T>::ArgType;
    return base::BindOnce(&internal::RunJavaCallback<ArgType>,
                          ScopedJavaGlobalRef<jobject>(env, obj));
  } else {
    using Arg1Type = typename internal::IsOnceCallback<T>::Arg1Type;
    using Arg2Type = typename internal::IsOnceCallback<T>::Arg2Type;
    return base::BindOnce(&internal::RunJavaCallback2<Arg1Type, Arg2Type>,
                          ScopedJavaGlobalRef<jobject>(env, obj));
  }
}

// @JniType("base::RepeatingCallback<void(NativeFoo)>") Callback<JavaFoo>
template <typename T>
  requires(base::android::internal::IsRepeatingCallback<T>::value)
inline T FromJniType(JNIEnv* env, const JavaRef<jobject>& obj) {
  namespace internal = base::android::internal;
  if constexpr (std::same_as<std::remove_cvref_t<T>, base::RepeatingClosure>) {
    return base::BindRepeating(
        &jni_zero::RunRunnable,
        jni_zero::ScopedJavaGlobalRef<jobject>(env, obj));
  } else if constexpr (requires {
                         typename internal::IsRepeatingCallback<T>::ArgType;
                       }) {
    using ArgType = typename internal::IsRepeatingCallback<T>::ArgType;
    return base::BindRepeating(&internal::RunJavaCallback<ArgType>,
                               ScopedJavaGlobalRef<jobject>(env, obj));
  } else {
    using Arg1Type = typename internal::IsRepeatingCallback<T>::Arg1Type;
    using Arg2Type = typename internal::IsRepeatingCallback<T>::Arg2Type;
    return base::BindRepeating(&internal::RunJavaCallback2<Arg1Type, Arg2Type>,
                               ScopedJavaGlobalRef<jobject>(env, obj));
  }
}

template <typename R, typename... Args>
inline ScopedJavaLocalRef<jobject> ToJniType(
    JNIEnv* env,
    base::OnceCallback<R(Args...)>&& callback) {
  return base::android::ToJniCallback(env, std::move(callback));
}

template <>
inline ScopedJavaLocalRef<jobject> ToJniType<base::RepeatingClosure>(
    JNIEnv* env,
    const base::RepeatingClosure& callback) {
  return base::android::ToJniCallback(env, callback);
}

template <typename R, typename... Args>
inline ScopedJavaLocalRef<jobject> ToJniType(
    JNIEnv* env,
    base::RepeatingCallback<R(Args...)>&& callback) {
  return base::android::ToJniCallback(env, std::move(callback));
}

template <typename R, typename... Args>
inline ScopedJavaLocalRef<jobject> ToJniType(
    JNIEnv* env,
    const base::RepeatingCallback<R(Args...)>& callback) {
  return base::android::ToJniCallback(env, callback);
}

}  // namespace jni_zero

#endif  // BASE_ANDROID_CALLBACK_ANDROID_H_
