use crate::merge::Merge;
use serde::{Deserialize, Serialize};
use std::fmt;
pub type E12 = (i32, i32);
pub fn eisenstein_norm(e: E12) -> i64 {
let (a, b) = (e.0 as i64, e.1 as i64);
a * a - a * b + b * b
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EisensteinRegister {
pub position: E12,
pub timestamp: u64,
pub node: String,
pub norm: i64,
}
impl EisensteinRegister {
pub fn new(position: E12, node: &str) -> Self {
Self {
position,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64,
node: node.to_string(),
norm: eisenstein_norm(position),
}
}
pub fn update(&mut self, position: E12, node: &str) {
let new_norm = eisenstein_norm(position);
self.position = position;
self.norm = new_norm;
self.node = node.to_string();
self.timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
}
pub fn hex_distance(&self) -> i32 {
let (a, b) = self.position;
a.abs().max(b.abs()).max((a - b).abs())
}
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
pub fn from_json(s: &str) -> Option<Self> {
serde_json::from_str(s).ok()
}
}
impl Merge for EisensteinRegister {
fn merge(&mut self, other: &Self) {
let self_wins = match self.norm.cmp(&other.norm) {
std::cmp::Ordering::Less => true, std::cmp::Ordering::Greater => false, std::cmp::Ordering::Equal => self.timestamp >= other.timestamp,
};
if !self_wins {
self.position = other.position;
self.norm = other.norm;
self.timestamp = other.timestamp;
self.node = other.node.clone();
}
}
}
impl fmt::Display for EisensteinRegister {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "E12({},{} norm={} from={})",
self.position.0, self.position.1, self.norm, self.node)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::merge::laws;
#[test]
fn test_norm() {
assert_eq!(eisenstein_norm((0, 0)), 0);
assert_eq!(eisenstein_norm((1, 0)), 1);
assert_eq!(eisenstein_norm((1, 1)), 1);
assert_eq!(eisenstein_norm((2, 1)), 3);
}
#[test]
fn test_lower_norm_wins() {
let mut a = EisensteinRegister::new((3, 3), "node-a"); let b = EisensteinRegister::new((1, 0), "node-b"); a.merge(&b);
assert_eq!(a.position, (1, 0)); }
#[test]
fn test_merge_commutative() {
let a = EisensteinRegister::new((3, 0), "a");
let b = EisensteinRegister::new((0, 1), "b");
let ab = a.merged(&b);
let ba = b.merged(&a);
assert_eq!(ab.position, ba.position);
}
#[test]
fn test_merge_idempotent() {
let a = EisensteinRegister::new((2, 1), "a");
assert!(laws::check_idempotent(&a));
}
#[test]
fn test_hex_distance() {
let r = EisensteinRegister::new((3, -1), "a");
assert_eq!(r.hex_distance(), 4); }
#[test]
fn test_json_roundtrip() {
let r = EisensteinRegister::new((2, -3), "node-a");
let json = r.to_json();
let restored = EisensteinRegister::from_json(&json).unwrap();
assert_eq!(restored.position, (2, -3));
assert_eq!(restored.node, "node-a");
}
}