use crate::error::Result;
use crate::virtual_texture::types::{GtpHeader, GtsCodec};
use byteorder::{LittleEndian, WriteBytesExt};
use std::io::{Seek, SeekFrom, Write};
#[derive(Debug, Clone)]
pub struct Chunk {
pub codec: GtsCodec,
pub parameter_block_id: u32,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Default)]
struct Page {
chunks: Vec<Chunk>,
used_size: u32,
}
pub struct GtpWriter {
guid: [u8; 16],
page_size: u32,
pages: Vec<Page>,
current_page: usize,
}
impl GtpWriter {
#[must_use]
pub fn new(guid: [u8; 16], page_size: u32) -> Self {
Self {
guid,
page_size,
pages: vec![Page::default()],
current_page: 0,
}
}
pub fn add_chunk(&mut self, chunk: Chunk) -> (u16, u16) {
let chunk_size = 12 + chunk.data.len() as u32;
let current_page = &self.pages[self.current_page];
let new_header_size = 4 + 4 * (current_page.chunks.len() as u32 + 1);
let total_needed = new_header_size + current_page.used_size + chunk_size;
if total_needed > self.page_size && !current_page.chunks.is_empty() {
self.pages.push(Page::default());
self.current_page = self.pages.len() - 1;
}
let page_idx = self.current_page as u16;
let chunk_idx = self.pages[self.current_page].chunks.len() as u16;
let page = &mut self.pages[self.current_page];
page.used_size += chunk_size;
page.chunks.push(chunk);
(page_idx, chunk_idx)
}
#[must_use]
pub fn num_pages(&self) -> u32 {
self.pages.len() as u32
}
pub fn write<W: Write + Seek>(&self, writer: &mut W) -> Result<()> {
writer.write_u32::<LittleEndian>(GtpHeader::MAGIC)?;
writer.write_u32::<LittleEndian>(4)?; writer.write_all(&self.guid)?;
for (page_idx, page) in self.pages.iter().enumerate() {
let page_start = (page_idx as u64) * (self.page_size as u64);
let data_start = if page_idx == 0 { 24 } else { page_start };
writer.seek(SeekFrom::Start(data_start))?;
writer.write_u32::<LittleEndian>(page.chunks.len() as u32)?;
let page_header_size = if page_idx == 0 { 24 } else { 0 };
let chunk_table_size = 4 + 4 * page.chunks.len();
let mut offset = (page_header_size + chunk_table_size) as u32;
for chunk in &page.chunks {
writer.write_u32::<LittleEndian>(offset)?;
offset += 12 + chunk.data.len() as u32;
}
for chunk in &page.chunks {
writer.write_u32::<LittleEndian>(chunk.codec as u32)?;
writer.write_u32::<LittleEndian>(chunk.parameter_block_id)?;
writer.write_u32::<LittleEndian>(chunk.data.len() as u32)?;
writer.write_all(&chunk.data)?;
}
let current_pos = writer.stream_position()?;
let page_end = page_start + self.page_size as u64;
if current_pos < page_end {
let padding = (page_end - current_pos) as usize;
let zeros = vec![0u8; padding];
writer.write_all(&zeros)?;
}
}
Ok(())
}
}