//! The integer super-resolution derivations of `extensions/exact-upscale.md` (§3):
//! `wai-upscale:sr2` and `wai-upscale:sr4`, each a pinned integer network over the integer
//! operator set (`wai-ops/1`), parameterised by a prior in the form `WSR1` that a sink holds
//! by its SHA-256. Given a picture's decoded samples and the prior, every sink computes the
//! same samples at twice (or four times) the resolution: the derivation is the base
//! upscale `up2_predict` (integer-operators §5E E11) plus a detail the network computes from
//! the picture, clamped to the sample range, and nothing else.
//!
//! - [`parse`] reads a `WSR1` prior and refuses one that breaks §3.1, with the reason in
//!   the order §3.1 checks it;
//! - [`graph`] is the lowering `sr2/1` (§3.4): the prior to one `WHG1` graph, with its two
//!   inputs (the picture and its base upscale);
//! - [`load`] loads that graph as a sink does, under `acc64` with its bounds certificate
//!   ([`crate::int_ops::load`]); [`meets_acc32`] says whether the certificate also meets
//!   `acc32`, which a 32-bit fabric runs (integer-operators §7.2);
//! - [`sr2`] and [`derive`] run it: through the integer operator set's interpreter, or on a
//!   GPU inside [`crate::int_ops::gpu::with_gpu`] (feature `ops_gpu`), with the same bytes.
//!
//! [`direct`] is the same arithmetic written out loop by loop from §3.2, with no graph:
//! the tests hold the graph's output to it.

use wai_ops::graph::{opcode, BiasKind, Graph, InputRec, Loaded, Op, Params, Round, Rq, TensorRec, CAP};
use wai_ops::{Profile, Tensor};

/// The derivation at twice the resolution (§3.2).
pub const SR2: &str = "wai-upscale:sr2";
/// The derivation at four times the resolution: `sr2` of `sr2` under one prior (§3.3).
pub const SR4: &str = "wai-upscale:sr4";
/// The lowering of a `WSR1` prior to `WHG1` (§3.4).
pub const LOWERING: &str = "sr2/1";
/// The magic of a prior.
pub const MAGIC: [u8; 4] = *b"WSR1";
/// The most layers a prior holds.
pub const MAX_LAYERS: usize = 8;
/// The most output channels a hidden layer has.
pub const MAX_CHANNELS: usize = 64;
/// The channels of the last layer: three colours, four sub-positions.
pub const DETAIL_CHANNELS: usize = 12;

/// One layer of a prior (§3.1).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Layer {
    /// The kernel side: 1, 3 or 5.
    pub k: u8,
    /// The requantisation shift, 0 to 31.
    pub shift: u8,
    pub cin: usize,
    pub cout: usize,
    /// `[cout][cin][k][k]`.
    pub w: Vec<i8>,
    pub bias: Vec<i32>,
    /// Each at least 1.
    pub mult: Vec<u16>,
}

/// A parsed `WSR1` prior (§3.1).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Prior {
    /// The ceiling of every hidden activation, 1 to 32767.
    pub act_hi: u16,
    pub layers: Vec<Layer>,
}

/// Why a derivation could not run.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SrError {
    /// The prior breaks §3.1: `bad_magic`, `version`, `field_range`, `truncated` or
    /// `trailing`, in §3.1's order.
    Prior(&'static str),
    /// The lowered graph did not load (integer-operators §10.5; never for a prior that
    /// reads, whose every value fits `acc64`), or the run refused (`too_large` for a picture
    /// whose tensors pass the caps of integer-operators §3).
    Refused(String),
    /// The picture is empty or its buffer is not `w·h·3` bytes.
    Geometry,
}

impl std::fmt::Display for SrError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SrError::Prior(c) => write!(f, "the prior does not read: {c}"),
            SrError::Refused(c) => write!(f, "the derivation is refused: {c}"),
            SrError::Geometry => f.write_str("the picture is empty or its buffer is not w·h·3 bytes"),
        }
    }
}

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

/// Read a `WSR1` prior (§3.1): the magic (`bad_magic`), the version (`version`), the
/// header's fields (`field_range`), each layer's fields and then its arrays, present
/// (`truncated`) and in range (`field_range`), and nothing after the last layer
/// (`trailing`).
pub fn parse(b: &[u8]) -> Result<Prior, SrError> {
    let need = |at: usize, n: usize| if b.len() < at + n { Err(SrError::Prior("truncated")) } else { Ok(()) };
    need(0, 4)?;
    if b[..4] != MAGIC {
        return Err(SrError::Prior("bad_magic"));
    }
    need(4, 1)?;
    if b[4] != 1 {
        return Err(SrError::Prior("version"));
    }
    need(5, 3)?;
    let n = b[5] as usize;
    let act_hi = u16::from_le_bytes([b[6], b[7]]);
    if !(1..=MAX_LAYERS).contains(&n) || !(1..=32767).contains(&act_hi) {
        return Err(SrError::Prior("field_range"));
    }
    let (mut at, mut cin, mut layers) = (8usize, 3usize, Vec::with_capacity(n));
    for i in 0..n {
        need(at, 4)?;
        let (k, shift, cout) = (b[at], b[at + 1], u16::from_le_bytes([b[at + 2], b[at + 3]]) as usize);
        let last = i + 1 == n;
        let cout_ok = if last { cout == DETAIL_CHANNELS } else { (1..=MAX_CHANNELS).contains(&cout) };
        if !matches!(k, 1 | 3 | 5) || shift > 31 || !cout_ok {
            return Err(SrError::Prior("field_range"));
        }
        at += 4;
        let nw = cout * cin * k as usize * k as usize;
        need(at, nw + 6 * cout)?;
        let w: Vec<i8> = b[at..at + nw].iter().map(|&v| v as i8).collect();
        at += nw;
        let bias: Vec<i32> = b[at..at + 4 * cout].as_chunks::<4>().0.iter().map(|c| i32::from_le_bytes(*c)).collect();
        at += 4 * cout;
        let mult: Vec<u16> = b[at..at + 2 * cout].as_chunks::<2>().0.iter().map(|c| u16::from_le_bytes(*c)).collect();
        at += 2 * cout;
        if mult.contains(&0) {
            return Err(SrError::Prior("field_range"));
        }
        layers.push(Layer { k, shift, cin, cout, w, bias, mult });
        cin = cout;
    }
    if at != b.len() {
        return Err(SrError::Prior("trailing"));
    }
    Ok(Prior { act_hi, layers })
}

/// The bytes of a prior: the inverse of [`parse`].
pub fn write(p: &Prior) -> Vec<u8> {
    let mut o = MAGIC.to_vec();
    o.push(1);
    o.push(p.layers.len() as u8);
    o.extend_from_slice(&p.act_hi.to_le_bytes());
    for l in &p.layers {
        o.extend_from_slice(&[l.k, l.shift]);
        o.extend_from_slice(&(l.cout as u16).to_le_bytes());
        o.extend(l.w.iter().map(|&v| v as u8));
        for v in &l.bias {
            o.extend_from_slice(&v.to_le_bytes());
        }
        for v in &l.mult {
            o.extend_from_slice(&v.to_le_bytes());
        }
    }
    o
}

/// The lowering `sr2/1` (§3.4): one graph with two data inputs, tensor 0 the picture
/// `[3, h, w]` and tensor 1 its base upscale `[3, 2h, 2w]`, each `check [0, 255]`, sizes set
/// per decode; then, each operator's output taking the next tensor index: `rescale` by the
/// pre-bias −128; for each layer a `conv` (stride 1, zero padding ⌊k/2⌋, one group, its
/// weights at one byte, an `rq` of one multiplier per channel at four bytes, one shift, a
/// `pre` bias at four bytes, `half_up`), and after each layer but the last a `clamp(0,
/// act_hi)`; `pixel_shuffle(2)`; `add` of the base and the detail; `clamp(0, 255)`, the one
/// output.
pub fn graph(p: &Prior) -> Graph {
    let mut tensors = vec![TensorRec::check(3, 0, 255), TensorRec::check(3, 0, 255)];
    let mut ops = Vec::new();
    let mut op = |tensors: &mut Vec<TensorRec>, code: u8, ins: Vec<u32>, out: TensorRec, p: Params| -> u32 {
        let t = tensors.len() as u32;
        tensors.push(out);
        ops.push(Op { code, ins, outs: vec![t], p });
        t
    };
    let centre = Rq { round: Round::Floor, bias_kind: BiasKind::Pre, m_kind: 0, s_kind: 1, m_bytes: 0, b_bytes: 4, mult: vec![], shift: vec![0], bias: vec![-128; 3] };
    let mut x = op(&mut tensors, opcode::RESCALE, vec![0], TensorRec::derived(3), Params::Rescale(centre));
    for (i, l) in p.layers.iter().enumerate() {
        let rq = Rq {
            round: Round::HalfUp,
            bias_kind: BiasKind::Pre,
            m_kind: 2,
            s_kind: 1,
            m_bytes: 4,
            b_bytes: 4,
            mult: l.mult.iter().map(|&m| m as i64).collect(),
            shift: vec![l.shift],
            bias: l.bias.iter().map(|&b| b as i64).collect(),
        };
        let k = l.k as u32;
        let conv = Params::Conv { cout: l.cout as u32, kh: k, kw: k, sh: 1, sw: 1, ph: k / 2, pw: k / 2, groups: 1, w_bytes: 1, rq, w: l.w.iter().map(|&v| v as i64).collect() };
        x = op(&mut tensors, opcode::CONV, vec![x], TensorRec::derived(l.cout as u32), conv);
        if i + 1 < p.layers.len() {
            x = op(&mut tensors, opcode::CLAMP, vec![x], TensorRec::derived(l.cout as u32), Params::Clamp { lo: 0, hi: p.act_hi as i64 });
        }
    }
    let d = op(&mut tensors, opcode::PIXEL_SHUFFLE, vec![x], TensorRec::derived(3), Params::Shuffle { r: 2 });
    let sum = op(&mut tensors, opcode::ADD, vec![1, d], TensorRec::derived(3), Params::None);
    let out = op(&mut tensors, opcode::CLAMP, vec![sum], TensorRec::derived(3), Params::Clamp { lo: 0, hi: 255 });
    let input = |tensor| InputRec { tensor, max_h: 0, max_w: 0, kind: 0, src: 0, init: 0 };
    Graph { max_elems: CAP as u32, scalars: vec![], inputs: vec![input(0), input(1)], outputs: vec![out], tensors, ops }
}

/// A prior's graph, loaded as a sink loads it: parsed (§3.1), lowered (§3.4), and loaded
/// from its canonical bytes under `acc64`, its bounds certificate included.
pub fn load(prior: &[u8]) -> Result<Loaded, SrError> {
    let p = parse(prior)?;
    crate::int_ops::load(&graph(&p)).map_err(|r| SrError::Refused(r.code.as_str().into()))
}

/// Whether a prior's graph also meets `acc32` (integer-operators §7.2), the profile a
/// 32-bit fabric runs: its certificate, computed from the graph's bytes alone.
pub fn meets_acc32(prior: &[u8]) -> bool {
    parse(prior).is_ok_and(|p| wai_ops::graph::load(&graph(&p).to_bytes(), Profile::Acc32).is_ok())
}

fn planar(rgb: &[u8], w: usize, h: usize) -> Tensor {
    let n = w * h;
    let mut d = vec![0i64; 3 * n];
    for (i, px) in rgb.as_chunks::<3>().0.iter().enumerate() {
        for c in 0..3 {
            d[c * n + i] = px[c] as i64;
        }
    }
    Tensor::new(3, h as u32, w as u32, d)
}

fn interleaved(t: &Tensor) -> Vec<u8> {
    let n = t.h as usize * t.w as usize;
    let mut o = vec![0u8; 3 * n];
    for (k, &v) in t.data.iter().enumerate() {
        o[(k % n) * 3 + k / n] = v as u8;
    }
    o
}

/// `up2_predict` (E11) of an RGB8 picture: the base of §3.2 step 1, which is
/// `wai-synthesis:upscale2` (synthesis §4.1).
pub fn up2(rgb: &[u8], w: usize, h: usize) -> Result<Vec<u8>, SrError> {
    if w == 0 || h == 0 || rgb.len() != w * h * 3 {
        return Err(SrError::Geometry);
    }
    let base = wai_ops::stage::up2_predict(&planar(rgb, w, h), 2 * h as u32, 2 * w as u32).ok_or(SrError::Geometry)?;
    Ok(interleaved(&base))
}

/// `wai-upscale:sr2` (§3.2) of a `w × h` RGB8 picture under a loaded prior: `2w × 2h` RGB8.
pub fn sr2(g: &Loaded, rgb: &[u8], w: usize, h: usize) -> Result<Vec<u8>, SrError> {
    let base = up2(rgb, w, h)?;
    let out = crate::int_ops::run_graph(g, vec![planar(rgb, w, h), planar(&base, 2 * w, 2 * h)], &[])
        .map_err(|r| SrError::Refused(r.code.as_str().into()))?;
    Ok(interleaved(&out[0]))
}

/// The factor a derivation scales each side by: 2 for [`SR2`], 4 for [`SR4`].
pub fn factor(derivation: &str) -> Option<usize> {
    match derivation {
        SR2 => Some(2),
        SR4 => Some(4),
        _ => None,
    }
}

/// Run `derivation` ([`SR2`] or [`SR4`]) over a `w × h` RGB8 picture: its output, of
/// `factor · w × factor · h`.
pub fn derive(derivation: &str, g: &Loaded, rgb: &[u8], w: usize, h: usize) -> Result<Vec<u8>, SrError> {
    match derivation {
        SR2 => sr2(g, rgb, w, h),
        SR4 => {
            let once = sr2(g, rgb, w, h)?;
            sr2(g, &once, 2 * w, 2 * h)
        }
        _ => Err(SrError::Refused("derivation_unknown".into())),
    }
}

/// A prior of the hidden layers `hidden` (each its kernel and output channels) and a last
/// layer of kernel `last_k`, every weight, bias and multiplier drawn from SHA-256 of `seed`
/// and a counter: for conformance corpora, where a prior exercises the arithmetic and needs
/// no training. Weights lie in −8..=8, biases in −512..=511, multipliers in 16..=47; the
/// hidden layers shift by 10 and the last by 14, so that the detail is a few tens of sample
/// values and both clamps decide some samples.
pub fn seeded_prior(seed: &[u8], hidden: &[(u8, usize)], last_k: u8, act_hi: u16) -> Vec<u8> {
    let mut stream = (0u32..).flat_map(|i| crate::pin::sha256(&[seed, &i.to_le_bytes()].concat()));
    let mut next = || stream.next().expect("an endless stream");
    let mut layers = Vec::new();
    let mut cin = 3usize;
    let shapes: Vec<(u8, usize)> = hidden.iter().copied().chain([(last_k, DETAIL_CHANNELS)]).collect();
    for (i, &(k, cout)) in shapes.iter().enumerate() {
        let nw = cout * cin * k as usize * k as usize;
        let w = (0..nw).map(|_| (next() % 17) as i8 - 8).collect();
        let bias = (0..cout).map(|_| (u16::from_le_bytes([next(), next()]) % 1024) as i32 - 512).collect();
        let mult = (0..cout).map(|_| 16 + (next() % 32) as u16).collect();
        let shift = if i + 1 < shapes.len() { 10 } else { 14 };
        layers.push(Layer { k, shift, cin, cout, w, bias, mult });
        cin = cout;
    }
    write(&Prior { act_hi, layers })
}

/// §3.2 written out loop by loop over the parsed prior, with no graph and no operator
/// set: the arithmetic the extension states, which the tests hold the graph to.
pub fn direct(p: &Prior, rgb: &[u8], w: usize, h: usize) -> Result<Vec<u8>, SrError> {
    let base = up2(rgb, w, h)?;
    let n = w * h;
    let mut x: Vec<i64> = (0..3 * n).map(|k| rgb[(k % n) * 3 + k / n] as i64 - 128).collect();
    for (li, l) in p.layers.iter().enumerate() {
        let (k, r) = (l.k as usize, l.k as usize / 2);
        let mut y = vec![0i64; l.cout * n];
        for o in 0..l.cout {
            for yy in 0..h {
                for xx in 0..w {
                    let mut a = 0i64;
                    for ci in 0..l.cin {
                        for ky in 0..k {
                            for kx in 0..k {
                                let (sy, sx) = (yy as i64 + ky as i64 - r as i64, xx as i64 + kx as i64 - r as i64);
                                if sy < 0 || sx < 0 || sy >= h as i64 || sx >= w as i64 {
                                    continue;
                                }
                                a += l.w[((o * l.cin + ci) * k + ky) * k + kx] as i64 * x[ci * n + sy as usize * w + sx as usize];
                            }
                        }
                    }
                    let prod = (a + l.bias[o] as i64) * l.mult[o] as i64;
                    let mut v = if l.shift == 0 { prod } else { (prod + (1i64 << (l.shift - 1))) >> l.shift };
                    if li + 1 < p.layers.len() {
                        v = v.clamp(0, p.act_hi as i64);
                    }
                    y[o * n + yy * w + xx] = v;
                }
            }
        }
        x = y;
    }
    let (ow, oh) = (2 * w, 2 * h);
    let mut out = vec![0u8; ow * oh * 3];
    for c in 0..3 {
        for oy in 0..oh {
            for ox in 0..ow {
                let d = x[(c * 4 + (oy % 2) * 2 + ox % 2) * n + (oy / 2) * w + ox / 2];
                let i = (oy * ow + ox) * 3 + c;
                out[i] = (base[i] as i64 + d).clamp(0, 255) as u8;
            }
        }
    }
    Ok(out)
}

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

    /// SplitMix64, for random priors and pictures.
    struct Rng(u64);
    impl Rng {
        fn next(&mut self) -> u64 {
            self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
            let mut z = self.0;
            z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
            z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
            z ^ (z >> 31)
        }
        fn below(&mut self, n: u64) -> u64 {
            self.next() % n
        }
    }

    fn random_prior(r: &mut Rng, hidden: &[(u8, usize)], last_k: u8) -> Prior {
        let mut layers = Vec::new();
        let mut cin = 3;
        let shapes: Vec<(u8, usize)> = hidden.iter().copied().chain([(last_k, DETAIL_CHANNELS)]).collect();
        for (k, cout) in shapes {
            let nw = cout * cin * k as usize * k as usize;
            layers.push(Layer {
                k,
                shift: (8 + r.below(10)) as u8,
                cin,
                cout,
                w: (0..nw).map(|_| r.below(256) as u8 as i8).collect(),
                bias: (0..cout).map(|_| r.below(1 << 16) as i32 - (1 << 15)).collect(),
                mult: (0..cout).map(|_| 1 + r.below(4096) as u16).collect(),
            });
            cin = cout;
        }
        Prior { act_hi: (1 + r.below(4000)) as u16, layers }
    }

    fn picture(r: &mut Rng, w: usize, h: usize) -> Vec<u8> {
        (0..w * h * 3).map(|_| r.below(256) as u8).collect()
    }

    /// The closed form of synthesis §4.1 (`wai-synthesis:upscale2`).
    fn upscale2(input: &[u8], w: usize, h: usize) -> Vec<u8> {
        let at = |x: usize, y: usize, c: usize| input[(y * w + x) * 3 + c] as u32;
        let mut out = vec![0u8; 4 * w * h * 3];
        for y in 0..h {
            for x in 0..w {
                let (x1, y1) = ((x + 1).min(w - 1), (y + 1).min(h - 1));
                for c in 0..3 {
                    let (a, b, cc, d) = (at(x, y, c), at(x1, y, c), at(x, y1, c), at(x1, y1, c));
                    out[((2 * y) * 2 * w + 2 * x) * 3 + c] = a as u8;
                    out[((2 * y) * 2 * w + 2 * x + 1) * 3 + c] = ((a + b + 1) >> 1) as u8;
                    out[((2 * y + 1) * 2 * w + 2 * x) * 3 + c] = ((a + cc + 1) >> 1) as u8;
                    out[((2 * y + 1) * 2 * w + 2 * x + 1) * 3 + c] = ((a + b + cc + d + 2) >> 2) as u8;
                }
            }
        }
        out
    }

    #[test]
    fn the_graph_computes_the_arithmetic_of_section_3_2() {
        let mut r = Rng(7);
        let shapes: [(&[(u8, usize)], u8); 5] = [(&[], 3), (&[(3, 4)], 3), (&[(3, 5), (1, 3)], 5), (&[(5, 2), (3, 6), (3, 4)], 1), (&[(1, 1)], 3)];
        for (hidden, last_k) in shapes {
            for (w, h) in [(1, 1), (1, 3), (5, 2), (7, 6), (4, 9)] {
                let p = random_prior(&mut r, hidden, last_k);
                let bytes = write(&p);
                assert_eq!(parse(&bytes).as_ref(), Ok(&p), "a prior reads back as written");
                let g = load(&bytes).expect("every prior that reads loads under acc64");
                let pic = picture(&mut r, w, h);
                assert_eq!(sr2(&g, &pic, w, h).unwrap(), direct(&p, &pic, w, h).unwrap(), "{hidden:?} {w}x{h}");
            }
        }
    }

    #[test]
    fn a_prior_with_no_detail_is_the_pinned_upscale() {
        let mut r = Rng(11);
        let mut p = random_prior(&mut r, &[(3, 4)], 3);
        let last = p.layers.last_mut().unwrap();
        last.w.iter_mut().for_each(|v| *v = 0);
        last.bias.iter_mut().for_each(|v| *v = 0);
        let g = load(&write(&p)).unwrap();
        for (w, h) in [(1, 1), (3, 2), (6, 5)] {
            let pic = picture(&mut r, w, h);
            assert_eq!(sr2(&g, &pic, w, h).unwrap(), upscale2(&pic, w, h));
            assert_eq!(up2(&pic, w, h).unwrap(), upscale2(&pic, w, h));
        }
    }

    #[test]
    fn sr4_is_sr2_twice() {
        let mut r = Rng(13);
        let p = random_prior(&mut r, &[(3, 3)], 3);
        let g = load(&write(&p)).unwrap();
        let pic = picture(&mut r, 3, 2);
        let once = direct(&p, &pic, 3, 2).unwrap();
        assert_eq!(derive(SR4, &g, &pic, 3, 2).unwrap(), direct(&p, &once, 6, 4).unwrap());
        assert_eq!(factor(SR4), Some(4));
        assert_eq!(factor("wai-synthesis:upscale2"), None);
    }

    #[test]
    fn a_prior_that_breaks_section_3_1_is_refused_in_its_order() {
        let mut r = Rng(17);
        let good = write(&random_prior(&mut r, &[(3, 2)], 3));
        let refused = |b: &[u8]| parse(b).err();
        let with = |at: usize, v: u8| {
            let mut b = good.clone();
            b[at] = v;
            b
        };
        assert_eq!(refused(&good[..3]), Some(SrError::Prior("truncated")));
        assert_eq!(refused(&with(0, b'X')), Some(SrError::Prior("bad_magic")));
        assert_eq!(refused(&with(4, 2)), Some(SrError::Prior("version")));
        assert_eq!(refused(&with(5, 0)), Some(SrError::Prior("field_range")));
        assert_eq!(refused(&with(5, 9)), Some(SrError::Prior("field_range")));
        assert_eq!(refused(&[&good[..6], &[0, 0]].concat()), Some(SrError::Prior("field_range")), "act_hi 0");
        assert_eq!(refused(&[&good[..6], &[0, 0x80]].concat()), Some(SrError::Prior("field_range")), "act_hi past 32767");
        assert_eq!(refused(&with(8, 2)), Some(SrError::Prior("field_range")), "an even kernel");
        assert_eq!(refused(&with(9, 32)), Some(SrError::Prior("field_range")), "a shift past 31");
        assert_eq!(refused(&with(10, 0)), Some(SrError::Prior("field_range")), "no output channel");
        assert_eq!(refused(&good[..good.len() - 1]), Some(SrError::Prior("truncated")));
        assert_eq!(refused(&[&good[..], &[0]].concat()), Some(SrError::Prior("trailing")));
        let mut zero_mult = good.clone();
        let n = zero_mult.len();
        zero_mult[n - 2] = 0;
        zero_mult[n - 1] = 0;
        assert_eq!(refused(&zero_mult), Some(SrError::Prior("field_range")), "a multiplier of 0");
        // The last layer has the twelve detail channels and no other count.
        let mut p = parse(&good).unwrap();
        p.layers.last_mut().unwrap().cout = 11;
        p.layers.last_mut().unwrap().w.truncate(11 * 2 * 9);
        p.layers.last_mut().unwrap().bias.truncate(11);
        p.layers.last_mut().unwrap().mult.truncate(11);
        assert_eq!(refused(&write(&p)), Some(SrError::Prior("field_range")));
    }

    #[test]
    fn a_picture_whose_tensors_pass_the_caps_is_refused() {
        let mut r = Rng(19);
        let p = random_prior(&mut r, &[(1, 64)], 1);
        let g = load(&write(&p)).unwrap();
        // 64 channels of 1024 x 1025 pass 2^26 elements.
        let (w, h) = (1024, 1025);
        let pic = vec![0u8; w * h * 3];
        assert_eq!(sr2(&g, &pic, w, h), Err(SrError::Refused("too_large".into())));
        assert_eq!(sr2(&g, &pic[..3], 2, 1), Err(SrError::Geometry));
    }
}
