use crate::classes::DxfClass;
use crate::io::dwg::crc::{crc16, CRC16_SEED};
use crate::io::dwg::dwg_stream_writers::DwgMergedWriter;
use crate::io::dwg::dwg_version::DwgVersion;
use crate::io::dwg::file_headers::section_definition::{end_sentinels, start_sentinels};
use crate::types::DxfVersion;
pub fn write_classes(version: DxfVersion, classes: &[DxfClass], maintenance_version: u8) -> Vec<u8> {
let dwg_version =
DwgVersion::from_dxf_version(version).unwrap_or(DwgVersion::AC15);
if version >= DxfVersion::AC1021 {
let mut writer = DwgMergedWriter::new(dwg_version, version);
writer.save_position_for_size();
let max_class_number = classes
.iter()
.map(|c| c.class_number)
.max()
.unwrap_or(0);
writer.write_bit_short(max_class_number);
writer.write_byte(0);
writer.write_byte(0);
writer.write_bit(true);
for c in classes {
writer.write_bit_short(c.class_number);
writer.write_bit_short(c.proxy_flags.0 as i16);
writer.write_variable_text(&c.application_name);
writer.write_variable_text(&c.cpp_class_name);
writer.write_variable_text(&c.dxf_name);
writer.write_bit(c.was_zombie);
writer.write_bit_short(c.item_class_id);
writer.write_bit_long(c.instance_count);
writer.write_bit_long(0);
writer.write_bit_long(0);
writer.write_bit_long(0);
writer.write_bit_long(0);
}
let section_data = writer.merge();
write_size_and_crc(version, maintenance_version, §ion_data)
} else {
let mut writer = DwgMergedWriter::new(dwg_version, version);
if version >= DxfVersion::AC1018 {
let max_class_number = classes
.iter()
.map(|c| c.class_number)
.max()
.unwrap_or(0);
writer.write_bit_short(max_class_number);
writer.write_byte(0);
writer.write_byte(0);
writer.write_bit(true);
}
for c in classes {
writer.write_bit_short(c.class_number);
writer.write_bit_short(c.proxy_flags.0 as i16);
writer.write_variable_text(&c.application_name);
writer.write_variable_text(&c.cpp_class_name);
writer.write_variable_text(&c.dxf_name);
writer.write_bit(c.was_zombie);
writer.write_bit_short(c.item_class_id);
if version >= DxfVersion::AC1018 {
writer.write_bit_long(c.instance_count);
writer.write_bit_long(0);
writer.write_bit_long(0);
writer.write_bit_long(0);
writer.write_bit_long(0);
}
}
let section_data = writer.merge();
write_size_and_crc(version, maintenance_version, §ion_data)
}
}
fn write_size_and_crc(version: DxfVersion, maintenance_version: u8, section_data: &[u8]) -> Vec<u8> {
let mut output = Vec::with_capacity(
16 + 4 + section_data.len() + 2 + 16 + 8,
);
output.extend_from_slice(&start_sentinels::CLASSES);
let mut crc_content = Vec::with_capacity(4 + section_data.len());
crc_content.extend_from_slice(&(section_data.len() as i32).to_le_bytes());
if DwgVersion::has_section_extra_rl(version, maintenance_version) {
crc_content.extend_from_slice(&0i32.to_le_bytes());
}
crc_content.extend_from_slice(section_data);
let crc = crc16(CRC16_SEED, &crc_content);
output.extend_from_slice(&crc_content);
output.extend_from_slice(&crc.to_le_bytes());
output.extend_from_slice(&end_sentinels::CLASSES);
if version >= DxfVersion::AC1018 {
output.extend_from_slice(&[0u8; 8]);
}
output
}
#[cfg(test)]
mod tests {
use super::*;
use crate::classes::ProxyFlags;
fn make_test_class(number: i16, name: &str) -> DxfClass {
DxfClass {
dxf_name: name.to_string(),
cpp_class_name: format!("AcDb{}", name),
application_name: "ObjectDBX Classes".to_string(),
proxy_flags: ProxyFlags::NONE,
instance_count: 0,
was_zombie: false,
is_an_entity: false,
class_number: number,
item_class_id: 0x1F3, }
}
#[test]
fn test_write_classes_empty() {
let data = write_classes(DxfVersion::AC1015, &[], 0);
assert!(data.len() >= 16 + 4 + 2 + 16);
assert_eq!(&data[..16], &start_sentinels::CLASSES);
let end_start = data.len() - 16;
assert_eq!(&data[end_start..], &end_sentinels::CLASSES);
}
#[test]
fn test_write_classes_r2004_has_trailing_zeros() {
let data = write_classes(DxfVersion::AC1018, &[], 0);
let last8 = &data[data.len() - 8..];
assert_eq!(last8, &[0u8; 8]);
}
#[test]
fn test_write_classes_with_one_class() {
let cls = make_test_class(500, "PLACEHOLDER");
let data = write_classes(DxfVersion::AC1015, &[cls], 0);
assert_eq!(&data[..16], &start_sentinels::CLASSES);
let size = i32::from_le_bytes([data[16], data[17], data[18], data[19]]);
assert!(size > 0, "Section data size should be > 0");
}
#[test]
fn test_write_classes_r2004_header() {
let cls = make_test_class(500, "TEST");
let data = write_classes(DxfVersion::AC1018, &[cls.clone()], 0);
let data_r2000 = write_classes(DxfVersion::AC1015, &[cls], 0);
assert!(
data.len() > data_r2000.len(),
"R2004 classes should be longer than R2000: {} vs {}",
data.len(),
data_r2000.len()
);
}
#[test]
fn test_write_classes_crc_present() {
let cls = make_test_class(500, "X");
let data = write_classes(DxfVersion::AC1015, &[cls], 0);
let end_sentinel_start = data.len() - 16;
let crc_bytes = &data[end_sentinel_start - 2..end_sentinel_start];
let size_plus_data = &data[16..end_sentinel_start - 2];
let expected_crc = crc16(CRC16_SEED, size_plus_data);
let actual_crc = u16::from_le_bytes([crc_bytes[0], crc_bytes[1]]);
assert_eq!(
actual_crc, expected_crc,
"CRC mismatch: got 0x{:04X}, expected 0x{:04X}",
actual_crc, expected_crc
);
}
}