lernie 0.1.91

lernie: the operator seat — the window and wire client for a yog server
//! **One engine, every name it is held under** (bl-13b9; DESIGN §4.39).
//!
//! The channel directory is the operator's, and it may lawfully name one
//! engine twice — two entries terminating at one listener (§8.2), or a
//! workspace entry enrolled before this box became an engine itself. Each is a
//! channel and each answers; painted one section a channel, the seat showed
//! one server as two engines and counted its waiting twice. The window's unit
//! is the ENGINE, so the channels are folded by the address they dial before
//! anything is counted or painted, and every name stays beside the one row.
//!
//! **It is a query over the roster and never a second roster** — the chunks
//! stay one per channel, because a reply is stamped with the channel it came
//! down and a gesture goes down the channel that can address its wall.

use crate::reply::roster::WsRow;

use super::{Aim, Chunk, Held, Model};

/// **One engine as the window shows it**: every channel this seat holds that
/// dials one address, the lead apart from the rest so that an engine with no
/// channel cannot be written.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Engine {
    /// This box's own engine where it is one of them — the channel that
    /// addresses every wall by its own name — else the first held.
    pub lead: Chunk,
    /// Every other name it is held under, in the order held.
    pub also: Vec<Chunk>,
}

impl Engine {
    /// The name the engine's row goes by: its lead's.
    pub(crate) fn name(&self) -> &str {
        &self.lead.channel.name
    }

    /// Every channel it is held under, the lead first.
    pub(crate) fn chunks(&self) -> impl Iterator<Item = &Chunk> {
        std::iter::once(&self.lead).chain(&self.also)
    }

    /// Whether `channel` is one of the names this engine is held under.
    pub fn holds(&self, channel: &str) -> bool {
        self.chunks().any(|chunk| chunk.channel.name == channel)
    }

    /// **Reached if any of its channels answered**; else the first that says
    /// why — it will not dial, or it refused the roster; else not heard from
    /// yet.
    pub fn held(&self) -> Held {
        let all = || self.chunks().map(|chunk| &chunk.held);
        all()
            .find(|held| **held == Held::Heard)
            .or_else(|| all().find(|held| **held != Held::Unheard))
            .unwrap_or(&self.lead.held)
            .clone()
    }

    /// **Each wall once**, as the first channel to answer it said it.
    pub fn walls(&self) -> Vec<WsRow> {
        let mut out: Vec<WsRow> = Vec::new();
        for wall in self.chunks().flat_map(|chunk| &chunk.walls) {
            if !out.iter().any(|held| held.workspace == wall.workspace) {
                out.push(wall.clone());
            }
        }
        out
    }

    /// **Where a gesture at `row` goes**: down the first of this engine's
    /// channels that can address it, or nowhere.
    pub fn aim(&self, row: &WsRow) -> Option<Aim> {
        self.chunks().find_map(|chunk| {
            chunk.channel.address(row).map(|address| Aim {
                channel: chunk.channel.name.clone(),
                address,
            })
        })
    }

    /// **The row's words**: the lead's header, and every other name it is
    /// held under beside it.
    pub fn header(&self) -> String {
        let lead = crate::ui::roster::header(&self.lead.channel);
        if self.also.is_empty() {
            return lead;
        }
        let also: Vec<&str> = self
            .also
            .iter()
            .map(|chunk| chunk.channel.name.as_str())
            .collect();
        format!("{lead} — {ALSO} {}", also.join(", "))
    }
}

/// What precedes the other names one engine is held under.
pub const ALSO: &str = "also held as";

impl Model {
    /// **The roster folded by address**, in the order its first channel was
    /// held. A channel that dials nothing this seat knows stands alone.
    pub fn folded(&self) -> Vec<Engine> {
        let mut out: Vec<Engine> = Vec::new();
        for chunk in &self.roster {
            let same = |engine: &&mut Engine| {
                chunk.channel.dials.is_some() && engine.lead.channel.dials == chunk.channel.dials
            };
            match out.iter_mut().find(same) {
                // The box's own engine leads: it addresses every wall.
                Some(engine) if chunk.channel.named_there.is_none() => {
                    let was = std::mem::replace(&mut engine.lead, chunk.clone());
                    engine.also.insert(0, was);
                }
                Some(engine) => engine.also.push(chunk.clone()),
                None => out.push(Engine {
                    lead: chunk.clone(),
                    also: Vec::new(),
                }),
            }
        }
        out
    }

    /// **The engine `channel` is one name of**, by its lead's name.
    pub fn lead_of(&self, channel: &str) -> Option<String> {
        self.folded()
            .into_iter()
            .find(|engine| engine.holds(channel))
            .map(|engine| engine.name().to_owned())
    }

    /// **The engine a gesture down `channel` is a use of** — its lead's name,
    /// and a channel this seat no longer holds is its own.
    pub fn engine_of(&self, channel: &str) -> String {
        self.lead_of(channel).unwrap_or_else(|| channel.to_owned())
    }
}

#[cfg(test)]
mod tests;