//! Live worlds — `wai.live.world` ([extensions/live-worlds.md]).
//!
//! A shared world in a room. The room authority signs a world statement (`WLW1`) naming the
//! world the room's operations replay in and the bounds each holds to. Each actor (a principal
//! whose grant holds the attribute `world` with the value `act`) publishes a world track,
//! `[room, principal, "world"]`, whose groups are op batches, each signed into the actor's
//! receipt chain as a room's media track is. Every participant merges the ops of every track
//! under the canonical order and replays them to one session hash.
//!
//! What this module implements, by section of the extension:
//!
//! - §3: the statement, its rules and its signature ([`Statement`]);
//! - §4, §9.2: a participant's copy of the world, object by object ([`World`]): the grant, the
//!   track's groups, its receipts (steps 1 to 3, [`World::offer`]), and its ops (steps 4 and 5,
//!   [`World::offer_batch`], which a page whose transport client checked the receipts calls);
//! - §8.2: the carry a member publishes in each epoch it reaches ([`World::carry`]);
//! - §9.3: the session hash ([`World::session_hash`]);
//! - §9: verification of a room's world tracks as given ([`verify`]).
//!
//! The corpus `live-conformance/worlds/` is written by [`corpus::generate`] and checked by
//! [`corpus::verify`], and by `live-conformance/live_worlds_verify.py`, which shares no code
//! with this module.
//!
//! [extensions/live-worlds.md]: ../../extensions/live-worlds.md

use std::collections::{BTreeMap, HashMap};

use ed25519_dalek::{Signer, SigningKey};
use sha2::{Digest, Sha256};

use crate::jwp_delivery::{self as jwp, binary::GroupReceipts, GroupReceipt, ReceiptsTrack};
use crate::live_events as events;
use crate::live_room::{self as room, Descriptor, HeldGrant};
use crate::world::CausalOp;
use crate::world_model::{self, ModelHeader};

pub mod corpus;

/// The record this extension registers.
pub const CAPABILITY: &str = "wai.live.world";
/// The magic of a world statement (§3.1).
pub const MAGIC: [u8; 4] = *b"WLW1";
/// The statement's version in this revision.
pub const VERSION: u8 = 1;
/// The domain the statement's signature is made under (§3.3).
pub const DOMAIN: &[u8] = b"wai:live-world\x01";
/// The one capability whose ops a world track carries in this revision (§3.4 step 3).
pub const MODEL_CAPABILITY: &str = "wai.world.model";
/// The grant attribute that lets a principal act (§5.1), and its value.
pub const ATTR: &str = "world";
pub const ACT: &str = "act";
/// The third field of a world track's namespace (§4.1), its Track Name, its receipts track's.
pub const FIELD: &str = "world";
pub const TRACK: &str = "0";
pub const RECEIPTS_TRACK: &str = "0_receipts";
/// The `track_alias` a world track's receipts carry (§4.3).
pub const RECEIPT_ALIAS: u64 = 4;
/// The bounds of `max_op_bytes` and `max_ops` (§3.1).
pub const MIN_OP_BYTES: u32 = 136;
pub const MAX_OP_BYTES: u32 = 65_536;
pub const MAX_OPS: u16 = 4_096;
/// The length of a `wai.world.model` world (§3.2).
pub const MODEL_WORLD_LEN: usize = 52;

// ---------------------------------------------------------------------------------------
// §11 Reasons
// ---------------------------------------------------------------------------------------

/// Why a world, a track or an object is refused (§11).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Reason {
    /// A code of live-rooms §11 or live-events §9: a descriptor or grant that does not hold,
    /// and a statement that does not read or verify (§3.4 steps 1, 2, 5 and 6).
    Room(room::Reason),
    /// A code of jwp-receipts §3.3 (§9.2 step 3).
    Receipt(jwp::Reason),
    WorldCapabilityUnknown,
    WorldStatementInvalid,
    WorldNotGranted,
    WorldGap,
    WorldIncomplete,
    OpMalformed,
    BatchTooLarge,
    OpTooLarge,
    BadOpSignature,
    ActorNotOwner,
    OpNotAction,
    LamportRegress,
    Equivocation,
}

impl Reason {
    /// The codes this extension adds, in §11's order.
    pub const ALL: [Reason; 13] = [
        Reason::WorldCapabilityUnknown,
        Reason::WorldStatementInvalid,
        Reason::WorldNotGranted,
        Reason::WorldGap,
        Reason::WorldIncomplete,
        Reason::OpMalformed,
        Reason::BatchTooLarge,
        Reason::OpTooLarge,
        Reason::BadOpSignature,
        Reason::ActorNotOwner,
        Reason::OpNotAction,
        Reason::LamportRegress,
        Reason::Equivocation,
    ];

    pub fn code(self) -> &'static str {
        match self {
            Reason::Room(r) => r.code(),
            Reason::Receipt(r) => r.label(),
            Reason::WorldCapabilityUnknown => "world_capability_unknown",
            Reason::WorldStatementInvalid => "world_statement_invalid",
            Reason::WorldNotGranted => "world_not_granted",
            Reason::WorldGap => "world_gap",
            Reason::WorldIncomplete => "world_incomplete",
            Reason::OpMalformed => "op_malformed",
            Reason::BatchTooLarge => "batch_too_large",
            Reason::OpTooLarge => "op_too_large",
            Reason::BadOpSignature => "bad_op_signature",
            Reason::ActorNotOwner => "actor_not_owner",
            Reason::OpNotAction => "op_not_action",
            Reason::LamportRegress => "lamport_regress",
            Reason::Equivocation => "equivocation",
        }
    }

    fn ev(r: events::Reason) -> Reason {
        Reason::Room(room::Reason::Events(r))
    }
}

impl std::fmt::Display for Reason {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.code())
    }
}

impl std::error::Error for Reason {}

fn sha256(parts: &[&[u8]]) -> [u8; 32] {
    let mut h = Sha256::new();
    for p in parts {
        h.update(p);
    }
    h.finalize().into()
}

pub fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}

// ---------------------------------------------------------------------------------------
// §3 The world statement
// ---------------------------------------------------------------------------------------

/// A world statement (§3.1).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Statement {
    pub room: [u8; 32],
    pub capability: String,
    pub world: Vec<u8>,
    pub max_op_bytes: u32,
    pub max_ops: u16,
    pub sig: [u8; 64],
}

/// The 52 bytes of a model header (§3.2): what [`ModelHeader::preimage`] writes.
pub fn model_world(h: &ModelHeader) -> Vec<u8> {
    h.preimage()
}

/// A `wai.world.model` world read (§3.2), or `None` where it is not that form.
pub fn read_model_world(b: &[u8]) -> Option<ModelHeader> {
    if b.len() != MODEL_WORLD_LEN {
        return None;
    }
    let u32_at = |i: usize| u32::from_le_bytes(b[i..i + 4].try_into().expect("4 bytes"));
    let h = ModelHeader {
        model_id: b[0..32].try_into().expect("32 bytes"),
        seed: u64::from_le_bytes(b[32..40].try_into().expect("8 bytes")),
        width: u32_at(40),
        height: u32_at(44),
        fps: u32_at(48),
    };
    (h.width != 0 && h.height != 0 && h.fps != 0).then_some(h)
}

/// A reader over a statement's bytes: a field past the end is `truncated` (§3.4 step 2).
struct Cursor<'a> {
    b: &'a [u8],
    at: usize,
}

impl<'a> Cursor<'a> {
    fn take(&mut self, n: usize) -> Result<&'a [u8], Reason> {
        let end = self.at.checked_add(n).filter(|&e| e <= self.b.len()).ok_or(Reason::ev(events::Reason::Truncated))?;
        let s = &self.b[self.at..end];
        self.at = end;
        Ok(s)
    }
}

impl Statement {
    /// A statement for a `wai.world.model` world, unsigned.
    pub fn for_model(room: [u8; 32], header: &ModelHeader, max_op_bytes: u32, max_ops: u16) -> Statement {
        Statement { room, capability: MODEL_CAPABILITY.into(), world: model_world(header), max_op_bytes, max_ops, sig: [0; 64] }
    }

    /// The bytes the digest covers: every field but `sig` (§3.3).
    pub fn body(&self) -> Vec<u8> {
        let mut o = Vec::with_capacity(48 + self.capability.len() + self.world.len());
        o.extend_from_slice(&MAGIC);
        o.push(VERSION);
        o.extend_from_slice(&self.room);
        o.push(self.capability.len() as u8);
        o.extend_from_slice(self.capability.as_bytes());
        o.extend_from_slice(&(self.world.len() as u16).to_le_bytes());
        o.extend_from_slice(&self.world);
        o.extend_from_slice(&self.max_op_bytes.to_le_bytes());
        o.extend_from_slice(&self.max_ops.to_le_bytes());
        o
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        let mut o = self.body();
        o.extend_from_slice(&self.sig);
        o
    }

    pub fn digest(&self) -> [u8; 32] {
        sha256(&[&self.body()])
    }

    /// Signed by `authority` (§3.3).
    pub fn sign(mut self, authority: &SigningKey) -> Statement {
        let mut m = DOMAIN.to_vec();
        m.extend_from_slice(&self.body());
        self.sig = authority.sign(&m).to_bytes();
        self
    }

    /// §3.4 steps 1 to 4: the record read and its fields' rules.
    pub fn read(b: &[u8]) -> Result<Statement, Reason> {
        if b.len() < 4 {
            return Err(Reason::ev(events::Reason::Truncated));
        }
        if b[..4] != MAGIC {
            return Err(Reason::ev(events::Reason::Malformed));
        }
        if b.len() == 4 {
            return Err(Reason::ev(events::Reason::Truncated));
        }
        if b[4] != VERSION {
            return Err(Reason::ev(events::Reason::UnknownVersion));
        }
        let mut c = Cursor { b, at: 5 };
        let room: [u8; 32] = c.take(32)?.try_into().expect("32 bytes");
        let n = c.take(1)?[0] as usize;
        let capability = c.take(n)?.to_vec();
        let n = u16::from_le_bytes(c.take(2)?.try_into().expect("2 bytes")) as usize;
        let world = c.take(n)?.to_vec();
        let max_op_bytes = u32::from_le_bytes(c.take(4)?.try_into().expect("4 bytes"));
        let max_ops = u16::from_le_bytes(c.take(2)?.try_into().expect("2 bytes"));
        let sig: [u8; 64] = c.take(64)?.try_into().expect("64 bytes");
        let at = c.at;
        if at != b.len() {
            return Err(Reason::ev(events::Reason::Malformed));
        }
        if capability != MODEL_CAPABILITY.as_bytes() {
            return Err(Reason::WorldCapabilityUnknown);
        }
        if read_model_world(&world).is_none() || !(MIN_OP_BYTES..=MAX_OP_BYTES).contains(&max_op_bytes) || !(1..=MAX_OPS).contains(&max_ops) {
            return Err(Reason::WorldStatementInvalid);
        }
        Ok(Statement { room, capability: MODEL_CAPABILITY.into(), world, max_op_bytes, max_ops, sig })
    }

    /// §3.4 whole: read beside the descriptor `d`, whose room id is `room`.
    pub fn check(b: &[u8], d: &Descriptor, room: &[u8; 32]) -> Result<Statement, Reason> {
        let s = Statement::read(b)?;
        if s.room != *room {
            return Err(Reason::ev(events::Reason::WrongRoom));
        }
        let mut m = DOMAIN.to_vec();
        m.extend_from_slice(&s.body());
        crate::sig::verify_strict(&d.authority, &m, &s.sig).map_err(|e| match e {
            crate::sig::SigError::BadKey => Reason::ev(events::Reason::BadKey),
            crate::sig::SigError::BadSignature => Reason::ev(events::Reason::BadSignature),
        })?;
        Ok(s)
    }

    /// The model header of a statement [`Statement::read`] accepted.
    pub fn header(&self) -> ModelHeader {
        read_model_world(&self.world).expect("a statement read holds a model world")
    }
}

// ---------------------------------------------------------------------------------------
// §5.1 The attribute, §4.1 naming
// ---------------------------------------------------------------------------------------

/// Whether a grant's attributes let its principal act (§5.1).
pub fn acts(attrs: &[(String, String)]) -> bool {
    attrs.iter().any(|(k, v)| k == ATTR && v == ACT)
}

/// The namespace of `principal`'s world track in `room` (§4.1).
pub fn track_namespace(room: &[u8; 32], principal: &[u8; 32]) -> Vec<String> {
    vec![hex(room), hex(principal), FIELD.to_string()]
}

// ---------------------------------------------------------------------------------------
// §4, §9.2 A participant's copy of the world
// ---------------------------------------------------------------------------------------

/// An op's canonical key (interactive-worlds §5.2): `(effect_tick, lamport, actor)`.
type OrderKey = (u64, u64, [u8; 32]);
/// An op's id (interactive-worlds §5.1): `(actor, lamport)`.
type OpKey = ([u8; 32], u64);

/// What one world track has given so far.
#[derive(Clone, Debug, Default)]
struct Track {
    /// The Group ID the next object must have; `None` before the first.
    next: Option<u64>,
    /// The track's last group receipt, the next one's parent.
    prev: Option<GroupReceipt>,
    /// The greatest `lamport` of the owner's ops the track carried (§4.2).
    last_lamport: Option<u64>,
}

/// What an object added to the world.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Admitted {
    /// Ops admitted for the first time.
    pub ops: usize,
    /// Ops the world already held (redeliveries, §4.2).
    pub redelivered: usize,
}

/// A participant's copy of a room's world (§9): the statement, the actors, each track's
/// progress, and the admitted ops in canonical order.
#[derive(Clone, Debug)]
pub struct World {
    pub statement: Statement,
    header: ModelHeader,
    private: bool,
    actors: Vec<[u8; 32]>,
    tracks: HashMap<[u8; 32], Track>,
    /// Admitted ops by canonical key (interactive-worlds §5.2), their bytes.
    ops: BTreeMap<OrderKey, Vec<u8>>,
    /// Admitted ops by op id, their canonical key.
    ids: HashMap<OpKey, OrderKey>,
}

/// One op of a batch, read.
struct Read<'a> {
    op: CausalOp,
    bytes: &'a [u8],
}

impl World {
    /// The world of a room: its statement checked beside the descriptor (§3.4), its actors the
    /// principals of the held grants that may act (§5.1). `private`: the room has a
    /// confidentiality record (§8).
    pub fn new(d: &Descriptor, grants: &[HeldGrant], statement: &[u8], private: bool) -> Result<World, Reason> {
        let room = d.room_id();
        let statement = Statement::check(statement, d, &room)?;
        let header = statement.header();
        let mut actors: Vec<[u8; 32]> = grants.iter().filter(|h| acts(&h.grant.attrs)).map(|h| h.grant.principal).collect();
        actors.sort_unstable();
        actors.dedup();
        Ok(World { statement, header, private, actors, tracks: HashMap::new(), ops: BTreeMap::new(), ids: HashMap::new() })
    }

    /// Whether the world is private (§8).
    pub fn private(&self) -> bool {
        self.private
    }

    /// Take in the grants of a room read again: a principal granted since becomes an actor.
    pub fn set_grants(&mut self, grants: &[HeldGrant]) {
        for h in grants {
            if acts(&h.grant.attrs) && !self.actors.contains(&h.grant.principal) {
                self.actors.push(h.grant.principal);
            }
        }
        self.actors.sort_unstable();
    }

    pub fn is_actor(&self, p: &[u8; 32]) -> bool {
        self.actors.binary_search(p).is_ok()
    }

    /// The Group ID `owner`'s track needs next, once it has given an object.
    pub fn next_group(&self, owner: &[u8; 32]) -> Option<u64> {
        self.tracks.get(owner).and_then(|t| t.next)
    }

    /// §9.2 steps 1 to 5 for one object of `owner`'s track: its Group ID, its receipts-track
    /// object, the bytes its receipt covers, and, in a private world, the op batch that opened
    /// from them. Nothing is changed when it is refused.
    pub fn offer(&mut self, owner: &[u8; 32], group: u64, receipts: &[u8], covered: &[u8], opened: Option<&[u8]>) -> Result<Admitted, Reason> {
        let t = self.precheck(owner, group)?;
        let g = self.receipts_step(owner, group, receipts, covered, &t)?;
        let batch = if self.private { opened.unwrap_or(covered) } else { covered };
        let a = self.ops_step(owner, batch, &t)?;
        self.commit(owner, group, Some(g), a.1, a.2);
        Ok(a.0)
    }

    /// §9.2 steps 1, 2, 4 and 5 for one object whose receipts were checked already, as a
    /// page's transport client checks them before it hands the object on (§12): the room
    /// client's verifier is the relay door's.
    pub fn offer_batch(&mut self, owner: &[u8; 32], group: u64, batch: &[u8]) -> Result<Admitted, Reason> {
        let t = self.precheck(owner, group)?;
        let a = self.ops_step(owner, batch, &t)?;
        self.commit(owner, group, None, a.1, a.2);
        Ok(a.0)
    }

    /// Steps 1 and 2.
    fn precheck(&self, owner: &[u8; 32], group: u64) -> Result<Track, Reason> {
        if !self.is_actor(owner) {
            return Err(Reason::WorldNotGranted);
        }
        let t = self.tracks.get(owner).cloned().unwrap_or_default();
        match t.next {
            None if !self.private && group != 0 => return Err(Reason::WorldGap),
            Some(n) if group != n => return Err(Reason::WorldGap),
            _ => {}
        }
        Ok(t)
    }

    /// Step 3: the receipts-track object, against the bytes its receipt covers.
    fn receipts_step(&self, owner: &[u8; 32], group: u64, receipts: &[u8], covered: &[u8], t: &Track) -> Result<GroupReceipt, Reason> {
        let rc = |e: jwp::DeliveryError| Reason::Receipt(e.reason);
        let r = GroupReceipts::from_binary(receipts).map_err(rc)?;
        if r.objects.len() != 1 {
            return Err(Reason::Receipt(jwp::Reason::Malformed));
        }
        let (g, o) = (&r.group, &r.objects[0]);
        let track = ReceiptsTrack { signer_pubkey: *owner, track_alias: RECEIPT_ALIAS };
        if g.signer_pubkey != *owner || g.track_alias != RECEIPT_ALIAS || g.group_id != group {
            return Err(Reason::Receipt(jwp::Reason::MoqtMismatch));
        }
        o.check_moqt(&track, group, 0).map_err(rc)?;
        g.verify().map_err(rc)?;
        // In a private world the first object opened above group 0 names a parent the verifier
        // does not hold, and starts its chain (§9.2 step 3).
        if !(self.private && t.prev.is_none() && group > 0) {
            g.check_parent(t.prev.as_ref()).map_err(rc)?;
        }
        o.accept(g, covered).map_err(rc)?;
        jwp::read_group_total(g, &[jwp::Delivered { object: o, envelope: covered }]).map_err(rc)?;
        Ok(g.clone())
    }

    /// Steps 4 and 5 over `batch`, without changing the world: what to admit, how many
    /// redeliveries, and the track's greatest owner `lamport` after it.
    #[allow(clippy::type_complexity)]
    fn ops_step(&self, owner: &[u8; 32], batch: &[u8], t: &Track) -> Result<(Admitted, Vec<(CausalOp, Vec<u8>)>, Option<u64>), Reason> {
        let mut read: Vec<Read<'_>> = Vec::new();
        let mut rest = batch;
        if rest.is_empty() {
            return Err(Reason::OpMalformed);
        }
        while !rest.is_empty() {
            let (op, used) = CausalOp::from_bytes(rest).map_err(|_| Reason::OpMalformed)?;
            read.push(Read { op, bytes: &rest[..used] });
            rest = &rest[used..];
        }
        if read.len() > self.statement.max_ops as usize {
            return Err(Reason::BatchTooLarge);
        }
        if read.iter().any(|r| r.bytes.len() > self.statement.max_op_bytes as usize) {
            return Err(Reason::OpTooLarge);
        }
        let mut last = t.last_lamport;
        let mut fresh: Vec<(CausalOp, Vec<u8>)> = Vec::new();
        let mut admitted = Admitted::default();
        for r in read {
            let op = r.op;
            if op.verify().is_err() {
                return Err(Reason::BadOpSignature);
            }
            if !self.private && op.actor != *owner {
                return Err(Reason::ActorNotOwner);
            }
            if op.mutation_kind != world_model::MUT_ACTION || !op.deps.is_empty() || world_model::decode_action(&op).is_err() {
                return Err(Reason::OpNotAction);
            }
            let id = (op.actor, op.lamport);
            let held: Option<&[u8]> = match self.ids.get(&id) {
                Some(k) => self.ops.get(k).map(Vec::as_slice),
                None => fresh.iter().find(|(o, _)| (o.actor, o.lamport) == id).map(|(_, b)| b.as_slice()),
            };
            let redelivery = match held {
                Some(b) if b == r.bytes => true,
                Some(_) => return Err(Reason::Equivocation),
                None => false,
            };
            if op.actor == *owner {
                if !redelivery && last.is_some_and(|l| op.lamport <= l) {
                    return Err(Reason::LamportRegress);
                }
                last = Some(last.map_or(op.lamport, |l| l.max(op.lamport)));
            }
            if redelivery {
                admitted.redelivered += 1;
            } else {
                admitted.ops += 1;
                fresh.push((op, r.bytes.to_vec()));
            }
        }
        Ok((admitted, fresh, last))
    }

    fn commit(&mut self, owner: &[u8; 32], group: u64, g: Option<GroupReceipt>, fresh: Vec<(CausalOp, Vec<u8>)>, last: Option<u64>) {
        let t = self.tracks.entry(*owner).or_default();
        t.next = Some(group + 1);
        if g.is_some() {
            t.prev = g;
        }
        t.last_lamport = last;
        for (op, b) in fresh {
            let k = op.order_key();
            self.ids.insert((op.actor, op.lamport), k);
            self.ops.insert(k, b);
        }
    }

    /// How many ops the world holds.
    pub fn count(&self) -> usize {
        self.ops.len()
    }

    /// The admitted ops in canonical order, concatenated: the merged log.
    pub fn log(&self) -> Vec<u8> {
        self.ops.values().flat_map(|b| b.iter().copied()).collect()
    }

    /// The admitted ops, each actor's count, by actor.
    pub fn by_actor(&self) -> BTreeMap<[u8; 32], usize> {
        let mut m = BTreeMap::new();
        for k in self.ops.keys() {
            *m.entry(k.2).or_insert(0) += 1;
        }
        m
    }

    /// The session hash (§9.3).
    pub fn session_hash(&self) -> [u8; 32] {
        let ops: Vec<CausalOp> = self.ops.values().map(|b| CausalOp::from_bytes(b).expect("an admitted op reads").0).collect();
        world_model::replay(&self.header, &ops, false).expect("admitted ops are actions").session_hash
    }

    /// A carry (§8.2): every admitted op, the actors in ascending order of their keys and each
    /// actor's ops in ascending `lamport`, in batches of at most `max_ops`.
    pub fn carry(&self) -> Vec<Vec<u8>> {
        let mut keys: Vec<&OrderKey> = self.ops.keys().collect();
        keys.sort_by_key(|k| (k.2, k.1));
        keys.chunks(self.statement.max_ops as usize).map(|c| c.iter().flat_map(|k| self.ops[*k].iter().copied()).collect()).collect()
    }
}

// ---------------------------------------------------------------------------------------
// §9 Verification
// ---------------------------------------------------------------------------------------

/// One object of a world track, as a verifier is given it (§9.1).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ObjectInput {
    pub group: u64,
    /// The receipts track's object of the same group (jwp-receipts §2.7.3).
    pub receipts: Vec<u8>,
    /// The bytes the receipt covers: the op batch, or in a private world the sealed bytes.
    pub payload: Vec<u8>,
    /// In a private world, the op batch that opened from `payload`.
    pub opened: Option<Vec<u8>>,
}

/// One world track: its owner and its objects in group order.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TrackInput {
    pub owner: [u8; 32],
    pub objects: Vec<ObjectInput>,
}

/// What a verifier is given (§9.1).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Input {
    pub descriptor: Vec<u8>,
    pub grants: Vec<Vec<u8>>,
    pub statement: Vec<u8>,
    pub private: bool,
    pub tracks: Vec<TrackInput>,
}

/// Where a refusal is.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Where {
    Descriptor,
    Grant(usize),
    Statement,
    Object { track: usize, group: u64 },
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Refusal {
    pub reason: Reason,
    pub at: Where,
}

/// A world that verifies (§9.4).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Outcome {
    pub session_hash: [u8; 32],
    pub ops: usize,
    pub actors: BTreeMap<[u8; 32], usize>,
}

/// §9 over `input`: the first refusal, or the outcome.
pub fn verify(input: &Input) -> Result<Outcome, Refusal> {
    let d = Descriptor::parse(&input.descriptor).map_err(|r| Refusal { reason: Reason::Room(r), at: Where::Descriptor })?;
    let room = d.room_id();
    let held = room::check_grants(&d, &room, &input.grants).map_err(|(i, r)| Refusal { reason: Reason::Room(r), at: Where::Grant(i) })?;
    let mut w = World::new(&d, &held, &input.statement, input.private).map_err(|reason| Refusal { reason, at: Where::Statement })?;
    for (i, t) in input.tracks.iter().enumerate() {
        for o in &t.objects {
            w.offer(&t.owner, o.group, &o.receipts, &o.payload, o.opened.as_deref()).map_err(|reason| Refusal { reason, at: Where::Object { track: i, group: o.group } })?;
        }
    }
    Ok(Outcome { session_hash: w.session_hash(), ops: w.count(), actors: w.by_actor() })
}

#[cfg(test)]
mod tests {
    use super::*;

    fn key(i: u8) -> SigningKey {
        SigningKey::from_bytes(&[i; 32])
    }

    #[test]
    fn a_statement_reads_back_and_its_rules_hold() {
        let h = ModelHeader { model_id: [7; 32], seed: 0, width: 64, height: 64, fps: 24 };
        let s = Statement::for_model([1; 32], &h, 4096, 64).sign(&key(9));
        let b = s.to_bytes();
        assert_eq!(Statement::read(&b), Ok(s.clone()));
        assert_eq!(s.header(), h);
        assert_eq!(Statement::read(&b[..3]), Err(Reason::ev(events::Reason::Truncated)));
        assert_eq!(Statement::read(&b[..4]), Err(Reason::ev(events::Reason::Truncated)));
        assert_eq!(Statement::read(&b[..b.len() - 1]), Err(Reason::ev(events::Reason::Truncated)));
        let mut more = b.clone();
        more.push(0);
        assert_eq!(Statement::read(&more), Err(Reason::ev(events::Reason::Malformed)));
        let mut v2 = b.clone();
        v2[4] = 2;
        assert_eq!(Statement::read(&v2), Err(Reason::ev(events::Reason::UnknownVersion)));
        let mut cap = s.clone();
        cap.capability = "wai.world.live".into();
        assert_eq!(Statement::read(&cap.to_bytes()), Err(Reason::WorldCapabilityUnknown));
        for bad in [Statement { max_op_bytes: 135, ..s.clone() }, Statement { max_ops: 0, ..s.clone() }, Statement { world: vec![0; 51], ..s.clone() }] {
            assert_eq!(Statement::read(&bad.to_bytes()), Err(Reason::WorldStatementInvalid));
        }
        let zero_fps = ModelHeader { fps: 0, ..h };
        assert_eq!(Statement::read(&Statement::for_model([1; 32], &zero_fps, 4096, 64).to_bytes()), Err(Reason::WorldStatementInvalid));
    }

    #[test]
    fn the_attribute_is_act_exactly() {
        assert!(acts(&[("order".into(), "0".into()), (ATTR.into(), ACT.into())]));
        assert!(!acts(&[(ATTR.into(), "watch".into())]));
        assert!(!acts(&[]));
        assert_eq!(track_namespace(&[0xab; 32], &[0xcd; 32])[2], "world");
    }

    #[test]
    fn every_reason_has_its_own_code() {
        let mut codes: Vec<&str> = Reason::ALL.iter().map(|r| r.code()).collect();
        codes.sort_unstable();
        codes.dedup();
        assert_eq!(codes.len(), Reason::ALL.len());
    }
}
