//! The manifest store of live-provenance §4.1: JUMBF superboxes (`jumb`), each a description
//! box (`jumd`: type UUID, toggles `0x03`, label and its zero byte) and its content boxes, as
//! C2PA 2.4 §11.1.4 lays them out, with the hashed-URI hash of §8.4.2.3.

use super::bmff::{boxes_in, write_box, BoxRef};
use sha2::{Digest, Sha256};

const fn ty(prefix: [u8; 4]) -> [u8; 16] {
    [
        prefix[0], prefix[1], prefix[2], prefix[3], 0x00, 0x11, 0x00, 0x10, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
        0x71,
    ]
}

/// The JUMBF type of the store (`c2pa`).
pub const T_STORE: [u8; 16] = ty(*b"c2pa");
/// A standard manifest (`c2ma`).
pub const T_MANIFEST: [u8; 16] = ty(*b"c2ma");
/// An update manifest (`c2um`).
pub const T_UPDATE: [u8; 16] = ty(*b"c2um");
/// A JSON content superbox (`json`).
pub const T_JSON: [u8; 16] = ty(*b"json");
/// The assertion store (`c2as`).
pub const T_ASSERTIONS: [u8; 16] = ty(*b"c2as");
/// A CBOR content superbox (`cbor`).
pub const T_CBOR: [u8; 16] = ty(*b"cbor");
/// The claim (`c2cl`).
pub const T_CLAIM: [u8; 16] = ty(*b"c2cl");
/// The claim signature (`c2cs`).
pub const T_SIGNATURE: [u8; 16] = ty(*b"c2cs");
/// Requestable and label present (C2PA 2.4 §11.1.4.1.2).
pub const TOGGLES: u8 = 0x03;

/// The store's label.
pub const LABEL_STORE: &str = "c2pa";
/// The assertion store's label.
pub const LABEL_ASSERTIONS: &str = "c2pa.assertions";
/// The claim's label.
pub const LABEL_CLAIM: &str = "c2pa.claim.v2";
/// The claim signature's label.
pub const LABEL_SIGNATURE: &str = "c2pa.signature";

/// A superbox of `ty` and `label` around `contents`.
pub fn superbox(ty: &[u8; 16], label: &str, contents: &[Vec<u8>]) -> Vec<u8> {
    let mut d = Vec::with_capacity(16 + 1 + label.len() + 1);
    d.extend_from_slice(ty);
    d.push(TOGGLES);
    d.extend_from_slice(label.as_bytes());
    d.push(0);
    let mut body = write_box(b"jumd", &d);
    for c in contents {
        body.extend_from_slice(c);
    }
    write_box(b"jumb", &body)
}

/// An assertion's superbox: a CBOR content superbox of one `cbor` box.
pub fn assertion_box(label: &str, cbor: &[u8]) -> Vec<u8> {
    superbox(&T_CBOR, label, &[write_box(b"cbor", cbor)])
}

/// A JSON assertion's superbox: a JSON content superbox of one `json` box (§4.7).
pub fn json_assertion_box(label: &str, json: &[u8]) -> Vec<u8> {
    superbox(&T_JSON, label, &[write_box(b"json", json)])
}

/// The form of an assertion's content.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Form {
    Cbor,
    Json,
}

/// An assertion's superbox of `form`.
pub fn any_assertion_box(label: &str, form: Form, content: &[u8]) -> Vec<u8> {
    match form {
        Form::Cbor => assertion_box(label, content),
        Form::Json => json_assertion_box(label, content),
    }
}

/// The hash of a superbox for a hashed URI (§8.4.2.3): SHA-256 over the superbox less its
/// 8-byte header.
pub fn box_hash(superbox: &[u8]) -> [u8; 32] {
    Sha256::digest(&superbox[8..]).into()
}

/// A store of one manifest, `id`, of the assertions (label, CBOR) in order, the claim and the
/// signature.
pub fn store(id: &str, assertions: &[(String, Vec<u8>)], claim: &[u8], signature: &[u8]) -> Vec<u8> {
    let a: Vec<(String, Form, Vec<u8>)> = assertions.iter().map(|(l, c)| (l.clone(), Form::Cbor, c.clone())).collect();
    store_of(&T_MANIFEST, id, &a, claim, signature)
}

/// A store of one manifest of type `ty` (standard or update), `id`, of the assertions
/// (label, form, content) in order, the claim and the signature.
pub fn store_of(ty: &[u8; 16], id: &str, assertions: &[(String, Form, Vec<u8>)], claim: &[u8], signature: &[u8]) -> Vec<u8> {
    let a: Vec<Vec<u8>> = assertions.iter().map(|(l, f, c)| any_assertion_box(l, *f, c)).collect();
    let manifest = superbox(
        ty,
        id,
        &[
            superbox(&T_ASSERTIONS, LABEL_ASSERTIONS, &a),
            superbox(&T_CLAIM, LABEL_CLAIM, &[write_box(b"cbor", claim)]),
            superbox(&T_SIGNATURE, LABEL_SIGNATURE, &[write_box(b"cbor", signature)]),
        ],
    );
    superbox(&T_STORE, LABEL_STORE, &[manifest])
}

/// One assertion as the store holds it.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Assertion {
    pub label: String,
    pub form: Form,
    /// Its content: CBOR or JSON, as `form` says.
    pub cbor: Vec<u8>,
    /// The hash a hashed URI to it holds.
    pub hash: [u8; 32],
}

/// The one manifest of a store, read.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Store {
    /// Its JUMBF type: standard (`c2ma`) or update (`c2um`).
    pub manifest_type: [u8; 16],
    pub manifest_id: String,
    /// The hashes a hashed URI to the manifest superbox and to its claim signature hold
    /// (§8.4.2.3).
    pub manifest_hash: [u8; 32],
    pub signature_hash: [u8; 32],
    pub assertions: Vec<Assertion>,
    pub claim: Vec<u8>,
    pub signature: Vec<u8>,
}

/// A superbox read: its description and its content boxes.
struct Read {
    ty: [u8; 16],
    label: String,
    content: Vec<BoxRef>,
}

fn read_super(b: &[u8], r: &BoxRef) -> Option<Read> {
    if &r.ty != b"jumb" {
        return None;
    }
    let inner = boxes_in(b, r.start + 8, r.end)?;
    let (jumd, content) = inner.split_first()?;
    if &jumd.ty != b"jumd" {
        return None;
    }
    let d = jumd.body(b);
    if d.len() < 18 || d[16] != TOGGLES || *d.last()? != 0 {
        return None;
    }
    let label = &d[17..d.len() - 1];
    if label.is_empty() || label.contains(&0) {
        return None;
    }
    Some(Read {
        ty: d[..16].try_into().expect("sixteen bytes"),
        label: String::from_utf8(label.to_vec()).ok()?,
        content: content.to_vec(),
    })
}

/// The single `cbor` box a superbox holds.
fn one_cbor<'a>(b: &'a [u8], s: &Read) -> Option<&'a [u8]> {
    one_of(b, s, b"cbor")
}

/// The single content box of type `ty` a superbox holds.
fn one_of<'a>(b: &'a [u8], s: &Read, ty: &[u8; 4]) -> Option<&'a [u8]> {
    match s.content.as_slice() {
        [c] if &c.ty == ty => Some(c.body(b)),
        _ => None,
    }
}

fn expect(b: &[u8], r: &BoxRef, ty: &[u8; 16], label: &str) -> Option<Read> {
    let s = read_super(b, r)?;
    (s.ty == *ty && s.label == label).then_some(s)
}

/// Read a store as §4.1 lays it out: exactly one superbox spanning the bytes, of one manifest,
/// whose content is the assertion store, the claim and the signature, in that order.
pub fn read_store(b: &[u8]) -> Option<Store> {
    let top = boxes_in(b, 0, b.len())?;
    let [store_box] = top.as_slice() else { return None };
    let store = expect(b, store_box, &T_STORE, LABEL_STORE)?;
    let [manifest_box] = store.content.as_slice() else { return None };
    let manifest = read_super(b, manifest_box)?;
    if manifest.ty != T_MANIFEST && manifest.ty != T_UPDATE {
        return None;
    }
    let [a, c, s] = manifest.content.as_slice() else { return None };
    let sig_box = *s;
    let a = expect(b, a, &T_ASSERTIONS, LABEL_ASSERTIONS)?;
    let claim = one_cbor(b, &expect(b, c, &T_CLAIM, LABEL_CLAIM)?)?.to_vec();
    let signature = one_cbor(b, &expect(b, s, &T_SIGNATURE, LABEL_SIGNATURE)?)?.to_vec();
    let mut assertions = Vec::new();
    for r in &a.content {
        let s = read_super(b, r)?;
        let (form, content) = if s.ty == T_CBOR {
            (Form::Cbor, one_cbor(b, &s)?)
        } else if s.ty == T_JSON {
            (Form::Json, one_of(b, &s, b"json")?)
        } else {
            return None;
        };
        assertions.push(Assertion { label: s.label, form, cbor: content.to_vec(), hash: box_hash(&b[r.start..r.end]) });
    }
    Some(Store {
        manifest_type: manifest.ty,
        manifest_id: manifest.label,
        manifest_hash: box_hash(&b[manifest_box.start..manifest_box.end]),
        signature_hash: box_hash(&b[sig_box.start..sig_box.end]),
        assertions,
        claim,
        signature,
    })
}

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

    #[test]
    fn a_store_reads_back() {
        let s = store("urn:c2pa:x", &[("a.b".into(), vec![0xa0]), ("c.d".into(), vec![0x01])], &[0xa0], &[0xf6]);
        let r = read_store(&s).unwrap();
        assert_eq!(r.manifest_id, "urn:c2pa:x");
        assert_eq!(r.assertions.len(), 2);
        assert_eq!(r.assertions[1].label, "c.d");
        assert_eq!(r.assertions[0].hash, box_hash(&assertion_box("a.b", &[0xa0])));
        assert_eq!(r.claim, vec![0xa0]);
        // Anything after the store, or a store of the wrong type, does not read.
        assert!(read_store(&[s.clone(), vec![0, 0, 0, 8, b'f', b'r', b'e', b'e']].concat()).is_none());
        let mut wrong = s.clone();
        let at = wrong.windows(5).position(|w| w == b"c2pa\0").unwrap();
        wrong[at] = b'C';
        assert!(read_store(&wrong).is_none());
    }
}
