///|
/// TopologyFingerprint captures the essential topology identity as
/// a compact, comparable value. Two fingerprints are equal iff the
/// slave count and ordered identity sequence match exactly.
pub(all) struct TopologyFingerprint {
  slave_count : Int
  identity_hash : Int64
} derive(Eq, Debug)

///|
/// TopologyFingerprintMatch describes how two fingerprints differ.
pub(all) enum TopologyFingerprintMatch {
  Identical
  SlaveCountChanged(expected~ : Int, actual~ : Int)
  IdentityChanged
} derive(Eq, Debug)

///|
fn hash_identity(
  acc : Int64,
  vendor_id : UInt,
  product_code : UInt,
  revision : UInt,
  serial : UInt,
) -> Int64 {
  let mut h = acc
  h = h * 31L + vendor_id.to_int64()
  h = h * 31L + product_code.to_int64()
  h = h * 31L + revision.to_int64()
  h = h * 31L + serial.to_int64()
  h
}

///|
/// Compute a topology fingerprint from a ScanReport.
pub fn topology_fingerprint(scan : ScanReport) -> TopologyFingerprint {
  let mut h : Int64 = 0x811c9dc5L // FNV offset basis
  let sorted = scan.slaves.copy()
  sorted.sort_by(fn(a, b) { a.position.compare(b.position) })
  for slave in sorted {
    h = hash_identity(
      h,
      slave.vendor_id,
      slave.product_code,
      slave.revision,
      slave.serial,
    )
  }
  { slave_count: scan.slave_count, identity_hash: h }
}

///|
/// Compute a topology fingerprint from a ConfigurationModel.
pub fn topology_fingerprint_from_config(
  config : ConfigurationModel,
) -> TopologyFingerprint {
  let mut h : Int64 = 0x811c9dc5L
  let sorted = config.slaves.copy()
  sorted.sort_by(fn(a, b) { a.position.compare(b.position) })
  for slave in sorted {
    match slave.identity {
      Some(id) =>
        h = hash_identity(
          h,
          id.vendor_id,
          id.product_code,
          id.revision,
          id.serial,
        )
      None => h = h * 31L
    }
  }
  { slave_count: config.total_slaves, identity_hash: h }
}

///|
/// Compare two topology fingerprints.
pub fn fingerprint_match(
  expected : TopologyFingerprint,
  actual : TopologyFingerprint,
) -> TopologyFingerprintMatch {
  if expected == actual {
    Identical
  } else if expected.slave_count != actual.slave_count {
    SlaveCountChanged(expected=expected.slave_count, actual=actual.slave_count)
  } else {
    IdentityChanged
  }
}