use super::{ArrangeRow, ArrangementError, ARRANGEMENT_DEFAULT_NAME};
use crate::generics::ArrangeRows;
use crate::{Defaults, IsDefault, OtToolsIoError};
use itertools::Itertools;
use ot_tools_io_derive::{AsMutDerive, AsRefDerive};
use serde::{Deserialize, Serialize};
use std::array::from_fn;
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Deserialize,
Serialize,
AsRefDerive,
AsMutDerive,
)]
pub struct ArrangementState {
pub name: [u8; 15],
pub unknown_1: [u8; 2],
pub n_rows: u8,
pub rows: ArrangeRows<ArrangeRow>,
}
impl Default for ArrangementState {
fn default() -> Self {
Self {
name: ARRANGEMENT_DEFAULT_NAME,
unknown_1: from_fn(|_| 0),
n_rows: 0,
rows: ArrangeRows::default(),
}
}
}
impl IsDefault for ArrangementState {
fn is_default(&self) -> bool {
let default = &Self::default();
default.unknown_1 == self.unknown_1
&& default.n_rows == self.n_rows
&& default.rows == self.rows
}
}
impl ArrangementState {
pub fn new(
name: [u8; 15],
unknown_1: [u8; 2],
n_rows: u8,
rows: ArrangeRows<ArrangeRow>,
) -> Self {
Self {
name,
unknown_1,
n_rows,
rows,
}
}
pub fn first_empty_row_index(&self) -> u8 {
self.rows
.iter()
.find_position(|x| matches!(x, ArrangeRow::EmptyRow(_)))
.map(|x| x.0 as u8)
.unwrap_or(0)
}
pub fn validate(&self) -> Result<(), OtToolsIoError> {
if self
.name
.iter()
.any(|x| super::is_not_valid_ascii_byte(*x) && *x != 0)
{
return Err(ArrangementError::Name(self.name).into());
}
let (n_rows, first_empty_idx) = (self.n_rows, self.first_empty_row_index());
if n_rows != first_empty_idx {
return Err(ArrangementError::RowCount {
n_rows,
first_empty_idx,
}
.into());
}
for row in self.rows.iter() {
match row {
ArrangeRow::PatternRow(x) => x.validate()?,
ArrangeRow::LoopOrJumpOrHaltRow(x) => x.validate()?,
ArrangeRow::ReminderRow(x) => x.validate()?,
_ => {}
}
}
Ok(())
}
pub(super) fn parse_from_bytes(bytes: &[u8; 5650]) -> Result<Self, OtToolsIoError> {
let name: [u8; 15] = from_fn(|x| bytes[x]);
let unknown_1: [u8; 2] = from_fn(|x| bytes[x + 15]);
let n_rows = bytes[17];
let mut rows: [ArrangeRow; 256] = ArrangeRow::defaults();
for (idx, row) in rows.iter_mut().enumerate() {
match idx >= n_rows as usize {
true => {
let offset = 18;
let idx_start = offset + (idx * 22);
let row_bytes: [u8; 22] = from_fn(|j| bytes[j + idx_start]);
*row = ArrangeRow::EmptyRow(row_bytes);
}
false => {
let offset = 18;
let idx_start = offset + (idx * 22);
let row_bytes: [u8; 22] = from_fn(|j| bytes[j + idx_start]);
*row = ArrangeRow::parse_from_bytes(&row_bytes)?;
}
}
}
let rows = ArrangeRows::new(rows);
let block = ArrangementState {
name,
unknown_1,
n_rows,
rows,
};
block.validate()?;
Ok(block)
}
pub(super) fn parse_to_bytes(&self) -> Result<Vec<u8>, OtToolsIoError> {
let mut v = vec![];
v.append(&mut self.name.to_vec());
v.append(&mut self.unknown_1.to_vec());
v.append(&mut vec![self.n_rows]);
for row in self.rows.iter() {
v.append(&mut row.parse_to_bytes()?);
}
Ok(v)
}
}
#[cfg(test)]
mod new_and_validate {
use crate::arrangements::{
ArrangeRow, ArrangementError, ArrangementState, LoopOrJumpOrHaltRow,
};
use crate::generics::ArrangeRows;
use crate::OtToolsIoError;
#[test]
fn ok() -> Result<(), OtToolsIoError> {
let arr = ArrangementState::new(
std::array::from_fn(|_| 48),
[0, 0],
0,
ArrangeRows::default(),
);
arr.validate()?;
Ok(())
}
#[test]
fn invalid_rows() -> Result<(), OtToolsIoError> {
let arr = ArrangementState::new(
std::array::from_fn(|_| 48),
[0, 0],
20,
ArrangeRows::default(),
);
assert_eq!(
arr.validate().err().unwrap().to_string(),
OtToolsIoError::Arrangement(ArrangementError::RowCount {
n_rows: 20,
first_empty_idx: 0
})
.to_string(),
);
Ok(())
}
#[test]
fn invalid_name() -> Result<(), OtToolsIoError> {
let arr = ArrangementState::new(
[20, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48],
[0, 0],
0,
ArrangeRows::default(),
);
assert_eq!(
arr.validate().err().unwrap().to_string(),
OtToolsIoError::Arrangement(ArrangementError::Name([
20, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48
]))
.to_string(),
);
Ok(())
}
#[test]
fn invalid_row() -> Result<(), OtToolsIoError> {
let mut rows = ArrangeRows::default();
let bad_row = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
loop_count: 200,
row_target: 0,
unused: Default::default(),
});
rows[0] = bad_row;
let arr = ArrangementState::new(std::array::from_fn(|_| 48), [0, 0], 1, rows);
assert_eq!(
arr.validate().err().unwrap().to_string(),
OtToolsIoError::Arrangement(ArrangementError::LoopCount(200)).to_string(),
);
Ok(())
}
}
#[cfg(test)]
mod parse_to_bytes {
use crate::arrangements::{ArrangeRow, ArrangementState, PatternRow};
use crate::generics::ArrangeRows;
#[test]
fn arrangement_block() {
let expected_rows: [ArrangeRow; 256] = std::array::from_fn(|i| {
if i < 10 {
ArrangeRow::PatternRow(PatternRow {
pattern_id: 1,
repetitions: 1,
unused_1: 0,
mute_mask: 1,
unused_2: 0,
tempo_1: 1,
tempo_2: 1,
scene_a: 1,
scene_b: 1,
unused_3: 0,
offset: 1,
unused_4: 0,
length: 1,
midi_transpose: [8, 1, 1, 1, 1, 1, 1, 8],
})
} else {
ArrangeRow::default()
}
});
let rows = ArrangeRows::new(expected_rows);
let expected = ArrangementState {
name: [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10],
unknown_1: [10, 9],
n_rows: 10,
rows,
};
let r = expected.parse_to_bytes();
println!("{r:?}");
assert!(r.is_ok());
let v = r.unwrap();
assert_eq!(5650, v.len());
}
}
#[cfg(test)]
mod parse_from_bytes {
use crate::arrangements::mocks::mock_arrangement_block_empty_rows;
use crate::arrangements::ArrangementState;
#[test]
fn valid_empty_rows() {
let (expected, test_bytes) = mock_arrangement_block_empty_rows();
let r = ArrangementState::parse_from_bytes(&test_bytes);
assert_eq!(expected, r.unwrap());
}
}