use sha2::{Digest, Sha256};
use unicode_normalization::UnicodeNormalization;
use chappi_syntax::{Document, Form, canonicalize};
use crate::{
AffectIntensity, AttentionMode, CognitiveState, Confidence, ConstraintStrength, DesireForce,
FocusFrame, GoalStatus, Lifetime, MetacognitionStatus, Node, NodeKind, PredictionStatus,
QuestionStatus, Source, TensionStatus,
};
pub fn state_hash(state: &CognitiveState) -> [u8; 32] {
Sha256::digest(semantic_bytes(state)).into()
}
fn semantic_bytes(state: &CognitiveState) -> Vec<u8> {
let mut output = Vec::new();
output.extend_from_slice(b"chappi-state\0");
output.extend_from_slice(&state.version.to_be_bytes());
let mut nodes = state.nodes.iter().map(node_bytes).collect::<Vec<_>>();
nodes.sort();
write_records(&mut output, &nodes);
let mut relations = state
.relations
.iter()
.map(|relation| {
let mut bytes = Vec::new();
write_str(&mut bytes, relation.kind.as_str());
write_str(&mut bytes, &relation.source);
write_str(&mut bytes, &relation.target);
bytes
})
.collect::<Vec<_>>();
relations.sort();
write_records(&mut output, &relations);
let mut streams = state
.focus_streams
.iter()
.map(|stream| {
let mut bytes = Vec::new();
write_str(&mut bytes, &stream.id);
let mut frames = stream.frames.iter().map(focus_bytes).collect::<Vec<_>>();
frames.sort();
write_records(&mut bytes, &frames);
bytes
})
.collect::<Vec<_>>();
streams.sort();
write_records(&mut output, &streams);
output
}
fn node_bytes(node: &Node) -> Vec<u8> {
let mut bytes = Vec::new();
write_str(&mut bytes, &node.id);
bytes.extend_from_slice(&node.revision.to_be_bytes());
write_str(&mut bytes, &node.owner);
write_str(&mut bytes, lifetime(node.lifetime));
bytes.push(node.salience);
write_node_kind(&mut bytes, &node.kind);
write_form(&mut bytes, &node.payload);
bytes
}
fn write_node_kind(output: &mut Vec<u8>, kind: &NodeKind) {
match kind {
NodeKind::Entity { entity_kind } => {
write_str(output, "entity");
write_str(output, entity_kind);
}
NodeKind::Claim { confidence } => {
write_str(output, "claim");
write_str(output, confidence_text(*confidence));
}
NodeKind::Evidence {
evidence_kind,
source,
} => {
write_str(output, "evidence");
write_str(output, evidence_kind);
match source {
Source::Symbol(value) => {
write_str(output, "symbol");
write_str(output, value);
}
Source::String(value) => {
write_str(output, "string");
write_str(output, value);
}
}
}
NodeKind::Goal { status } => write_pair(output, "goal", goal_status(*status)),
NodeKind::Constraint { strength } => {
write_pair(output, "constraint", constraint_strength(*strength));
}
NodeKind::Question { status } => write_pair(output, "question", question_status(*status)),
NodeKind::Relationship { target, status } => {
write_str(output, "relationship");
write_str(output, target);
write_str(output, status);
}
NodeKind::Desire { force } => write_pair(output, "desire", desire_force(*force)),
NodeKind::Attention { mode } => write_pair(output, "attention", attention_mode(*mode)),
NodeKind::Association => write_str(output, "association"),
NodeKind::Tension { status } => write_pair(output, "tension", tension_status(*status)),
NodeKind::Prediction { confidence, status } => {
write_str(output, "prediction");
write_str(output, confidence_text(*confidence));
write_str(output, prediction_status(*status));
}
NodeKind::Perspective(value) => {
write_str(output, "perspective");
write_str(output, &value.target);
write_str(output, confidence_text(value.confidence));
}
NodeKind::Affect {
target,
affect_kind,
intensity,
} => {
write_str(output, "affect");
write_str(output, target);
write_str(output, affect_kind);
write_str(output, affect_intensity(*intensity));
}
NodeKind::Metacognition { status } => {
write_pair(output, "metacognition", metacognition_status(*status));
}
}
}
fn focus_bytes(frame: &FocusFrame) -> Vec<u8> {
let mut bytes = Vec::new();
write_str(&mut bytes, &frame.stream_id);
bytes.extend_from_slice(&frame.sequence.to_be_bytes());
write_str(&mut bytes, &frame.owner);
write_str(&mut bytes, frame.transition.as_str());
match frame.previous_sequence {
None => bytes.push(0),
Some(sequence) => {
bytes.push(1);
bytes.extend_from_slice(&sequence.to_be_bytes());
}
}
write_sorted_strings(&mut bytes, &frame.foreground);
write_sorted_strings(&mut bytes, &frame.background);
write_sorted_strings(&mut bytes, &frame.residue);
write_form(&mut bytes, &frame.trigger);
bytes
}
fn write_sorted_strings(output: &mut Vec<u8>, values: &[String]) {
let mut values = values
.iter()
.map(|value| value.nfc().collect::<String>())
.collect::<Vec<_>>();
values.sort_unstable();
output.extend_from_slice(&(values.len() as u64).to_be_bytes());
for value in values {
write_bytes(output, value.as_bytes());
}
}
fn write_form(output: &mut Vec<u8>, form: &Form) {
let bytes = canonicalize(&Document {
forms: vec![form.clone()],
});
write_bytes(output, &bytes);
}
fn write_pair(output: &mut Vec<u8>, left: &str, right: &str) {
write_str(output, left);
write_str(output, right);
}
fn write_records(output: &mut Vec<u8>, records: &[Vec<u8>]) {
output.extend_from_slice(&(records.len() as u64).to_be_bytes());
for record in records {
write_bytes(output, record);
}
}
fn write_str(output: &mut Vec<u8>, value: &str) {
let normalized = value.nfc().collect::<String>();
write_bytes(output, normalized.as_bytes());
}
fn write_bytes(output: &mut Vec<u8>, value: &[u8]) {
output.extend_from_slice(&(value.len() as u64).to_be_bytes());
output.extend_from_slice(value);
}
fn lifetime(value: Lifetime) -> &'static str {
match value {
Lifetime::Persistent => "persistent",
Lifetime::Working => "working",
Lifetime::Transient => "transient",
}
}
fn confidence_text(value: Confidence) -> &'static str {
match value {
Confidence::Tentative => "tentative",
Confidence::Plausible => "plausible",
Confidence::Likely => "likely",
Confidence::Settled => "settled",
}
}
fn goal_status(value: GoalStatus) -> &'static str {
match value {
GoalStatus::Considering => "considering",
GoalStatus::Active => "active",
GoalStatus::Committed => "committed",
GoalStatus::Done => "done",
GoalStatus::Dropped => "dropped",
}
}
fn constraint_strength(value: ConstraintStrength) -> &'static str {
match value {
ConstraintStrength::Soft => "soft",
ConstraintStrength::Hard => "hard",
}
}
fn question_status(value: QuestionStatus) -> &'static str {
match value {
QuestionStatus::Open => "open",
QuestionStatus::Answered => "answered",
QuestionStatus::Deferred => "deferred",
}
}
fn desire_force(value: DesireForce) -> &'static str {
match value {
DesireForce::Weak => "weak",
DesireForce::Moderate => "moderate",
DesireForce::Strong => "strong",
}
}
fn attention_mode(value: AttentionMode) -> &'static str {
match value {
AttentionMode::Foreground => "foreground",
AttentionMode::Background => "background",
}
}
fn tension_status(value: TensionStatus) -> &'static str {
match value {
TensionStatus::Open => "open",
TensionStatus::Resolved => "resolved",
}
}
fn prediction_status(value: PredictionStatus) -> &'static str {
match value {
PredictionStatus::Pending => "pending",
PredictionStatus::Matched => "matched",
PredictionStatus::Surprised => "surprised",
}
}
fn affect_intensity(value: AffectIntensity) -> &'static str {
match value {
AffectIntensity::Weak => "weak",
AffectIntensity::Moderate => "moderate",
AffectIntensity::Strong => "strong",
}
}
fn metacognition_status(value: MetacognitionStatus) -> &'static str {
match value {
MetacognitionStatus::Uncertain => "uncertain",
MetacognitionStatus::Monitoring => "monitoring",
MetacognitionStatus::Resolved => "resolved",
}
}