Documentation
use raff::prelude::*;
use std::io::{Cursor, Read};

#[test]
fn test_raff_header() {
    #[rustfmt::skip]
    let data = &[
        0xF0, 0x9F, 0xA6, 0x8A, // Icon
        0x52, 0x41, 0x46, 0x46, // RAFF
        0x30, 0x2E, 0x32, 0x0A, // Version 0.2
        0x1A,                   // SUB character
        // Application header (18 bytes)
        b'T', b'e', b's', b't', b'.', b'A', b'p', b'p', // "Test.App"
        0, 0, 0, 0, 0, 0, 0, 0,                          // padding
        0x00, 0x01                                       // version 0.1
    ];

    let mut stream = Cursor::new(&data[..]);

    let (header, app_header) = read_raff_header(&mut stream).unwrap();
    assert_eq!(header.major, 0);
    assert_eq!(header.minor, 2);
    assert_eq!(app_header.as_string(), "Test.App");
}

#[test]
fn test_write_raff_header() {
    let mut stream = Cursor::new(Vec::new());
    let app_header = RaffApplicationHeader::with_str("Test.App", 0, 2).unwrap();

    write_raff_header(&mut stream, &app_header).expect("should write");

    #[rustfmt::skip]
    let expected_start = &[
        0xF0, 0x9F, 0xA6, 0x8A, // Icon
        0x52, 0x41, 0x46, 0x46, // RAFF
        0x30, 0x2E, 0x32, 0x0A, // Version 0.2
        0x1A,                   // SUB character
    ];

    assert_eq!(&stream.get_ref()[..13], expected_start);

    let empty = &[0xff; 0x53];
    write_chunk(&mut stream, "xb".try_into().unwrap(), empty).expect("Failed to write empty");

    let buffer = stream.into_inner();
    let mut stream = Cursor::new(buffer);
    let (_raff_header, _app_header) =
        read_raff_header(&mut stream).expect("Failed to read headers");
    let header = read_chunk_header(&mut stream).expect("Failed to read chunk header");
    assert_eq!(header.tag.name, "xb".try_into().unwrap());
    assert_eq!(header.size, 0x53);
    let mut data = vec![0u8; header.size as usize];
    stream.read_exact(&mut data).unwrap();
    assert_eq!(data, empty);
    assert_eq!(stream.position() as usize, stream.get_ref().len());
}

#[test]
fn is_valid_char() {
    assert!(is_valid_tag_char(b'a'));
    assert!(is_valid_tag_char(b'z'));
    assert!(is_valid_tag_char(b'A'));
    assert!(is_valid_tag_char(b'Z'));
    assert!(is_valid_tag_char(b'0'));
    assert!(is_valid_tag_char(b'9'));
    assert!(is_valid_tag_char(b'_'));
}

#[test]
fn test_application_header_creation() {
    let header = RaffApplicationHeader::with_str("MyGame.SaveFile", 1, 0).unwrap();
    assert_eq!(header.as_string(), "MyGame.SaveFile");
    assert_eq!(header.app_major, 1);
    assert_eq!(header.app_minor, 0);
}

#[test]
fn test_application_header_short_names() {
    let header = RaffApplicationHeader::with_str("Game", 2, 5).unwrap();
    assert_eq!(header.as_string(), "Game");
}

#[test]
fn test_application_header_full_length() {
    let header = RaffApplicationHeader::with_str("1234567890123456", 9, 9).unwrap();
    assert_eq!(header.as_string(), "1234567890123456");
}

#[test]
fn test_application_header_too_long() {
    let result = RaffApplicationHeader::with_str("ThisIsTooLongForSixteenBytes", 1, 0);
    assert!(result.is_err());
}

#[test]
fn test_application_header_utf8() {
    // Test with emoji (4 bytes each in UTF-8)
    let header = RaffApplicationHeader::with_str("Game🎮.Save", 1, 0).unwrap();
    assert_eq!(header.as_string(), "Game🎮.Save");

    // Test round-trip with UTF-8
    let mut stream = Cursor::new(Vec::new());
    header.serialize(&mut stream).unwrap();
    stream.set_position(0);
    let deserialized = RaffApplicationHeader::deserialize(&mut stream).unwrap();
    assert_eq!(deserialized.as_string(), "Game🎮.Save");
}

#[test]
fn test_application_header_utf8_byte_limit() {
    // "🎮" is 4 bytes, so "🎮🎮🎮🎮" is 16 bytes - should work
    let header = RaffApplicationHeader::with_str("🎮🎮🎮🎮", 1, 0);
    assert!(header.is_ok());

    // "🎮🎮🎮🎮🎮" would be 20 bytes - must fail
    let header = RaffApplicationHeader::with_str("🎮🎮🎮🎮🎮", 1, 0);
    assert!(header.is_err());
}

#[test]
fn test_application_header_serialize_deserialize() {
    let header = RaffApplicationHeader::with_str("Editor.Palette", 1, 5).unwrap();

    let mut stream = Cursor::new(Vec::new());
    header.serialize(&mut stream).expect("should serialize");

    // Check the serialized length is 18 bytes (16 + 1 + 1)
    assert_eq!(stream.get_ref().len(), 18);

    // Deserialize and verify
    stream.set_position(0);
    let deserialized = RaffApplicationHeader::deserialize(&mut stream).unwrap();

    assert_eq!(header, deserialized);
    assert_eq!(deserialized.as_string(), "Editor.Palette");
    assert_eq!(deserialized.app_major, 1);
    assert_eq!(deserialized.app_minor, 5);
}

#[test]
fn test_write_raff_header_with_app() {
    let mut stream = Cursor::new(Vec::new());

    write_raff_header_with_app(&mut stream, "TestApp.Config", 1, 2).expect("should write");

    // Verify total length: 13 (RAFF header) + 18 (App header) = 31 bytes
    assert_eq!(stream.get_ref().len(), 31);

    // Read back and verify
    stream.set_position(0);
    let (raff_header, app_header) = read_raff_header(&mut stream).unwrap();

    assert_eq!(raff_header.major, 0);
    assert_eq!(raff_header.minor, 2);
    assert_eq!(app_header.as_string(), "TestApp.Config");
    assert_eq!(app_header.app_major, 1);
    assert_eq!(app_header.app_minor, 2);
}

#[test]
fn test_complete_raff_file_with_app_header() {
    let mut stream = Cursor::new(Vec::new());

    // Write complete file
    write_raff_header_with_app(&mut stream, "CoolGame.Save", 1, 0).unwrap();
    write_chunk(&mut stream, "dt".try_into().unwrap(), b"Hello, World!").unwrap();
    write_chunk(&mut stream, "md".try_into().unwrap(), b"metadata").unwrap();

    // Read back
    let buffer = stream.into_inner();
    let mut stream = Cursor::new(buffer);

    let (_raff_header, app_header) = read_raff_header(&mut stream).unwrap();
    assert_eq!(app_header.as_string(), "CoolGame.Save");
    assert_eq!(app_header.app_major, 1);
    assert_eq!(app_header.app_minor, 0);

    // Read first chunk
    let chunk1 = read_chunk_header(&mut stream).unwrap();
    assert_eq!(chunk1.tag.name, "dt".try_into().unwrap());
    assert_eq!(chunk1.size, 13);
    let mut data1 = vec![0u8; chunk1.size as usize];
    stream.read_exact(&mut data1).unwrap();
    assert_eq!(&data1, b"Hello, World!");

    // Read second chunk
    let chunk2 = read_chunk_header(&mut stream).unwrap();
    assert_eq!(chunk2.tag.name, "md".try_into().unwrap());
    assert_eq!(chunk2.size, 8);
    let mut data2 = vec![0u8; chunk2.size as usize];
    stream.read_exact(&mut data2).unwrap();
    assert_eq!(&data2, b"metadata");
}