// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.

// THIS FILE WAS AUTO-GENERATED BY ./ntcore/generate_topics.py. DO NOT MODIFY

#include <jni.h>

#include "wpi/util/jni_util.hpp"

#include "org_wpilib_networktables_NetworkTablesJNI.h"
#include "wpi/nt/ntcore.h"

using namespace wpi::util::java;

//
// Globals and load/unload
//
{% for t in types %}
static JClass timestamped{{ t.TypeName }}Cls;
{%- endfor %}
{%- for t in types %}
{%- if t.jni.ToJavaArray == "MakeJObjectArray" %}
static JClass {{ t.jni.jtype }}Cls;
{%- endif %}
{%- endfor %}
static JException illegalArgEx;
static JException indexOobEx;
static JException nullPointerEx;

static const JClassInit classes[] = {
{%- for t in types %}
    {"org/wpilib/networktables/Timestamped{{ t.TypeName }}", &timestamped{{ t.TypeName }}Cls},
{%- endfor %}
{%- for t in types %}
{%- if t.jni.ToJavaArray == "MakeJObjectArray" %}
    {"{{ t.jni.jtypestr }}", &{{ t.jni.jtype }}Cls},
{%- endif %}
{%- endfor %}
};

static const JExceptionInit exceptions[] = {
    {"java/lang/IllegalArgumentException", &illegalArgEx},
    {"java/lang/IndexOutOfBoundsException", &indexOobEx},
    {"java/lang/NullPointerException", &nullPointerEx},
};

namespace wpi::nt {

bool JNI_LoadTypes(JNIEnv* env) {
  // Cache references to classes
  for (auto& c : classes) {
    *c.cls = JClass(env, c.name);
    if (!*c.cls) {
      return false;
    }
  }

  for (auto& c : exceptions) {
    *c.cls = JException(env, c.name);
    if (!*c.cls) {
      return false;
    }
  }

  return true;
}

void JNI_UnloadTypes(JNIEnv* env) {
  // Delete global references
  for (auto& c : classes) {
    c.cls->free(env);
  }
  for (auto& c : exceptions) {
    c.cls->free(env);
  }
}

}  // namespace wpi::nt

static std::vector<int> FromJavaBooleanArray(JNIEnv* env, jbooleanArray jarr) {
  CriticalJSpan<const jboolean> ref{env, jarr};
  if (!ref) {
    return {};
  }
  std::span<const jboolean> elements{ref};
  size_t len = elements.size();
  std::vector<int> arr;
  arr.reserve(len);
  for (size_t i = 0; i < len; ++i) {
    arr.push_back(elements[i]);
  }
  return arr;
}

static std::vector<std::string> FromJavaStringArray(JNIEnv* env, jobjectArray jarr) {
  size_t len = env->GetArrayLength(jarr);
  std::vector<std::string> arr;
  arr.reserve(len);
  for (size_t i = 0; i < len; ++i) {
    JLocal<jstring> elem{
        env, static_cast<jstring>(env->GetObjectArrayElement(jarr, i))};
    if (!elem) {
      return {};
    }
    arr.emplace_back(JStringRef{env, elem}.str());
  }
  return arr;
}

{% for t in types %}
static jobject MakeJObject(JNIEnv* env, wpi::nt::Timestamped{{ t.TypeName }} value) {
  static jmethodID constructor = env->GetMethodID(
      timestamped{{ t.TypeName }}Cls, "<init>", "(JJ{{ t.jni.jtypestr }})V");
{%- if t.jni.JavaObject %}
  JLocal<{{ t.jni.jtype }}> val{env, {{ t.jni.ToJavaBegin }}value.value{{ t.jni.ToJavaEnd }}};
  return env->NewObject(timestamped{{ t.TypeName }}Cls, constructor,
                        static_cast<jlong>(value.time),
                        static_cast<jlong>(value.serverTime), val.obj());
{%- else %}
  return env->NewObject(timestamped{{ t.TypeName }}Cls, constructor,
                        static_cast<jlong>(value.time),
                        static_cast<jlong>(value.serverTime),
                        {{ t.jni.ToJavaBegin }}value.value{{ t.jni.ToJavaEnd }});
{%- endif %}
}
{% endfor %}
{%- for t in types %}
static jobjectArray MakeJObject(JNIEnv* env,
                                std::span<const wpi::nt::Timestamped{{ t.TypeName }}> arr) {
  jobjectArray jarr =
      env->NewObjectArray(arr.size(), timestamped{{ t.TypeName }}Cls, nullptr);
  if (!jarr) {
    return nullptr;
  }
  for (size_t i = 0; i < arr.size(); ++i) {
    JLocal<jobject> elem{env, MakeJObject(env, arr[i])};
    env->SetObjectArrayElement(jarr, i, elem.obj());
  }
  return jarr;
}
{% endfor %}
{%- for t in types %}
{%- if t.jni.ToJavaArray == "MakeJObjectArray" %}
static jobjectArray MakeJObjectArray(JNIEnv* env, std::span<const {{ t.cpp.ValueType }}> arr) {
  jobjectArray jarr =
      env->NewObjectArray(arr.size(), {{ t.jni.jtype }}Cls, nullptr);
  if (!jarr) {
    return nullptr;
  }
  for (size_t i = 0; i < arr.size(); ++i) {
    JLocal<jobject> elem{env, {{ t.jni.ToJavaBegin }}arr[i]{{ t.jni.ToJavaEnd }}};
    env->SetObjectArrayElement(jarr, i, elem.obj());
  }
  return jarr;
}
{% endif %}
{%- endfor %}

extern "C" {
{% for t in types %}
/*
 * Class:     org_wpilib_networktables_NetworkTablesJNI
 * Method:    getAtomic{{ t.TypeName }}
 * Signature: (I{{ t.jni.jtypestr }})Lorg/wpilib/networktables/Timestamped{{ t.TypeName }};
 */
JNIEXPORT jobject JNICALL
Java_org_wpilib_networktables_NetworkTablesJNI_getAtomic{{ t.TypeName }}
  (JNIEnv* env, jclass, jint subentry, {{ t.jni.jtype }} defaultValue)
{
  return MakeJObject(env, wpi::nt::GetAtomic{{ t.TypeName }}(subentry, {{ t.jni.FromJavaBegin }}defaultValue{{ t.jni.FromJavaEnd }}));
}

/*
 * Class:     org_wpilib_networktables_NetworkTablesJNI
 * Method:    readQueue{{ t.TypeName }}
 * Signature: (I)[Lorg/wpilib/networktables/Timestamped{{ t.TypeName }};
 */
JNIEXPORT jobjectArray JNICALL
Java_org_wpilib_networktables_NetworkTablesJNI_readQueue{{ t.TypeName }}
  (JNIEnv* env, jclass, jint subentry)
{
  return MakeJObject(env, wpi::nt::ReadQueue{{ t.TypeName }}(subentry));
}

/*
 * Class:     org_wpilib_networktables_NetworkTablesJNI
 * Method:    readQueueValues{{ t.TypeName }}
 * Signature: (I)[{{ t.jni.jtypestr }}
 */
JNIEXPORT {% if t.jni.JavaObject %}jobject{% else %}{{ t.jni.jtype }}{% endif %}Array JNICALL
Java_org_wpilib_networktables_NetworkTablesJNI_readQueueValues{{ t.TypeName }}
  (JNIEnv* env, jclass, jint subentry)
{
  return {{ t.jni.ToJavaArray }}(env, wpi::nt::ReadQueueValues{{ t.TypeName }}(subentry));
}
{% if t.TypeName == "Raw" %}
/*
 * Class:     org_wpilib_networktables_NetworkTablesJNI
 * Method:    setRaw
 * Signature: (IJ[BII)Z
 */
JNIEXPORT jboolean JNICALL
Java_org_wpilib_networktables_NetworkTablesJNI_setRaw
  (JNIEnv* env, jclass, jint entry, jlong time, jbyteArray value, jint start, jint len)
{
  if (!value) {
    nullPointerEx.Throw(env, "value is null");
    return false;
  }
  if (start < 0) {
    indexOobEx.Throw(env, "start must be >= 0");
    return false;
  }
  if (len < 0) {
    indexOobEx.Throw(env, "len must be >= 0");
    return false;
  }
  CriticalJSpan<const jbyte> cvalue{env, value};
  if (static_cast<unsigned int>(start + len) > cvalue.size()) {
    indexOobEx.Throw(env, "start + len must be smaller than array length");
    return false;
  }
  return wpi::nt::SetRaw(entry, cvalue.uarray().subspan(start, len), time);
}

/*
 * Class:     org_wpilib_networktables_NetworkTablesJNI
 * Method:    setRawBuffer
 * Signature: (IJLjava/nio/ByteBuffer;II)Z
 */
JNIEXPORT jboolean JNICALL
Java_org_wpilib_networktables_NetworkTablesJNI_setRawBuffer
  (JNIEnv* env, jclass, jint entry, jlong time, jobject value, jint start, jint len)
{
  if (!value) {
    nullPointerEx.Throw(env, "value is null");
    return false;
  }
  if (start < 0) {
    indexOobEx.Throw(env, "start must be >= 0");
    return false;
  }
  if (len < 0) {
    indexOobEx.Throw(env, "len must be >= 0");
    return false;
  }
  JSpan<const jbyte> cvalue{env, value, static_cast<size_t>(start + len)};
  if (!cvalue) {
    illegalArgEx.Throw(env, "value must be a native ByteBuffer");
    return false;
  }
  return wpi::nt::SetRaw(entry, cvalue.uarray().subspan(start, len), time);
}
{% else %}
/*
 * Class:     org_wpilib_networktables_NetworkTablesJNI
 * Method:    set{{ t.TypeName }}
 * Signature: (IJ{{ t.jni.jtypestr }})Z
 */
JNIEXPORT jboolean JNICALL
Java_org_wpilib_networktables_NetworkTablesJNI_set{{ t.TypeName }}
  (JNIEnv*{% if t.jni.JavaObject %} env{% endif %}, jclass, jint entry, jlong time, {{ t.jni.jtype }} value)
{
{%- if t.jni.JavaObject %}
  if (!value) {
    nullPointerEx.Throw(env, "value cannot be null");
    return false;
  }
{%- endif %}
  return wpi::nt::Set{{ t.TypeName }}(entry, {{ t.jni.FromJavaBegin }}value{{ t.jni.FromJavaEnd }}, time);
}
{% endif %}
/*
 * Class:     org_wpilib_networktables_NetworkTablesJNI
 * Method:    get{{ t.TypeName }}
 * Signature: (I{{ t.jni.jtypestr }}){{ t.jni.jtypestr }}
 */
JNIEXPORT {{ t.jni.jtype }} JNICALL
Java_org_wpilib_networktables_NetworkTablesJNI_get{{ t.TypeName }}
  (JNIEnv*{% if t.jni.JavaObject %} env{% endif %}, jclass, jint entry, {{ t.jni.jtype }} defaultValue)
{
{%- if t.jni.JavaObject %}
  auto val = wpi::nt::GetEntryValue(entry);
  if (!val || !val.Is{{ t.TypeName }}()) {
    return defaultValue;
  }
  return {{ t.jni.ToJavaBegin }}val.Get{{ t.TypeName }}(){{ t.jni.ToJavaEnd }};
{%- else %}
  return wpi::nt::Get{{ t.TypeName }}(entry, defaultValue);
{%- endif %}
}
{% if t.TypeName == "Raw" %}
/*
 * Class:     org_wpilib_networktables_NetworkTablesJNI
 * Method:    setDefaultRaw
 * Signature: (IJ[BII)Z
 */
JNIEXPORT jboolean JNICALL
Java_org_wpilib_networktables_NetworkTablesJNI_setDefaultRaw
  (JNIEnv* env, jclass, jint entry, jlong, jbyteArray defaultValue, jint start, jint len)
{
  if (!defaultValue) {
    nullPointerEx.Throw(env, "value is null");
    return false;
  }
  if (start < 0) {
    indexOobEx.Throw(env, "start must be >= 0");
    return false;
  }
  if (len < 0) {
    indexOobEx.Throw(env, "len must be >= 0");
    return false;
  }
  CriticalJSpan<const jbyte> cvalue{env, defaultValue};
  if (static_cast<unsigned int>(start + len) > cvalue.size()) {
    indexOobEx.Throw(env, "start + len must be smaller than array length");
    return false;
  }
  return wpi::nt::SetDefaultRaw(entry, cvalue.uarray().subspan(start, len));
}

/*
 * Class:     org_wpilib_networktables_NetworkTablesJNI
 * Method:    setDefaultRawBuffer
 * Signature: (IJLjava/nio/ByteBuffer;II)Z
 */
JNIEXPORT jboolean JNICALL
Java_org_wpilib_networktables_NetworkTablesJNI_setDefaultRawBuffer
  (JNIEnv* env, jclass, jint entry, jlong, jobject defaultValue, jint start, jint len)
{
  if (!defaultValue) {
    nullPointerEx.Throw(env, "value is null");
    return false;
  }
  if (start < 0) {
    indexOobEx.Throw(env, "start must be >= 0");
    return false;
  }
  if (len < 0) {
    indexOobEx.Throw(env, "len must be >= 0");
    return false;
  }
  JSpan<const jbyte> cvalue{env, defaultValue, static_cast<size_t>(start + len)};
  if (!cvalue) {
    illegalArgEx.Throw(env, "value must be a native ByteBuffer");
    return false;
  }
  return wpi::nt::SetDefaultRaw(entry, cvalue.uarray().subspan(start, len));
}
{% else %}
/*
 * Class:     org_wpilib_networktables_NetworkTablesJNI
 * Method:    setDefault{{ t.TypeName }}
 * Signature: (IJ{{ t.jni.jtypestr }})Z
 */
JNIEXPORT jboolean JNICALL
Java_org_wpilib_networktables_NetworkTablesJNI_setDefault{{ t.TypeName }}
  (JNIEnv*{% if t.jni.JavaObject %} env{% endif %}, jclass, jint entry, jlong, {{ t.jni.jtype }} defaultValue)
{
{%- if t.jni.JavaObject %}
  if (!defaultValue) {
    nullPointerEx.Throw(env, "defaultValue cannot be null");
    return false;
  }
{%- endif %}
  return wpi::nt::SetDefault{{ t.TypeName }}(entry, {{ t.jni.FromJavaBegin }}defaultValue{{ t.jni.FromJavaEnd }});
}
{% endif %}
{% endfor %}
}  // extern "C"

