use std::collections::BTreeMap;
use std::hash::Hash as StdHash;
use serde::{Deserialize, Serialize};
use crate::identity::Author;
pub trait LogId: Clone + Eq + Ord + StdHash + Serialize + for<'de> Deserialize<'de> {}
impl<T> LogId for T where T: Clone + Eq + Ord + StdHash + Serialize + for<'de> Deserialize<'de> {}
pub type SeqNum = u64;
pub type LogHeights<A, L> = BTreeMap<A, BTreeMap<L, SeqNum>>;
pub type LogRanges<A, L> = BTreeMap<A, BTreeMap<L, (Option<SeqNum>, Option<SeqNum>)>>;
pub fn compare<A, L>(local: &LogHeights<A, L>, remote: &LogHeights<A, L>) -> LogRanges<A, L>
where
A: Author,
L: LogId,
{
let mut remote_needs: LogRanges<A, L> = BTreeMap::default();
for (verifying_key, local_logs) in local {
let Some(remote_logs) = remote.get(verifying_key) else {
let needs = local_logs
.iter()
.map(|(log_id, log_height)| (log_id.clone(), (None, Some(*log_height))))
.collect();
remote_needs.insert(verifying_key.to_owned(), needs);
continue;
};
if local_logs == remote_logs {
continue;
}
for (log_id, local_log_height) in local_logs {
let Some(remote_log_height) = remote_logs.get(log_id) else {
remote_needs
.entry(verifying_key.to_owned())
.or_default()
.insert(log_id.clone(), (None, Some(*local_log_height)));
continue;
};
if remote_log_height < local_log_height {
remote_needs
.entry(verifying_key.to_owned())
.or_default()
.insert(
log_id.clone(),
(Some(*remote_log_height), Some(*local_log_height)),
);
}
}
}
remote_needs
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use crate::logs::compare;
type Author = u8;
impl crate::identity::Author for Author {}
const ALICE: Author = 0;
const BOB: Author = 1;
#[test]
fn both_empty() {
let local: BTreeMap<Author, BTreeMap<u64, u64>> = BTreeMap::new();
let remote: BTreeMap<Author, BTreeMap<u64, u64>> = BTreeMap::new();
let result = compare(&local, &remote);
assert!(result.is_empty());
}
#[test]
fn remote_empty() {
let mut local: BTreeMap<Author, BTreeMap<u64, u64>> = BTreeMap::new();
let logs = BTreeMap::from([(1, 5), (2, 10)]);
local.insert(ALICE, logs);
let remote: BTreeMap<Author, BTreeMap<u64, u64>> = BTreeMap::new();
let result = compare(&local, &remote);
let needs = result.get(&ALICE).unwrap();
assert_eq!(needs.get(&1), Some(&(None, Some(5))));
assert_eq!(needs.get(&2), Some(&(None, Some(10))));
}
#[test]
fn remote_missing_single_log() {
let mut local = BTreeMap::new();
local.insert(ALICE, BTreeMap::from([(1, 5), (2, 10)]));
let mut remote = BTreeMap::new();
remote.insert(ALICE, BTreeMap::from([(1, 5)]));
let result = compare(&local, &remote);
let needs = result.get(&ALICE).unwrap();
assert_eq!(needs.get(&2), Some(&(None, Some(10))));
assert!(!needs.contains_key(&1));
}
#[test]
fn remote_behind() {
let mut local = BTreeMap::new();
local.insert(ALICE, BTreeMap::from([(1, 20)]));
let mut remote = BTreeMap::new();
remote.insert(ALICE, BTreeMap::from([(1, 10)]));
let result = compare(&local, &remote);
let needs = result.get(&ALICE).unwrap();
assert_eq!(needs.get(&1), Some(&(Some(10), Some(20))));
}
#[test]
fn remote_ahead() {
let mut local = BTreeMap::new();
local.insert(ALICE, BTreeMap::from([(1, 20)]));
let mut remote = BTreeMap::new();
remote.insert(ALICE, BTreeMap::from([(1, 30)]));
let result = compare(&local, &remote);
assert!(result.is_empty());
}
#[test]
fn equal() {
let mut local = BTreeMap::new();
local.insert(ALICE, BTreeMap::from([(1, 20)]));
let mut remote = BTreeMap::new();
remote.insert(ALICE, BTreeMap::from([(1, 20)]));
let result = compare(&local, &remote);
assert!(result.is_empty());
}
#[test]
fn remote_missing_multiple_logs() {
let mut local = BTreeMap::new();
local.insert(ALICE, BTreeMap::from([(1, 5), (2, 10), (3, 15)]));
let mut remote = BTreeMap::new();
remote.insert(ALICE, BTreeMap::from([(1, 5)]));
let result = compare(&local, &remote);
let needs = result.get(&ALICE).unwrap();
assert_eq!(needs.get(&2), Some(&(None, Some(10))));
assert_eq!(needs.get(&3), Some(&(None, Some(15))));
assert!(!needs.contains_key(&1));
}
#[test]
fn remote_missing_author() {
let mut local = BTreeMap::new();
local.insert(ALICE, BTreeMap::from([(1, 5)]));
local.insert(BOB, BTreeMap::from([(1, 5)]));
let mut remote = BTreeMap::new();
remote.insert(ALICE, BTreeMap::from([(1, 5)]));
let result = compare(&local, &remote);
let needs = result.get(&BOB).unwrap();
assert_eq!(needs.get(&1), Some(&(None, Some(5))));
}
}