use std::{collections::HashMap, fmt::Debug, fs::{self, File}, io::{BufReader, Cursor}, path::Path, sync::{Arc, RwLock}};
use serde::Serialize;
use crate::{PAK_FILE_VERSION, Pak, PakInner, btree::PakTreeBuilder, error::PakResult, index::{Indices, PakIndex, PakSearchable}, meta::{PakMeta, PakSizing}, pointer::{PakPointer, PakUntypedPointer}};
pub struct PakBuilder {
chunks : Vec<PakVaultReference>,
size_in_bytes : u64,
vault : Vec<u8>,
name: String,
description: String,
author: String,
version : String,
extra : Vec<u8>
}
impl PakBuilder {
pub fn new() -> Self {
Self {
vault : Vec::new(),
chunks : Vec::new(),
size_in_bytes : 0,
name: String::new(),
description: String::new(),
author: String::new(),
extra : Vec::new(),
version : String::new()
}
}
pub fn pak_no_search<T>(&mut self, item : &T) -> PakResult<PakPointer> where T : Serialize {
let bytes = bincode::serialize(item)?;
let pointer = PakPointer::new_untyped(self.size_in_bytes, bytes.len() as u64);
self.size_in_bytes += bytes.len() as u64;
self.vault.extend(bytes);
self.chunks.push(PakVaultReference { pointer: pointer.clone(), indices: vec![] });
Ok(pointer)
}
pub fn pak<T>(&mut self, item : &T) -> PakResult<PakPointer> where T : Serialize + PakSearchable {
let mut indices = Indices::default();
item.get_indices(&mut indices);
let bytes = bincode::serialize(item)?;
let pointer = PakPointer::new_typed::<T>(self.size_in_bytes, bytes.len() as u64);
self.size_in_bytes += bytes.len() as u64;
self.vault.extend(bytes);
self.chunks.push(PakVaultReference { pointer: pointer.clone(), indices: indices.unwrap() });
Ok(pointer)
}
pub fn size(&self) -> u64 {
self.size_in_bytes
}
pub fn len(&self) -> usize {
self.chunks.len()
}
pub fn with_name(mut self, name: &str) -> Self {
self.name = name.to_string();
self
}
pub fn with_description(mut self, description: &str) -> Self {
self.description = description.to_string();
self
}
pub fn with_author(mut self, author: &str) -> Self {
self.author = author.to_string();
self
}
pub fn set_name(&mut self, name: &str) {
self.name = name.to_string();
}
pub fn set_description(&mut self, description: &str) {
self.description = description.to_string();
}
pub fn set_author(&mut self, author: &str) {
self.author = author.to_string();
}
pub fn set_extra<T>(&mut self, value : &T) -> PakResult<()> where T : Serialize {
self.extra = bincode::serialize(value)?;
Ok(())
}
pub fn set_version(&mut self, version: String) {
self.version = version;
}
pub fn build_file(self, path : impl AsRef<Path>) -> PakResult<Pak> {
let (out, sizing, meta) = self.build_internal()?;
fs::write(&path, out)?;
let inner = Arc::new(PakInner {
sizing,
meta,
source: RwLock::new(Box::new(BufReader::new(File::open(path)?))),
});
let pak = Pak { inner };
Ok(pak)
}
pub fn build_in_memory(self) -> PakResult<Pak> {
let (out, sizing, meta) = self.build_internal()?;
let inner = Arc::new(PakInner {
sizing,
meta,
source: RwLock::new(Box::new(Cursor::new(out))),
});
let pak = Pak { inner };
Ok(pak)
}
fn build_internal(mut self) -> PakResult<(Vec<u8>, PakSizing, PakMeta)> {
let mut map : HashMap<String, PakTreeBuilder> = HashMap::new();
let mut list_values : HashMap<String, Vec<PakPointer>> = HashMap::new();
for chunk in &self.chunks {
for index in &chunk.indices{
map.entry(index.key.clone())
.or_insert(PakTreeBuilder::new(16))
.access()
.insert(index.value.clone(), chunk.pointer.clone())
;
}
let mut ptr = chunk.pointer.clone();
ptr.drop_type();
list_values.entry(chunk.pointer.type_name().to_string())
.or_insert(Vec::new())
.push(ptr);
}
let mut pointer_map : HashMap<String, PakUntypedPointer> = HashMap::new();
for (key, tree) in map {
let pointer = tree.into_pak(&mut self)?;
pointer_map.insert(key, pointer.as_untyped());
}
let mut lists = HashMap::<String, PakPointer>::new();
for (type_name, list) in list_values.into_iter() {
let pointer = self.pak_no_search(&list)?;
lists.insert(type_name, pointer);
}
let hash = sha256::digest(self.vault.as_slice());
let meta = PakMeta {
identifier : format!("{hash}|{}|{}", self.name, self.version),
name: self.name,
description: self.description,
author: self.author,
version: self.version,
pak_version: PAK_FILE_VERSION.to_string(),
extra : self.extra
};
let sizing = PakSizing {
meta_size: bincode::serialized_size(&meta)?,
indices_size: bincode::serialized_size(&pointer_map)?,
vault_size: bincode::serialized_size(&self.vault)?,
list_size: bincode::serialized_size(&lists)?,
};
let mut sizing_out = bincode::serialize(&sizing)?;
let mut meta_out = bincode::serialize(&meta)?;
let mut pointer_map_out = bincode::serialize(&pointer_map)?;
let mut vault_out = bincode::serialize(&self.vault)?;
let mut list_out = bincode::serialize(&lists)?;
let mut out = Vec::<u8>::new();
out.append(&mut sizing_out);
out.append(&mut meta_out);
out.append(&mut pointer_map_out);
out.append(&mut list_out);
out.append(&mut vault_out);
Ok((out, sizing, meta))
}
}
#[derive(Debug, Clone)]
pub(crate) struct PakVaultReference {
pointer : PakPointer,
indices : Vec<PakIndex>
}