use crate::merge::Merge;
use crate::counter::ConstraintGCounter;
use crate::orset::ConstraintORSet;
use crate::eisenstein::EisensteinRegister;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FleetTile {
pub id: String,
pub room: String,
pub content: String,
pub tags: Vec<String>,
pub constraints: ConstraintORSet,
pub metrics: ConstraintGCounter,
pub position: Option<EisensteinRegister>,
pub author: String,
pub created_at: u64,
pub updated_at: u64,
pub content_hash: u64,
}
impl FleetTile {
pub fn new(room: &str, id: &str, content: &str, author: &str) -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
Self {
id: id.to_string(),
room: room.to_string(),
content: content.to_string(),
tags: Vec::new(),
constraints: ConstraintORSet::new(),
metrics: ConstraintGCounter::new(),
position: None,
author: author.to_string(),
created_at: now,
updated_at: now,
content_hash: Self::hash_content(content),
}
}
pub fn tag(&mut self, tag: &str) {
if !self.tags.contains(&tag.to_string()) {
self.tags.push(tag.to_string());
}
}
pub fn has_tag(&self, tag: &str) -> bool {
self.tags.iter().any(|t| t == tag)
}
fn hash_content(content: &str) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for byte in content.bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
pub fn update_content(&mut self, content: &str) {
self.content = content.to_string();
self.content_hash = Self::hash_content(content);
self.updated_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
}
pub fn verify_integrity(&self) -> bool {
Self::hash_content(&self.content) == self.content_hash
}
pub fn to_json(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_default()
}
}
impl Merge for FleetTile {
fn merge(&mut self, other: &Self) {
if self.id != other.id || self.room != other.room {
return;
}
self.constraints.merge(&other.constraints);
self.metrics.merge(&other.metrics);
for tag in &other.tags {
self.tag(tag);
}
if other.updated_at > self.updated_at {
self.content = other.content.clone();
self.content_hash = other.content_hash;
self.updated_at = other.updated_at;
}
match (&mut self.position, &other.position) {
(Some(mine), Some(theirs)) => mine.merge(theirs),
(None, Some(theirs)) => self.position = Some(theirs.clone()),
_ => {}
}
self.created_at = self.created_at.min(other.created_at);
}
}
impl fmt::Display for FleetTile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Tile({}/{} by={}, {} bytes, {} constraints, {})",
self.room, self.id, self.author,
self.content.len(),
self.constraints.len(),
self.metrics)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tile_creation() {
let t = FleetTile::new("fleet-ops", "tile-1", "Hello fleet", "forgemaster");
assert_eq!(t.id, "tile-1");
assert_eq!(t.room, "fleet-ops");
assert!(t.verify_integrity());
}
#[test]
fn test_content_integrity() {
let mut t = FleetTile::new("test", "1", "original", "a");
assert!(t.verify_integrity());
t.update_content("modified");
assert!(t.verify_integrity());
let mut t2 = t.clone();
t2.content = "tampered".to_string();
assert!(!t2.verify_integrity());
}
#[test]
fn test_tagging() {
let mut t = FleetTile::new("test", "1", "x", "a");
t.tag("critical");
t.tag("verified");
t.tag("critical"); assert_eq!(t.tags.len(), 2);
assert!(t.has_tag("critical"));
}
#[test]
fn test_merge_tiles() {
let mut a = FleetTile::new("room", "1", "content-a", "agent-a");
a.constraints.add("bounds", "agent-a");
a.metrics.record_satisfied("agent-a", 100);
let mut b = FleetTile::new("room", "1", "content-b", "agent-b");
b.constraints.add("norm", "agent-b");
b.metrics.record_satisfied("agent-b", 200);
a.merge(&b);
assert!(a.constraints.contains("bounds"));
assert!(a.constraints.contains("norm"));
assert_eq!(a.metrics.total_satisfied(), 300);
}
#[test]
fn test_merge_different_ids_ignored() {
let mut a = FleetTile::new("room", "1", "a", "a");
let b = FleetTile::new("room", "2", "b", "b");
a.merge(&b);
assert_eq!(a.content, "a"); }
#[test]
fn test_json_roundtrip() {
let mut t = FleetTile::new("room", "1", "test content", "agent");
t.tag("important");
t.constraints.add("bounds", "node-a");
let json = t.to_json();
assert!(json.contains("room"));
assert!(json.contains("important"));
}
}