//! Edge cases beyond the golden fixtures: the error path, input immutability,
//! determinism, the verbatim tail, and the output length formula.

use std::path::Path;

use ttf2eot::{ttf2eot, Error};

/// Load a fixture by name from `tests/fixtures`.
fn load(name: &str) -> Vec<u8> {
    let p = Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests/fixtures")
        .join(name);
    std::fs::read(&p).unwrap_or_else(|e| panic!("read {name}: {e}"))
}

/// Tag offset of directory entry `i` in the SFNT table directory.
fn tag_offset(i: usize) -> usize {
    12 + i * 16
}

/// Corrupt a tag so the required table is no longer found, then expect the
/// "required table not found" error.
#[test]
fn missing_os2_errors() {
    let mut ttf = load("test.ttf");
    // Entry 1 in test.ttf is the OS/2 table. Break its tag.
    let off = tag_offset(1);
    ttf[off..off + 4].copy_from_slice(b"XXXX");
    assert_eq!(ttf2eot(&ttf), Err(Error::RequiredTableNotFound));
}

#[test]
fn missing_head_errors() {
    let mut ttf = load("test.ttf");
    // Entry 7 is the head table.
    let off = tag_offset(7);
    ttf[off..off + 4].copy_from_slice(b"XXXX");
    assert_eq!(ttf2eot(&ttf), Err(Error::RequiredTableNotFound));
}

#[test]
fn missing_name_errors() {
    let mut ttf = load("test.ttf");
    // Entry 12 is the name table.
    let off = tag_offset(12);
    ttf[off..off + 4].copy_from_slice(b"XXXX");
    assert_eq!(ttf2eot(&ttf), Err(Error::RequiredTableNotFound));
}

/// An empty buffer cannot supply `numTables` and returns an error, not a panic.
#[test]
fn empty_input_errors() {
    assert_eq!(ttf2eot(&[]), Err(Error::UnexpectedEof));
}

/// A few header bytes are not enough for the directory walk.
#[test]
fn truncated_input_errors() {
    let ttf = load("test.ttf");
    // Keep the header and first directory entry tag, drop the rest.
    let short = &ttf[..20];
    assert!(ttf2eot(short).is_err());
}

/// The function reads the input only. The buffer is unchanged after the call.
#[test]
fn input_not_mutated() {
    let ttf = load("test.ttf");
    let snapshot = ttf.clone();
    let _ = ttf2eot(&ttf).unwrap();
    assert_eq!(ttf, snapshot);
}

/// Same input yields the same output every time.
#[test]
fn deterministic() {
    let ttf = load("test_v0.ttf");
    let a = ttf2eot(&ttf).unwrap();
    let b = ttf2eot(&ttf).unwrap();
    assert_eq!(a, b);
}

/// The input font is appended at the tail unmodified.
#[test]
fn tail_is_verbatim() {
    for name in ["test.ttf", "test_v0.ttf"] {
        let ttf = load(name);
        let out = ttf2eot(&ttf).unwrap();
        assert_eq!(&out[out.len() - ttf.len()..], &ttf[..], "{name}");
    }
}

/// The LENGTH field at offset 0 holds the whole output length, and the output
/// length matches the prefix plus the four strings plus the pad plus the font.
#[test]
fn length_field_matches_output() {
    for name in ["test.ttf", "test_v0.ttf"] {
        let ttf = load(name);
        let out = ttf2eot(&ttf).unwrap();
        let length_field = u32::from_le_bytes(out[0..4].try_into().unwrap());
        assert_eq!(length_field as usize, out.len(), "{name} LENGTH");

        // 82 prefix + family + subfamily + version + full + 2 pad + font.
        // Each name string region is u16 len prefix + that many bytes + 2.
        let strings_total = out.len() - 82 - 2 - ttf.len();
        assert!(strings_total >= 4, "{name}: at least four 1-byte defaults");
    }
}
