// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Trait for types that can be randomly generated
pub(open) trait Arbitrary {
  // `arbitrary` function takes a random number generator and a number that 
  // determines the size of the generated value as arguments, and returns a
  // randomly generated value of certain type.
  fn arbitrary(Int, @splitmix.RandomState) -> Self
}

///|
pub impl Arbitrary for Unit with fn arbitrary(_, _) {
  ()
}

///|
pub impl Arbitrary for Bool with fn arbitrary(_, rs) {
  rs.next_double() < 0.5
}

///|
pub impl Arbitrary for Int with fn arbitrary(size, rs) {
  if size == 0 {
    0
  } else {
    rs.next_int() % size
  }
}

///|
pub impl Arbitrary for UInt with fn arbitrary(size, rs) {
  if size == 0 {
    0
  } else {
    rs.next_uint() % size.reinterpret_as_uint()
  }
}

///|
pub impl Arbitrary for Byte with fn arbitrary(_, rs) {
  rs.next_uint().to_byte()
}

///|
pub impl Arbitrary for Bytes with fn arbitrary(size, rs) {
  if size == 0 {
    Bytes::new(0)
  } else {
    let sz = rs.next_positive_int() % size
    Bytes::makei(sz, _ => Arbitrary::arbitrary(size, rs))
  }
}

///|
pub impl Arbitrary for Int64 with fn arbitrary(size, rs) {
  if size == 0 {
    0
  } else {
    rs.next_int64() % size.to_int64()
  }
}

///|
pub impl Arbitrary for UInt64 with fn arbitrary(size, rs) {
  if size == 0 {
    0
  } else {
    rs.next_uint64() % size.to_uint64()
  }
}

///|
pub impl Arbitrary for Float with fn arbitrary(_, rs) {
  rs.next_float()
}

///|
pub impl Arbitrary for Double with fn arbitrary(_, rs) {
  rs.next_double()
}

///|
pub impl Arbitrary for Char with fn arbitrary(_, rs) {
  // Bias toward ASCII, but regularly produce arbitrary Unicode scalar
  // values so consumers also see multi-byte and non-BMP characters.
  if rs.next_uint() % 4 < 3 {
    (rs.next_uint() % 128).reinterpret_as_int().to_char().unwrap()
  } else {
    // Unicode scalar values: [0x0, 0xD7FF] and [0xE000, 0x10FFFF].
    let index = (rs.next_uint() % (0x110000 - 0x800)).reinterpret_as_int()
    let code = if index < 0xD800 { index } else { index + 0x800 }
    code.to_char().unwrap()
  }
}

///|
pub impl Arbitrary for String with fn arbitrary(size, rs) {
  let len = if size == 0 { 0 } else { rs.next_positive_int() % size }
  for i in 0.. {
    guard i < len else { None }
    let elem = X::arbitrary(i, rs)
    i += 1
    Some(elem)
  })
}

///|
/// Generates a single random value, optionally with a given size and random state.
pub fn[T : Arbitrary] gen(size? : Int, state? : @splitmix.RandomState) -> T {
  let size = size.unwrap_or(0)
  let state = state.unwrap_or_else(() => {
    (Default::default() : @splitmix.RandomState)
  })
  Arbitrary::arbitrary(size, state)
}

///|
pub impl[X : Arbitrary] Arbitrary for X? with fn arbitrary(i, rs) {
  if rs.next_double() < 0.3 {
    None
  } else {
    Some(Arbitrary::arbitrary(i, rs))
  }
}

///|
pub impl[T : Arbitrary, E : Arbitrary] Arbitrary for Result[T, E] with fn arbitrary(
  size,
  rs,
) {
  if Arbitrary::arbitrary(size, rs) {
    Ok(T::arbitrary(size, rs))
  } else {
    Err(E::arbitrary(size, rs))
  }
}

///|
pub impl[X : Arbitrary] Arbitrary for FixedArray[X] with fn arbitrary(size, rs) {
  let len = if size == 0 { 0 } else { rs.next_positive_int() % size }
  FixedArray::makei(len, i => X::arbitrary(i, rs))
}

///|
pub impl[A : Arbitrary] Arbitrary for ArrayView[A] with fn arbitrary(size, rs) {
  let arr : Array[A] = Arbitrary::arbitrary(size, rs)
  // Note can not use Array::arbitrary
  arr
}

///|
pub impl[X : Arbitrary] Arbitrary for Array[X] with fn arbitrary(size, rs) {
  let len = if size == 0 { 0 } else { rs.next_positive_int() % size }
  Array::makei(len, x => X::arbitrary(x, rs))
}

///|
pub impl Arbitrary for @bigint.BigInt with fn arbitrary(size, rs) {
  if size == 0 {
    0
  } else {
    rs.next_int64() |> @bigint.BigInt::from_int64
  }
}

///|
pub impl[X : Arbitrary] Arbitrary for @ref.Ref[X] with fn arbitrary(size, rs) {
  @ref.new(X::arbitrary(size, rs))
}

///|
pub impl[A : Arbitrary, B : Arbitrary] Arbitrary for (A, B) with fn arbitrary(
  size,
  r0,
) {
  let r1 = r0.split()
  (Arbitrary::arbitrary(size, r0), Arbitrary::arbitrary(size, r1))
}

///|
pub impl[A : Arbitrary, B : Arbitrary, C : Arbitrary] Arbitrary for (A, B, C) with fn arbitrary(
  size,
  r0,
) {
  let r1 = r0.split()
  let (v1, v2) = Arbitrary::arbitrary(size, r1)
  (Arbitrary::arbitrary(size, r0), v1, v2)
}

///|
pub impl[A : Arbitrary, B : Arbitrary, C : Arbitrary, D : Arbitrary] Arbitrary for (
  A,
  B,
  C,
  D,
) with fn arbitrary(size, r0) {
  let r1 = r0.split()
  let (v1, v2, v3) = Arbitrary::arbitrary(size, r1)
  (Arbitrary::arbitrary(size, r0), v1, v2, v3)
}

///|
pub impl[
  A : Arbitrary,
  B : Arbitrary,
  C : Arbitrary,
  D : Arbitrary,
  E : Arbitrary,
] Arbitrary for (A, B, C, D, E) with fn arbitrary(size, r0) {
  let r1 = r0.split()
  let (v1, v2, v3, v4) = Arbitrary::arbitrary(size, r1)
  (Arbitrary::arbitrary(size, r0), v1, v2, v3, v4)
}

///|
pub impl[
  A : Arbitrary,
  B : Arbitrary,
  C : Arbitrary,
  D : Arbitrary,
  E : Arbitrary,
  F : Arbitrary,
] Arbitrary for (A, B, C, D, E, F) with fn arbitrary(size, r0) {
  let r1 = r0.split()
  let (v1, v2, v3, v4, v5) = Arbitrary::arbitrary(size, r1)
  (Arbitrary::arbitrary(size, r0), v1, v2, v3, v4, v5)
}

///|
pub impl[
  A : Arbitrary,
  B : Arbitrary,
  C : Arbitrary,
  D : Arbitrary,
  E : Arbitrary,
  F : Arbitrary,
  G : Arbitrary,
] Arbitrary for (A, B, C, D, E, F, G) with fn arbitrary(size, r0) {
  let r1 = r0.split()
  let (v1, v2, v3, v4, v5, v6) = Arbitrary::arbitrary(size, r1)
  (Arbitrary::arbitrary(size, r0), v1, v2, v3, v4, v5, v6)
}