///|
// Classfile CONSTANT_Utf8 lengths count UTF-16 code units in Modified UTF-8,
// not Unicode scalar values or ordinary UTF-8. Stop before the bound can grow.
fn mutf8_length_bounded(text : String, limit : Int) -> Int raise JniError {
  if text.length() > limit {
    raise DescriptorTooLong
  }
  let mut bytes = 0
  for i in 0.. limit {
      raise DescriptorTooLong
    }
  }
  bytes
}

///|
/// An internal JVM binary class name, for example `java/lang/String`.
pub struct JniClass {
  priv internal_name : String
  priv internal_name_bytes : Int
} derive(Eq, Debug)

///|
#deprecated("Use `==` instead", skip_current_package=true)
#doc(hidden)
pub extend JniClass with Eq::{equal, not_equal}

///|
#deprecated("Use `Debug::to_repr` instead", skip_current_package=true)
#doc(hidden)
pub extend JniClass with Debug::{to_repr}

///|
/// Validate the internal name and reserve two MUTF-8 bytes for `L` and `;`.
/// Oversized names raise `DescriptorTooLong`; malformed names retain
/// `InvalidClassName`. The non-raising descriptor/object constructors are then
/// guaranteed to fit the 65,535-byte classfile limit.
pub fn JniClass::parse(internal_name : String) -> JniClass raise JniError {
  if internal_name.is_empty() ||
    internal_name.has_prefix("/") ||
    internal_name.has_suffix("/") ||
    internal_name.contains("//") ||
    internal_name.contains(".") ||
    internal_name.contains(";") ||
    internal_name.contains("[") ||
    internal_name.contains_code_unit(0) {
    raise InvalidClassName(internal_name)
  }
  let internal_name_bytes = mutf8_length_bounded(internal_name, 65533)
  { internal_name, internal_name_bytes, }
}

///|
pub fn JniClass::internal_name(self : JniClass) -> String {
  self.internal_name
}

///|
pub fn JniClass::descriptor(self : JniClass) -> String {
  "L\{self.internal_name};"
}

///|
/// A non-void JNI value type. Its parameter-slot width is retained so method
/// descriptors cannot undercount `long` and `double`.
pub struct JniType {
  priv descriptor : String
  priv descriptor_bytes : Int
  priv parameter_slots : Int
  priv array_dimensions : Int
} derive(Eq, Debug)

///|
#deprecated("Use `==` instead", skip_current_package=true)
#doc(hidden)
pub extend JniType with Eq::{equal, not_equal}

///|
#deprecated("Use `Debug::to_repr` instead", skip_current_package=true)
#doc(hidden)
pub extend JniType with Debug::{to_repr}

///|
pub fn JniType::boolean() -> JniType {
  {
    descriptor: "Z",
    descriptor_bytes: 1,
    parameter_slots: 1,
    array_dimensions: 0,
  }
}

///|
pub fn JniType::byte() -> JniType {
  {
    descriptor: "B",
    descriptor_bytes: 1,
    parameter_slots: 1,
    array_dimensions: 0,
  }
}

///|
pub fn JniType::char() -> JniType {
  {
    descriptor: "C",
    descriptor_bytes: 1,
    parameter_slots: 1,
    array_dimensions: 0,
  }
}

///|
pub fn JniType::short() -> JniType {
  {
    descriptor: "S",
    descriptor_bytes: 1,
    parameter_slots: 1,
    array_dimensions: 0,
  }
}

///|
pub fn JniType::int() -> JniType {
  {
    descriptor: "I",
    descriptor_bytes: 1,
    parameter_slots: 1,
    array_dimensions: 0,
  }
}

///|
pub fn JniType::long() -> JniType {
  {
    descriptor: "J",
    descriptor_bytes: 1,
    parameter_slots: 2,
    array_dimensions: 0,
  }
}

///|
pub fn JniType::float() -> JniType {
  {
    descriptor: "F",
    descriptor_bytes: 1,
    parameter_slots: 1,
    array_dimensions: 0,
  }
}

///|
pub fn JniType::double() -> JniType {
  {
    descriptor: "D",
    descriptor_bytes: 1,
    parameter_slots: 2,
    array_dimensions: 0,
  }
}

///|
pub fn JniType::object(class : JniClass) -> JniType {
  {
    descriptor: class.descriptor(),
    descriptor_bytes: class.internal_name_bytes + 2,
    parameter_slots: 1,
    array_dimensions: 0,
  }
}

///|
pub fn JniType::array(element : JniType) -> JniType raise JniError {
  if element.array_dimensions >= 255 {
    raise TooManyArrayDimensions
  }
  if element.descriptor_bytes >= 65535 {
    raise DescriptorTooLong
  }
  {
    descriptor: "[\{element.descriptor}",
    descriptor_bytes: element.descriptor_bytes + 1,
    parameter_slots: 1,
    array_dimensions: element.array_dimensions + 1,
  }
}

///|
pub fn JniType::descriptor(self : JniType) -> String {
  self.descriptor
}

///|
/// A validated JVM method descriptor. The default return type is `void`.
/// Validation covers descriptor shape, MUTF-8 length, and up to 255 explicit
/// parameter slots, not a function-pointer ABI or an instance/static context.
/// Instance invocation also needs a slot for `this` and permits at most 254
/// explicit slots; current Kotlin native registrations are static.
pub struct JniMethod {
  priv descriptor : String
} derive(Eq, Debug)

///|
#deprecated("Use `==` instead", skip_current_package=true)
#doc(hidden)
pub extend JniMethod with Eq::{equal, not_equal}

///|
#deprecated("Use `Debug::to_repr` instead", skip_current_package=true)
#doc(hidden)
pub extend JniMethod with Debug::{to_repr}

///|
pub fn JniMethod::new(
  parameters : ArrayView[JniType],
  return_type? : JniType,
) -> JniMethod raise JniError {
  let result_descriptor = match return_type {
    Some(value) => value.descriptor
    None => "V"
  }
  let mut descriptor_bytes = 2 +
    (match return_type {
      Some(value) => value.descriptor_bytes
      None => 1
    })
  if descriptor_bytes > 65535 {
    raise DescriptorTooLong
  }
  let mut descriptor_units = 2 + result_descriptor.length()
  let mut parameter_slots = 0
  for parameter in parameters {
    parameter_slots += parameter.parameter_slots
    if parameter_slots > 255 {
      raise TooManyParameterSlots
    }
    descriptor_bytes += parameter.descriptor_bytes
    if descriptor_bytes > 65535 {
      raise DescriptorTooLong
    }
    descriptor_units += parameter.descriptor.length()
  }
  let builder = StringBuilder(size_hint=descriptor_units * 2)
  builder.write_char('(')
  for parameter in parameters {
    builder.write_string(parameter.descriptor)
  }
  builder.write_char(')')
  builder.write_string(result_descriptor)
  { descriptor: builder.to_string(), }
}

///|
pub fn JniMethod::descriptor(self : JniMethod) -> String {
  self.descriptor
}

///|
/// A native-method registration declaration without a raw function pointer.
/// It validates the name and descriptor, not registration linkage or ABI.
pub struct NativeMethod {
  priv name : String
  priv signature : JniMethod
} derive(Eq, Debug)

///|
#deprecated("Use `==` instead", skip_current_package=true)
#doc(hidden)
pub extend NativeMethod with Eq::{equal, not_equal}

///|
#deprecated("Use `Debug::to_repr` instead", skip_current_package=true)
#doc(hidden)
pub extend NativeMethod with Debug::{to_repr}

///|
pub fn NativeMethod::new(
  name : String,
  signature : JniMethod,
) -> NativeMethod raise JniError {
  if name.is_empty() ||
    name.contains("/") ||
    name.contains(".") ||
    name.contains(";") ||
    name.contains("[") ||
    name.contains("<") ||
    name.contains(">") ||
    name.contains_code_unit(0) {
    raise InvalidNativeMethodName(name)
  }
  ignore(mutf8_length_bounded(name, 65535))
  { name, signature, }
}

///|
pub fn NativeMethod::name(self : NativeMethod) -> String {
  self.name
}

///|
pub fn NativeMethod::descriptor(self : NativeMethod) -> String {
  self.signature.descriptor()
}