///|
pub fn py_bool() -> @qc.Gen[@py.PyBool] {
  let gen : @qc.Gen[Bool] = @qc.Gen::spawn()
  gen.fmap(@py.PyBool::from(_))
}

///|
pub fn py_integer() -> @qc.Gen[@py.PyInteger] {
  let gen : @qc.Gen[Int64] = @qc.Gen::spawn()
  gen.fmap(@py.PyInteger::from(_))
}

///|
pub fn py_float() -> @qc.Gen[@py.PyFloat] {
  let gen : @qc.Gen[Double] = @qc.Gen::spawn()
  gen.fmap(@py.PyFloat::from(_))
}

///|
pub fn py_string() -> @qc.Gen[@py.PyString] {
  let gen : @qc.Gen[String] = @qc.Gen::spawn()
  gen.fmap(@py.PyString::from(_))
}

///|
pub impl[T : @cqc.Arbitrary + PyObjectInto] @cqc.Arbitrary for TypedPyList[T] with arbitrary(
  size : Int,
  rng : @qc.RandomState,
) -> TypedPyList[T] {
  let arr : Array[T] = @cqc.Arbitrary::arbitrary(size, rng)
  let arr_gen = arr.map(x => x.to_py_object())
  { data: @py.PyList::from(arr_gen) }
}

///|
pub fn[A : @cqc.Arbitrary + PyObjectInto, B : @cqc.Arbitrary + PyObjectInto] py_tuple2() -> @qc.Gen[
  TypedPyTuple2[A, B],
] {
  @qc.Gen::new(fn(size, rng) {
    let a : A = @cqc.Arbitrary::arbitrary(size, rng)
    let b : B = @cqc.Arbitrary::arbitrary(size, rng)
    let tuple = @py.PyTuple::new(2)
    tuple..set(0, a.to_py_object())
    tuple..set(1, b.to_py_object())
    { tuple2: tuple }
  })
}

///|
pub impl[A : @cqc.Arbitrary + PyObjectInto, B : @cqc.Arbitrary + PyObjectInto] @cqc.Arbitrary for TypedPyTuple2[
  A,
  B,
] with arbitrary(size : Int, rng : @qc.RandomState) {
  let a : A = @cqc.Arbitrary::arbitrary(size, rng)
  let b : B = @cqc.Arbitrary::arbitrary(size, rng)
  let tuple = @py.PyTuple::new(2)
  tuple..set(0, a.to_py_object())
  tuple..set(1, b.to_py_object())
  { tuple2: tuple }
}

///|
pub impl[
  A : @cqc.Arbitrary + PyObjectInto,
  B : @cqc.Arbitrary + PyObjectInto,
  C : @cqc.Arbitrary + PyObjectInto,
] @cqc.Arbitrary for TypedPyTuple3[A, B, C] with arbitrary(
  size : Int,
  rng : @qc.RandomState,
) {
  let a : A = @cqc.Arbitrary::arbitrary(size, rng)
  let b : B = @cqc.Arbitrary::arbitrary(size, rng)
  let c : C = @cqc.Arbitrary::arbitrary(size, rng)
  let tuple = @py.PyTuple::new(3)
  tuple..set(0, a.to_py_object())
  tuple..set(1, b.to_py_object())
  tuple..set(2, c.to_py_object())
  { tuple3: tuple }
}

///|
pub impl[B : @cqc.Arbitrary + PyObjectInto] @cqc.Arbitrary for TypedPyDict[B] with arbitrary(
  size,
  eng,
) {
  let kvs : Array[(String, B)] = @cqc.Arbitrary::arbitrary(size, eng)
  let dict = @py.PyDict::new()
  for _, sv in kvs {
    let (s, v) = sv
    dict.set(s, v.to_py_object())
  }
  { dict, }
}

///|
test "rng typed py list" {
  let gen : @qc.Gen[TypedPyList[Int]] = @qc.Gen::spawn()
  let sample = gen.run(230, @splitmix.new(seed=22))
  inspect(sample, content="[0, 0, 0, 0, 2, 1, 0, 1, -2, 8]")
}

///|
test "rng typed py dict" {
  let gen : @qc.Gen[TypedPyDict[Int64]] = @qc.Gen::spawn()
  let sample = gen.run(50, @splitmix.new(seed=1234))
  inspect(
    sample,
    content=(
      #|{'': -2, 'c': 2, '\x17xy\x0b': 4, '^*': 0, 'nl\x02#F': 4, '}k>U\x15"Z': 2, '8 \x12zzyQ': 1, '\x07X\x04!': 9, '#ZZ\t': 9, "h*\x18\x135p'F": -11}
    ),
  )
}

///|
test "rng typed py tuple2" {
  let gen : @qc.Gen[TypedPyTuple2[String, Double]] = @qc.Gen::spawn()
  let sample = gen.run(40, @splitmix.new(seed=32))
  inspect(sample, content="('K', 0.3914602989489513)")
}

///|
test "rng typed py tuple3" {
  let gen : @qc.Gen[TypedPyTuple3[Int64, String, Bool]] = @qc.Gen::spawn()
  let sample = gen.run(15, @splitmix.new(seed=412))
  inspect(sample, content="(-3, '\\x02vY)', True)")
}

///|
test {
  let nums = [1L, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
  let py_nums = nums.map(@py.PyInteger::from) |> @py.PyList::from
  guard @py.pyimport("collections") is Some(collections)

  // It's equivalent to `from collections import Counter`
  guard collections.get_attr("Counter") is Some(PyCallable(counter))
  let args = @py.PyTuple::new(1)
  args..set(0, py_nums)
  guard (try? counter.invoke(args~)) is Ok(Some(cnt))
  guard cnt is PyDict(cnt)
  println(cnt)
}