///|
pub let fnv_offset_basis : UInt64 = 14695981039346656037UL

///|
pub let fnv_prime : UInt64 = 1099511628211UL

///|
pub fn fnv1a_hash_byte(byte : Int, hash : UInt64) -> UInt64 {
  (hash ^ byte.to_uint64()) * fnv_prime
}

///|
pub fn fnv1a_hash_int(value : Int, hash : UInt64) -> UInt64 {
  let mut h = hash
  let mut uv = value.to_uint64()
  let mut i = 0
  while i < 8 {
    let byte = (uv & 0xFFUL).to_int()
    h = fnv1a_hash_byte(byte, h)
    uv = uv >> 8
    i = i + 1
  }
  h
}

///|
pub fn fnv1a_hash_string(s : String, hash : UInt64) -> UInt64 {
  let mut h = hash
  let mut i = 0
  while i < s.length() {
    h = fnv1a_hash_byte(s[i].to_int(), h)
    i = i + 1
  }
  h
}

///|
pub fn fnv1a_hash_uint64(value : UInt64, hash : UInt64) -> UInt64 {
  let mut h = hash
  let mut v = value
  let mut i = 0
  while i < 8 {
    h = fnv1a_hash_byte((v % 256UL).to_int(), h)
    v = v / 256UL
    i = i + 1
  }
  h
}

///|
pub fn fingerprint_bytes(bytes : Array[Int]) -> UInt64 {
  let mut h = fnv_offset_basis
  let mut i = 0
  while i < bytes.length() {
    h = fnv1a_hash_byte(bytes[i], h)
    i = i + 1
  }
  h
}

///|
pub fn fingerprint_ints(ints : Array[Int]) -> UInt64 {
  let mut h = fnv_offset_basis
  let mut i = 0
  while i < ints.length() {
    h = fnv1a_hash_int(ints[i], h)
    i = i + 1
  }
  h
}

///|
pub fn fingerprint_strings(strings : Array[String]) -> UInt64 {
  let mut h = fnv_offset_basis
  let mut i = 0
  while i < strings.length() {
    h = fnv1a_hash_string(strings[i], h)
    i = i + 1
  }
  h
}

///|
pub fn fingerprint_uint64s(values : Array[UInt64]) -> UInt64 {
  let mut h = fnv_offset_basis
  let mut i = 0
  while i < values.length() {
    h = fnv1a_hash_uint64(values[i], h)
    i = i + 1
  }
  h
}

///|
pub fn combine(a : UInt64, b : UInt64) -> UInt64 {
  fnv1a_hash_uint64(b, a)
}

///|
pub struct RollingFingerprint {
  mut hash : UInt64
  mut count : Int
} derive(Debug, Eq)

///|
pub fn RollingFingerprint::new() -> RollingFingerprint {
  { hash: fnv_offset_basis, count: 0 }
}

///|
pub fn RollingFingerprint::from_hash(
  hash : UInt64,
  count : Int,
) -> RollingFingerprint {
  { hash, count }
}

///|
pub fn RollingFingerprint::update_int(
  self : RollingFingerprint,
  value : Int,
) -> Unit {
  self.hash = fnv1a_hash_int(value, self.hash)
  self.count = self.count + 1
}

///|
pub fn RollingFingerprint::update_string(
  self : RollingFingerprint,
  value : String,
) -> Unit {
  self.hash = fnv1a_hash_string(value, self.hash)
  self.count = self.count + 1
}

///|
pub fn RollingFingerprint::update_uint64(
  self : RollingFingerprint,
  value : UInt64,
) -> Unit {
  self.hash = fnv1a_hash_uint64(value, self.hash)
  self.count = self.count + 1
}

///|
pub fn RollingFingerprint::update_byte(
  self : RollingFingerprint,
  value : Int,
) -> Unit {
  self.hash = fnv1a_hash_byte(value, self.hash)
  self.count = self.count + 1
}

///|
pub fn RollingFingerprint::hash(self : RollingFingerprint) -> UInt64 {
  self.hash
}

///|
pub fn RollingFingerprint::count(self : RollingFingerprint) -> Int {
  self.count
}

///|
pub fn RollingFingerprint::is_empty(self : RollingFingerprint) -> Bool {
  self.count == 0
}

///|
pub fn RollingFingerprint::combine(
  self : RollingFingerprint,
  other : RollingFingerprint,
) -> RollingFingerprint {
  {
    hash: fnv1a_hash_uint64(other.hash, self.hash),
    count: self.count + other.count,
  }
}

///|
pub fn RollingFingerprint::reset(self : RollingFingerprint) -> Unit {
  self.hash = fnv_offset_basis
  self.count = 0
}