use crate::error::{IoError, Result};
use crate::matlab::{
MatType, MI_INT32, MI_INT8, MI_MATRIX, MI_UINT32, MX_CHAR_CLASS, MX_DOUBLE_CLASS,
MX_INT32_CLASS, MX_SINGLE_CLASS, MX_UINT8_CLASS,
};
use byteorder::{LittleEndian, WriteBytesExt};
use scirs2_core::ndarray::ArrayD;
use std::io::{Seek, Write};
const MI_UINT8: i32 = 2;
#[allow(dead_code)]
const MI_INT16: i32 = 3;
const MI_UINT16: i32 = 4;
const MI_SINGLE: i32 = 7;
const MI_DOUBLE: i32 = 9;
#[allow(dead_code)]
const MI_INT64: i32 = 12;
#[allow(dead_code)]
const MI_UINT64: i32 = 13;
#[allow(dead_code)]
pub fn write_mat_header<W: Write>(writer: &mut W) -> Result<()> {
writer
.write_all(b"MATLAB")
.map_err(|e| IoError::FileError(format!("Failed to write header: {e}")))?;
let description = b" 5.0 MAT-file, Created by: scirs2-io library";
let description_len = description.len();
writer
.write_all(description)
.map_err(|e| IoError::FileError(format!("Failed to write description: {e}")))?;
let headertext_target = 124;
let already_written = 6 + description_len; let padding_needed = headertext_target - already_written;
if padding_needed > 0 {
let padding = vec![0u8; padding_needed];
writer
.write_all(&padding)
.map_err(|e| IoError::FileError(format!("Failed to write padding: {e}")))?;
}
writer
.write_u16::<LittleEndian>(0x0100) .map_err(|e| IoError::FileError(format!("Failed to write version: {e}")))?;
writer
.write_u16::<LittleEndian>(0x4D49) .map_err(|e| IoError::FileError(format!("Failed to write endianness: {e}")))?;
Ok(())
}
#[allow(dead_code)]
pub fn write_variable<W: Write + Seek>(
writer: &mut W,
name: &str,
mat_type: &MatType,
) -> Result<()> {
writer
.write_i32::<LittleEndian>(MI_MATRIX)
.map_err(|e| IoError::FileError(format!("Failed to write matrix type: {e}")))?;
let size_pos = writer
.stream_position()
.map_err(|e| IoError::FileError(format!("Failed to get size position: {e}")))?;
writer
.write_i32::<LittleEndian>(0)
.map_err(|e| IoError::FileError(format!("Failed to write size placeholder: {e}")))?;
let data_start = writer
.stream_position()
.map_err(|e| IoError::FileError(format!("Failed to get data start: {e}")))?;
match mat_type {
MatType::Double(array) => {
write_matrix_header_content(writer, name, array.shape(), MX_DOUBLE_CLASS, false)?;
write_double_data(writer, array)?;
}
MatType::Single(array) => {
write_matrix_header_content(writer, name, array.shape(), MX_SINGLE_CLASS, false)?;
write_single_data(writer, array)?;
}
MatType::Int32(array) => {
write_matrix_header_content(writer, name, array.shape(), MX_INT32_CLASS, false)?;
write_int32_data(writer, array)?;
}
MatType::Logical(array) => {
write_matrix_header_content(writer, name, array.shape(), MX_UINT8_CLASS, true)?;
write_logical_data(writer, array)?;
}
MatType::Char(string) => {
write_char_data_content(writer, name, string)?;
}
_ => {
return Err(IoError::Other(format!(
"Data _type not yet supported in simplified writer: {:?}",
std::any::type_name::<MatType>()
)));
}
}
let data_end = writer
.stream_position()
.map_err(|e| IoError::FileError(format!("Failed to get data end: {e}")))?;
let total_size = (data_end - data_start) as i32;
let current_pos = data_end;
writer
.seek(std::io::SeekFrom::Start(size_pos))
.map_err(|e| IoError::FileError(format!("Failed to seek to size: {e}")))?;
writer
.write_i32::<LittleEndian>(total_size)
.map_err(|e| IoError::FileError(format!("Failed to write actual size: {e}")))?;
writer
.seek(std::io::SeekFrom::Start(current_pos))
.map_err(|e| IoError::FileError(format!("Failed to seek back: {e}")))?;
Ok(())
}
#[allow(dead_code)]
fn write_matrix_header_content<W: Write + Seek>(
writer: &mut W,
name: &str,
shape: &[usize],
class_type: i32,
is_logical: bool,
) -> Result<()> {
writer
.write_i32::<LittleEndian>(MI_UINT32)
.map_err(|e| IoError::FileError(format!("Failed to write flags type: {}", e)))?;
writer
.write_i32::<LittleEndian>(8)
.map_err(|e| IoError::FileError(format!("Failed to write flags size: {}", e)))?;
let mut flags = class_type as u32;
if is_logical {
flags |= 0x200;
}
writer
.write_u32::<LittleEndian>(flags)
.map_err(|e| IoError::FileError(format!("Failed to write flags: {}", e)))?;
writer
.write_u32::<LittleEndian>(0) .map_err(|e| IoError::FileError(format!("Failed to write reserved: {}", e)))?;
let dims_size = (shape.len() * 4) as i32;
writer
.write_i32::<LittleEndian>(MI_INT32)
.map_err(|e| IoError::FileError(format!("Failed to write dims type: {}", e)))?;
writer
.write_i32::<LittleEndian>(dims_size)
.map_err(|e| IoError::FileError(format!("Failed to write dims size: {}", e)))?;
for &dim in shape.iter().rev() {
writer
.write_i32::<LittleEndian>(dim as i32)
.map_err(|e| IoError::FileError(format!("Failed to write dimension: {}", e)))?;
}
let padding = (8 - (dims_size % 8)) % 8;
if padding > 0 {
writer
.write_all(&vec![0u8; padding as usize])
.map_err(|e| IoError::FileError(format!("Failed to write padding: {}", e)))?;
}
let name_bytes = name.as_bytes();
let name_len = name_bytes.len() as i32;
writer
.write_i32::<LittleEndian>(MI_INT8)
.map_err(|e| IoError::FileError(format!("Failed to write name type: {}", e)))?;
writer
.write_i32::<LittleEndian>(name_len)
.map_err(|e| IoError::FileError(format!("Failed to write name size: {}", e)))?;
writer
.write_all(name_bytes)
.map_err(|e| IoError::FileError(format!("Failed to write name: {}", e)))?;
let name_padding = (8 - (name_len % 8)) % 8;
if name_padding > 0 {
writer
.write_all(&vec![0u8; name_padding as usize])
.map_err(|e| IoError::FileError(format!("Failed to write name padding: {}", e)))?;
}
Ok(())
}
#[allow(dead_code)]
fn write_double_data<W: Write>(writer: &mut W, array: &ArrayD<f64>) -> Result<()> {
let data_size = (array.len() * 8) as i32;
writer
.write_i32::<LittleEndian>(MI_DOUBLE)
.map_err(|e| IoError::FileError(format!("Failed to write double type: {}", e)))?;
writer
.write_i32::<LittleEndian>(data_size)
.map_err(|e| IoError::FileError(format!("Failed to write double size: {}", e)))?;
for &value in array.iter() {
writer
.write_f64::<LittleEndian>(value)
.map_err(|e| IoError::FileError(format!("Failed to write double value: {}", e)))?;
}
let padding = (8 - (data_size % 8)) % 8;
if padding > 0 {
writer
.write_all(&vec![0u8; padding as usize])
.map_err(|e| IoError::FileError(format!("Failed to write double padding: {}", e)))?;
}
Ok(())
}
#[allow(dead_code)]
fn write_single_data<W: Write>(writer: &mut W, array: &ArrayD<f32>) -> Result<()> {
let data_size = (array.len() * 4) as i32;
writer
.write_i32::<LittleEndian>(MI_SINGLE)
.map_err(|e| IoError::FileError(format!("Failed to write single type: {}", e)))?;
writer
.write_i32::<LittleEndian>(data_size)
.map_err(|e| IoError::FileError(format!("Failed to write single size: {}", e)))?;
for &value in array.iter() {
writer
.write_f32::<LittleEndian>(value)
.map_err(|e| IoError::FileError(format!("Failed to write single value: {}", e)))?;
}
let padding = (8 - (data_size % 8)) % 8;
if padding > 0 {
writer
.write_all(&vec![0u8; padding as usize])
.map_err(|e| IoError::FileError(format!("Failed to write single padding: {}", e)))?;
}
Ok(())
}
#[allow(dead_code)]
fn write_int32_data<W: Write>(writer: &mut W, array: &ArrayD<i32>) -> Result<()> {
let data_size = (array.len() * 4) as i32;
writer
.write_i32::<LittleEndian>(MI_INT32)
.map_err(|e| IoError::FileError(format!("Failed to write int32 type: {}", e)))?;
writer
.write_i32::<LittleEndian>(data_size)
.map_err(|e| IoError::FileError(format!("Failed to write int32 size: {}", e)))?;
for &value in array.iter() {
writer
.write_i32::<LittleEndian>(value)
.map_err(|e| IoError::FileError(format!("Failed to write int32 value: {}", e)))?;
}
let padding = (8 - (data_size % 8)) % 8;
if padding > 0 {
writer
.write_all(&vec![0u8; padding as usize])
.map_err(|e| IoError::FileError(format!("Failed to write int32 padding: {}", e)))?;
}
Ok(())
}
#[allow(dead_code)]
fn write_logical_data<W: Write>(writer: &mut W, array: &ArrayD<bool>) -> Result<()> {
let data_size = array.len() as i32;
writer
.write_i32::<LittleEndian>(MI_UINT8)
.map_err(|e| IoError::FileError(format!("Failed to write logical type: {}", e)))?;
writer
.write_i32::<LittleEndian>(data_size)
.map_err(|e| IoError::FileError(format!("Failed to write logical size: {}", e)))?;
for &value in array.iter() {
writer
.write_u8(if value { 1 } else { 0 })
.map_err(|e| IoError::FileError(format!("Failed to write logical value: {}", e)))?;
}
let padding = (8 - (data_size % 8)) % 8;
if padding > 0 {
writer
.write_all(&vec![0u8; padding as usize])
.map_err(|e| IoError::FileError(format!("Failed to write logical padding: {}", e)))?;
}
Ok(())
}
#[allow(dead_code)]
fn write_char_data_content<W: Write + Seek>(
writer: &mut W,
name: &str,
string: &str,
) -> Result<()> {
let utf16_chars: Vec<u16> = string.encode_utf16().collect();
let shape = [1, utf16_chars.len()];
write_matrix_header_content(writer, name, &shape, MX_CHAR_CLASS, false)?;
let data_size = (utf16_chars.len() * 2) as i32;
writer
.write_i32::<LittleEndian>(MI_UINT16)
.map_err(|e| IoError::FileError(format!("Failed to write char type: {}", e)))?;
writer
.write_i32::<LittleEndian>(data_size)
.map_err(|e| IoError::FileError(format!("Failed to write char size: {}", e)))?;
for &ch in &utf16_chars {
writer
.write_u16::<LittleEndian>(ch)
.map_err(|e| IoError::FileError(format!("Failed to write char: {}", e)))?;
}
let padding = (8 - (data_size % 8)) % 8;
if padding > 0 {
writer
.write_all(&vec![0u8; padding as usize])
.map_err(|e| IoError::FileError(format!("Failed to write char padding: {}", e)))?;
}
Ok(())
}