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

#include "base/android/jni_array.h"

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/check_op.h"
#include "base/containers/extend.h"
#include "base/containers/heap_array.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_view_util.h"

namespace base::android {

ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(JNIEnv* env,
                                               const uint8_t* bytes,
                                               size_t len) {
  return ToJavaByteArray(
      env,
      // SAFETY: required from caller, see UNSAFE_BUFFER_USAGE in header.
      UNSAFE_BUFFERS(base::span(bytes, len)));
}

ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(
    JNIEnv* env,
    base::span<const uint8_t> bytes) {
  jbyteArray byte_array = env->NewByteArray(checked_cast<jsize>(bytes.size()));
  CheckException(env);
  DCHECK(byte_array);

  static_assert(sizeof(jbyte) == sizeof(uint8_t));
  static_assert(alignof(jbyte) <= alignof(uint8_t));
  env->SetByteArrayRegion(byte_array, jsize{0},
                          checked_cast<jsize>(bytes.size()),
                          reinterpret_cast<const jbyte*>(bytes.data()));
  CheckException(env);

  return ScopedJavaLocalRef<jbyteArray>::Adopt(env, byte_array);
}

ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(JNIEnv* env,
                                               std::string_view str) {
  return ToJavaByteArray(env, base::as_byte_span(str));
}

ScopedJavaLocalRef<jbooleanArray> ToJavaBooleanArray(
    JNIEnv* env,
    const std::vector<bool>& bools) {
  // Make an actual array of types equivalent to `bool`.
  auto actual_bools = HeapArray<bool>::Uninit(bools.size());
  std::ranges::copy(bools, actual_bools.begin());
  return ToJavaBooleanArray(env, actual_bools);
}

ScopedJavaLocalRef<jbooleanArray> ToJavaBooleanArray(JNIEnv* env,
                                                     span<const bool> bools) {
  jbooleanArray boolean_array =
      env->NewBooleanArray(checked_cast<jsize>(bools.size()));
  CheckException(env);
  DCHECK(boolean_array);

  static_assert(sizeof(jboolean) == sizeof(bool));
  static_assert(alignof(jboolean) <= alignof(bool));
  env->SetBooleanArrayRegion(boolean_array, jsize{0},
                             checked_cast<jsize>(bools.size()),
                             reinterpret_cast<const jboolean*>(bools.data()));
  CheckException(env);

  return ScopedJavaLocalRef<jbooleanArray>::Adopt(env, boolean_array);
}

ScopedJavaLocalRef<jintArray> ToJavaIntArray(JNIEnv* env,
                                             base::span<const int32_t> ints) {
  jintArray int_array = env->NewIntArray(checked_cast<jsize>(ints.size()));
  CheckException(env);
  DCHECK(int_array);

  static_assert(sizeof(jint) == sizeof(int32_t));
  static_assert(alignof(jint) <= alignof(int32_t));
  env->SetIntArrayRegion(int_array, jsize{0}, checked_cast<jsize>(ints.size()),
                         reinterpret_cast<const jint*>(ints.data()));
  CheckException(env);

  return ScopedJavaLocalRef<jintArray>::Adopt(env, int_array);
}

// Returns a new Java long array converted from the given int64_t array.
BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray(
    JNIEnv* env,
    base::span<const int64_t> longs) {
  jlongArray long_array = env->NewLongArray(checked_cast<jsize>(longs.size()));
  CheckException(env);
  DCHECK(long_array);

  static_assert(sizeof(jlong) == sizeof(int64_t));
  static_assert(alignof(jlong) <= alignof(int64_t));
  env->SetLongArrayRegion(long_array, jsize{0},
                          checked_cast<jsize>(longs.size()),
                          reinterpret_cast<const jlong*>(longs.data()));
  CheckException(env);

  return ScopedJavaLocalRef<jlongArray>::Adopt(env, long_array);
}

BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray(
    JNIEnv* env,
    base::span<const float> floats) {
  jfloatArray float_array =
      env->NewFloatArray(checked_cast<jsize>(floats.size()));
  CheckException(env);
  DCHECK(float_array);

  static_assert(sizeof(jfloat) == sizeof(float));
  static_assert(alignof(jfloat) <= alignof(float));
  env->SetFloatArrayRegion(float_array, jsize{0},
                           checked_cast<jsize>(floats.size()),
                           reinterpret_cast<const jfloat*>(floats.data()));
  CheckException(env);

  return ScopedJavaLocalRef<jfloatArray>::Adopt(env, float_array);
}

BASE_EXPORT ScopedJavaLocalRef<jdoubleArray> ToJavaDoubleArray(
    JNIEnv* env,
    base::span<const double> doubles) {
  jdoubleArray double_array =
      env->NewDoubleArray(checked_cast<jsize>(doubles.size()));
  CheckException(env);
  DCHECK(double_array);

  static_assert(sizeof(jdouble) == sizeof(double));
  static_assert(alignof(jdouble) <= alignof(double));
  env->SetDoubleArrayRegion(double_array, jsize{0},
                            checked_cast<jsize>(doubles.size()),
                            reinterpret_cast<const jdouble*>(doubles.data()));
  CheckException(env);

  return ScopedJavaLocalRef<jdoubleArray>::Adopt(env, double_array);
}

BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfObjects(
    JNIEnv* env,
    jclass clazz,
    base::span<const ScopedJavaLocalRef<jobject>> v) {
  jobjectArray joa =
      env->NewObjectArray(checked_cast<jsize>(v.size()), clazz, nullptr);
  CheckException(env);

  for (size_t i = 0; i < v.size(); ++i) {
    env->SetObjectArrayElement(joa, checked_cast<jsize>(i), v[i].obj());
  }
  return ScopedJavaLocalRef<jobjectArray>::Adopt(env, joa);
}

BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfObjects(
    JNIEnv* env,
    base::span<const ScopedJavaLocalRef<jobject>> v) {
  return ToJavaArrayOfObjects(env, jni_zero::g_object_class, v);
}

BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfObjects(
    JNIEnv* env,
    base::span<const ScopedJavaGlobalRef<jobject>> v) {
  jobjectArray joa = env->NewObjectArray(checked_cast<jsize>(v.size()),
                                         jni_zero::g_object_class, nullptr);
  CheckException(env);

  for (size_t i = 0; i < v.size(); ++i) {
    env->SetObjectArrayElement(joa, checked_cast<jsize>(i), v[i].obj());
  }
  return ScopedJavaLocalRef<jobjectArray>::Adopt(env, joa);
}

BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToTypedJavaArrayOfObjects(
    JNIEnv* env,
    base::span<const ScopedJavaLocalRef<jobject>> v,
    jclass type) {
  jobjectArray joa =
      env->NewObjectArray(checked_cast<jsize>(v.size()), type, nullptr);
  CheckException(env);

  for (size_t i = 0; i < v.size(); ++i) {
    env->SetObjectArrayElement(joa, checked_cast<jsize>(i), v[i].obj());
  }
  return ScopedJavaLocalRef<jobjectArray>::Adopt(env, joa);
}

BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToTypedJavaArrayOfObjects(
    JNIEnv* env,
    base::span<const ScopedJavaGlobalRef<jobject>> v,
    jclass type) {
  jobjectArray joa =
      env->NewObjectArray(checked_cast<jsize>(v.size()), type, nullptr);
  CheckException(env);

  for (size_t i = 0; i < v.size(); ++i) {
    env->SetObjectArrayElement(joa, checked_cast<jsize>(i), v[i].obj());
  }
  return ScopedJavaLocalRef<jobjectArray>::Adopt(env, joa);
}

ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray(
    JNIEnv* env,
    base::span<const std::string> v) {
  ScopedJavaLocalRef<jclass> byte_array_clazz = GetClass(env, "[B");
  jobjectArray joa = env->NewObjectArray(checked_cast<jsize>(v.size()),
                                         byte_array_clazz.obj(), nullptr);
  CheckException(env);

  for (size_t i = 0; i < v.size(); ++i) {
    ScopedJavaLocalRef<jbyteArray> byte_array = ToJavaByteArray(env, v[i]);
    env->SetObjectArrayElement(joa, checked_cast<jsize>(i), byte_array.obj());
  }
  return ScopedJavaLocalRef<jobjectArray>::Adopt(env, joa);
}

ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray(
    JNIEnv* env,
    base::span<const std::vector<uint8_t>> v) {
  ScopedJavaLocalRef<jclass> byte_array_clazz = GetClass(env, "[B");
  jobjectArray joa = env->NewObjectArray(checked_cast<jsize>(v.size()),
                                         byte_array_clazz.obj(), nullptr);
  CheckException(env);

  for (size_t i = 0; i < v.size(); ++i) {
    ScopedJavaLocalRef<jbyteArray> byte_array = ToJavaByteArray(env, v[i]);
    env->SetObjectArrayElement(joa, checked_cast<jsize>(i), byte_array.obj());
  }
  return ScopedJavaLocalRef<jobjectArray>::Adopt(env, joa);
}

ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings(
    JNIEnv* env,
    base::span<const std::string> v) {
  jobjectArray joa = env->NewObjectArray(checked_cast<jsize>(v.size()),
                                         jni_zero::g_string_class, nullptr);
  CheckException(env);

  for (size_t i = 0; i < v.size(); ++i) {
    ScopedJavaLocalRef<jstring> item = ConvertUTF8ToJavaString(env, v[i]);
    env->SetObjectArrayElement(joa, checked_cast<jsize>(i), item.obj());
  }
  return ScopedJavaLocalRef<jobjectArray>::Adopt(env, joa);
}

ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStringArray(
    JNIEnv* env,
    base::span<const std::vector<std::string>> vec_outer) {
  ScopedJavaLocalRef<jclass> string_array_clazz =
      GetClass(env, "[Ljava/lang/String;");

  jobjectArray joa = env->NewObjectArray(checked_cast<jsize>(vec_outer.size()),
                                         string_array_clazz.obj(), nullptr);
  CheckException(env);

  for (size_t i = 0; i < vec_outer.size(); ++i) {
    ScopedJavaLocalRef<jobjectArray> inner =
        ToJavaArrayOfStrings(env, vec_outer[i]);
    env->SetObjectArrayElement(joa, checked_cast<jsize>(i), inner.obj());
  }

  return ScopedJavaLocalRef<jobjectArray>::Adopt(env, joa);
}

ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStringArray(
    JNIEnv* env,
    base::span<const std::vector<std::u16string>> vec_outer) {
  ScopedJavaLocalRef<jclass> string_array_clazz =
      GetClass(env, "[Ljava/lang/String;");

  jobjectArray joa = env->NewObjectArray(checked_cast<jsize>(vec_outer.size()),
                                         string_array_clazz.obj(), nullptr);
  CheckException(env);

  for (size_t i = 0; i < vec_outer.size(); ++i) {
    ScopedJavaLocalRef<jobjectArray> inner =
        ToJavaArrayOfStrings(env, vec_outer[i]);
    env->SetObjectArrayElement(joa, checked_cast<jsize>(i), inner.obj());
  }

  return ScopedJavaLocalRef<jobjectArray>::Adopt(env, joa);
}

ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings(
    JNIEnv* env,
    base::span<const std::u16string> v) {
  jobjectArray joa = env->NewObjectArray(checked_cast<jsize>(v.size()),
                                         jni_zero::g_string_class, nullptr);
  CheckException(env);

  for (size_t i = 0; i < v.size(); ++i) {
    ScopedJavaLocalRef<jstring> item = ConvertUTF16ToJavaString(env, v[i]);
    env->SetObjectArrayElement(joa, checked_cast<jsize>(i), item.obj());
  }
  return ScopedJavaLocalRef<jobjectArray>::Adopt(env, joa);
}

void AppendJavaStringArrayToStringVector(JNIEnv* env,
                                         const JavaRef<jobjectArray>& array,
                                         std::vector<std::u16string>* out) {
  DCHECK(out);
  if (!array) {
    return;
  }
  size_t len = SafeGetArrayLength(env, array);
  if (!len) {
    return;
  }
  out->resize(out->size() + len);
  span<std::u16string> back = span(*out).last(len);
  for (size_t i = 0; i < len; ++i) {
    auto str = ScopedJavaLocalRef<jstring>::Adopt(
        env, static_cast<jstring>(env->GetObjectArrayElement(
                 array.obj(), checked_cast<jsize>(i))));
    ConvertJavaStringToUTF16(env, str.obj(), &back[i]);
  }
}

void AppendJavaStringArrayToStringVector(JNIEnv* env,
                                         const JavaRef<jobjectArray>& array,
                                         std::vector<std::string>* out) {
  DCHECK(out);
  if (!array) {
    return;
  }
  size_t len = SafeGetArrayLength(env, array);
  if (!len) {
    return;
  }
  out->resize(out->size() + len);
  span<std::string> back = span(*out).last(len);
  for (size_t i = 0; i < len; ++i) {
    auto str = ScopedJavaLocalRef<jstring>::Adopt(
        env, static_cast<jstring>(env->GetObjectArrayElement(
                 array.obj(), checked_cast<jsize>(i))));
    ConvertJavaStringToUTF8(env, str.obj(), &back[i]);
  }
}

void AppendJavaByteArrayToByteVector(JNIEnv* env,
                                     const JavaRef<jbyteArray>& byte_array,
                                     std::vector<uint8_t>* out) {
  DCHECK(out);
  if (!byte_array) {
    return;
  }
  size_t len = SafeGetArrayLength(env, byte_array);
  if (!len) {
    return;
  }
  out->resize(out->size() + len);
  span<uint8_t> back = span(*out).last(len);

  static_assert(sizeof(jbyte) == sizeof(uint8_t));
  static_assert(alignof(jbyte) <= alignof(uint8_t));
  env->GetByteArrayRegion(byte_array.obj(), jsize{0},
                          checked_cast<jsize>(back.size()),
                          reinterpret_cast<jbyte*>(back.data()));
}

void JavaByteArrayToByteVector(JNIEnv* env,
                               const JavaRef<jbyteArray>& byte_array,
                               std::vector<uint8_t>* out) {
  DCHECK(out);
  DCHECK(byte_array);
  out->clear();
  AppendJavaByteArrayToByteVector(env, byte_array, out);
}

size_t JavaByteArrayToByteSpan(JNIEnv* env,
                               const JavaRef<jbyteArray>& byte_array,
                               base::span<uint8_t> dest) {
  CHECK(byte_array);
  size_t len = SafeGetArrayLength(env, byte_array);
  span<uint8_t> copy_dest = dest.first(len);

  static_assert(sizeof(jbyte) == sizeof(uint8_t));
  static_assert(alignof(jbyte) <= alignof(uint8_t));
  env->GetByteArrayRegion(byte_array.obj(), jsize{0},
                          checked_cast<jsize>(copy_dest.size()),
                          reinterpret_cast<jbyte*>(copy_dest.data()));
  return len;
}

void JavaByteArrayToString(JNIEnv* env,
                           const JavaRef<jbyteArray>& byte_array,
                           std::string* out) {
  DCHECK(out);
  DCHECK(byte_array);

  std::vector<uint8_t> byte_vector;
  JavaByteArrayToByteVector(env, byte_array, &byte_vector);
  out->assign(byte_vector.begin(), byte_vector.end());
}

void JavaBooleanArrayToBoolVector(JNIEnv* env,
                                  const JavaRef<jbooleanArray>& boolean_array,
                                  std::vector<bool>* out) {
  DCHECK(out);
  if (!boolean_array) {
    return;
  }
  size_t len = SafeGetArrayLength(env, boolean_array);
  out->resize(len);
  if (!len) {
    return;
  }
  // SAFETY: `SafeGetArrayLength()` returns the number of elements in the
  // `boolean_array`, though it can return 0 if the array is invalid. So we only
  // call `GetBooleanArrayElements()` when it's positive. Then
  // GetBooleanArrayElements() returns a buffer of the size returned from
  // `SafeGetArrayLength()`.
  span<jboolean> values = UNSAFE_BUFFERS(
      span(env->GetBooleanArrayElements(boolean_array.obj(), nullptr), len));
  for (size_t i = 0; i < values.size(); ++i) {
    (*out)[i] = static_cast<bool>(values[i]);
  }
  env->ReleaseBooleanArrayElements(boolean_array.obj(), values.data(),
                                   JNI_ABORT);
}

void JavaIntArrayToIntVector(JNIEnv* env,
                             const JavaRef<jintArray>& int_array,
                             std::vector<int>* out) {
  DCHECK(out);
  size_t len = SafeGetArrayLength(env, int_array);
  out->resize(len);
  if (!len) {
    return;
  }
  env->GetIntArrayRegion(int_array.obj(), jsize{0}, checked_cast<jsize>(len),
                         out->data());
}

void JavaLongArrayToInt64Vector(JNIEnv* env,
                                const JavaRef<jlongArray>& long_array,
                                std::vector<int64_t>* out) {
  DCHECK(out);
  std::vector<jlong> temp;
  JavaLongArrayToLongVector(env, long_array, &temp);
  out->resize(0);
  Extend(*out, temp);
}

void JavaLongArrayToLongVector(JNIEnv* env,
                               const JavaRef<jlongArray>& long_array,
                               std::vector<jlong>* out) {
  DCHECK(out);
  size_t len = SafeGetArrayLength(env, long_array);
  out->resize(len);
  if (!len) {
    return;
  }
  env->GetLongArrayRegion(long_array.obj(), jsize{0}, checked_cast<jsize>(len),
                          out->data());
}

void JavaFloatArrayToFloatVector(JNIEnv* env,
                                 const JavaRef<jfloatArray>& float_array,
                                 std::vector<float>* out) {
  DCHECK(out);
  size_t len = SafeGetArrayLength(env, float_array);
  out->resize(len);
  if (!len) {
    return;
  }
  env->GetFloatArrayRegion(float_array.obj(), jsize{0},
                           checked_cast<jsize>(len), out->data());
}

void JavaDoubleArrayToDoubleVector(JNIEnv* env,
                                   const JavaRef<jdoubleArray>& double_array,
                                   std::vector<double>* out) {
  DCHECK(out);
  size_t len = SafeGetArrayLength(env, double_array);
  out->resize(len);
  if (!len) {
    return;
  }
  env->GetDoubleArrayRegion(double_array.obj(), jsize{0},
                            checked_cast<jsize>(len), out->data());
}

void JavaArrayOfByteArrayToStringVector(JNIEnv* env,
                                        const JavaRef<jobjectArray>& array,
                                        std::vector<std::string>* out) {
  DCHECK(out);
  size_t len = SafeGetArrayLength(env, array);
  out->resize(len);
  for (size_t i = 0; i < len; ++i) {
    auto bytes_array = ScopedJavaLocalRef<jbyteArray>::Adopt(
        env, static_cast<jbyteArray>(env->GetObjectArrayElement(
                 array.obj(), checked_cast<jsize>(i))));
    size_t bytes_len = SafeGetArrayLength(env, bytes_array);
    // SAFETY: `SafeGetArrayLength()` returns the number of elements in the
    // `boobytes_array`, though it can return 0 if the array is invalid. So we
    // only call `GetByteArrayElements()` when it's positive. Then
    // GetByteArrayElements() returns a buffer of the size returned from
    // `SafeGetArrayLength()`.
    if (!bytes_len) {
      (*out)[i].clear();
      continue;
    }
    span<jbyte> bytes = UNSAFE_BUFFERS(
        span(env->GetByteArrayElements(bytes_array.obj(), nullptr), bytes_len));
    (*out)[i] = base::as_string_view(base::as_bytes(bytes));
    env->ReleaseByteArrayElements(bytes_array.obj(), bytes.data(), JNI_ABORT);
  }
}

void JavaArrayOfByteArrayToBytesVector(JNIEnv* env,
                                       const JavaRef<jobjectArray>& array,
                                       std::vector<std::vector<uint8_t>>* out) {
  DCHECK(out);
  const size_t len = SafeGetArrayLength(env, array);
  out->resize(len);
  for (size_t i = 0; i < len; ++i) {
    auto bytes_array = ScopedJavaLocalRef<jbyteArray>::Adopt(
        env, static_cast<jbyteArray>(env->GetObjectArrayElement(
                 array.obj(), checked_cast<jsize>(i))));
    JavaByteArrayToByteVector(env, bytes_array, &(*out)[i]);
  }
}

void Java2dStringArrayTo2dStringVector(
    JNIEnv* env,
    const JavaRef<jobjectArray>& array,
    std::vector<std::vector<std::u16string>>* out) {
  DCHECK(out);
  size_t len = SafeGetArrayLength(env, array);
  out->resize(len);
  for (size_t i = 0; i < len; ++i) {
    auto strings_array = ScopedJavaLocalRef<jobjectArray>::Adopt(
        env, static_cast<jobjectArray>(env->GetObjectArrayElement(
                 array.obj(), checked_cast<jsize>(i))));

    (*out)[i].clear();
    AppendJavaStringArrayToStringVector(env, strings_array, &(*out)[i]);
  }
}

void Java2dStringArrayTo2dStringVector(
    JNIEnv* env,
    const JavaRef<jobjectArray>& array,
    std::vector<std::vector<std::string>>* out) {
  DCHECK(out);
  size_t len = SafeGetArrayLength(env, array);
  out->resize(len);
  for (size_t i = 0; i < len; ++i) {
    auto strings_array = ScopedJavaLocalRef<jobjectArray>::Adopt(
        env, static_cast<jobjectArray>(env->GetObjectArrayElement(
                 array.obj(), checked_cast<jsize>(i))));

    (*out)[i].clear();
    AppendJavaStringArrayToStringVector(env, strings_array, &(*out)[i]);
  }
}

void JavaArrayOfIntArrayToIntVector(JNIEnv* env,
                                    const JavaRef<jobjectArray>& array,
                                    std::vector<std::vector<int>>* out) {
  DCHECK(out);
  size_t len = SafeGetArrayLength(env, array);
  out->resize(len);
  for (size_t i = 0; i < len; ++i) {
    auto int_array = ScopedJavaLocalRef<jintArray>::Adopt(
        env, static_cast<jintArray>(env->GetObjectArrayElement(
                 array.obj(), checked_cast<jsize>(i))));
    JavaIntArrayToIntVector(env, int_array, &(*out)[i]);
  }
}

}  // namespace base::android
