use std::io::{Read, Seek};
use crate::bank;
use crate::cbin::{self, Cbin, Header};
use crate::error::Error;
use crate::formats::ne5::program;
use crate::types::RangedU16Pair;
pub const FORMAT: &str = "ne5t";
pub const KNOWN_VERSIONS: &[u32] = &[0, 1];
pub const BODY_LEN: usize = 18;
pub const FILE_LEN: usize = 0x2c + BODY_LEN;
pub const PROGRAM_COUNT: usize = 4;
pub const BANK_COUNT: u16 = 4;
pub const SLOT_COUNT: u16 = 50;
pub const DEFAULT_VERSION: u32 = 1;
pub type Location = RangedU16Pair<BANK_COUNT, SLOT_COUNT>;
pub type Bank = bank::Bank<Cbin<Song>, Location>;
#[nord_bits_derive::bitbody(18)]
pub struct Song {
#[bits(0..=15)]
pub version: u16,
#[bits(16..=24)]
pub a: program::Location,
#[bits(25..=33)]
pub b: program::Location,
#[bits(34..=42)]
pub c: program::Location,
#[bits(43..=51)]
pub d: program::Location,
}
impl Song {
pub fn programs(&self) -> [program::Location; PROGRAM_COUNT] {
[self.a, self.b, self.c, self.d]
}
pub fn get(&self, slot: u16) -> program::Location {
match self.programs().get(slot as usize) {
Some(at) => *at,
None => panic!("no slot {slot}: a song holds {PROGRAM_COUNT} programs"),
}
}
pub fn set(&mut self, slot: u16, location: program::Location) {
match slot {
0 => self.a = location,
1 => self.b = location,
2 => self.c = location,
3 => self.d = location,
_ => panic!("no slot {slot}: a song holds {PROGRAM_COUNT} programs"),
}
}
}
pub fn location(file: &Cbin<Song>) -> Result<Location, Error> {
program::slot(&file.header)
}
pub fn new(
location: Location,
version: u32,
programs: [program::Location; PROGRAM_COUNT],
) -> Cbin<Song> {
let [a, b, c, d] = programs;
Cbin {
header: Header::new(FORMAT, location.inner(), version),
body: Song {
raw: [0; BODY_LEN],
version: version as u16,
a,
b,
c,
d,
},
}
}
pub fn read_from(reader: &mut (impl Read + Seek)) -> Result<Cbin<Song>, Error> {
let file: Cbin<Song> = cbin::read(reader, FORMAT)?;
program::known_version(FORMAT, file.header.version, KNOWN_VERSIONS)?;
program::unset_aux(FORMAT, &file.header)?;
location(&file)?;
Ok(file)
}
impl bank::Item<Location> for Cbin<Song> {
fn location(&self) -> Location {
location(self).expect("a song's location is validated at construction")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bank::Item;
use crate::error::Error;
use std::io::Cursor;
#[test]
fn read_write_new_song() -> Result<(), Error> {
let song = new(
(0, 1).try_into()?,
DEFAULT_VERSION,
[
(1, 2).try_into()?,
(2, 3).try_into()?,
(3, 4).try_into()?,
(4, 5).try_into()?,
],
);
assert_eq!(song.location(), (0, 1));
assert_eq!(song.get(0), (1, 2));
assert_eq!(song.get(1), (2, 3));
assert_eq!(song.get(2), (3, 4));
assert_eq!(song.get(3), (4, 5));
let mut write_result = Vec::new();
song.write_to(&mut Cursor::new(&mut write_result)).unwrap();
let result = read_from(&mut Cursor::new(&mut write_result)).unwrap();
assert_eq!(song.location(), result.location());
assert_eq!(song.get(0), result.get(0));
assert_eq!(song.get(1), result.get(1));
assert_eq!(song.get(2), result.get(2));
assert_eq!(song.get(3), result.get(3));
Ok(())
}
#[test]
fn version_survives_a_round_trip() -> Result<(), Error> {
for version in [0u32, 1] {
let song = new(
(0, 5).try_into()?,
version,
[
(1, 2).try_into()?,
(2, 3).try_into()?,
(3, 4).try_into()?,
(4, 5).try_into()?,
],
);
let mut bytes = Vec::new();
song.write_to(&mut Cursor::new(&mut bytes)).unwrap();
assert_eq!(
u32::from_le_bytes(bytes[0x14..0x18].try_into().unwrap()),
version,
"header version for v{version}",
);
assert_eq!(
u16::from_be_bytes(bytes[0x2c..0x2e].try_into().unwrap()) as u32,
version,
"body version echo for v{version}",
);
let back = read_from(&mut Cursor::new(&mut bytes)).unwrap();
assert_eq!(back.header.version, version);
assert_eq!(back.get(0), song.get(0));
}
Ok(())
}
#[test]
fn update_song_program() -> Result<(), Error> {
let mut song = new(
(0, 1).try_into()?,
DEFAULT_VERSION,
[
(1, 2).try_into()?,
(2, 3).try_into()?,
(3, 4).try_into()?,
(4, 5).try_into()?,
],
);
song.set(1, (5, 20).try_into()?);
assert_eq!(song.location(), (0, 1));
assert_eq!(song.get(0), (1, 2));
assert_eq!(song.get(1), (5, 20));
assert_eq!(song.get(2), (3, 4));
assert_eq!(song.get(3), (4, 5));
let mut write_result = Vec::new();
song.write_to(&mut Cursor::new(&mut write_result)).unwrap();
let result = read_from(&mut Cursor::new(&mut write_result)).unwrap();
assert_eq!(song.location(), result.location());
assert_eq!(song.get(0), result.get(0));
assert_eq!(song.get(1), result.get(1));
assert_eq!(song.get(2), result.get(2));
assert_eq!(song.get(3), result.get(3));
Ok(())
}
}