use csv::{StringRecordsIter, Writer};
use getset::{Getters, Setters};
use rayon::prelude::*;
use serde_derive::{Serialize, Deserialize};
use std::borrow::Cow;
use std::collections::{BTreeMap, HashMap};
use std::fs::File;
use crate::binary::{ReadBytes, WriteBytes};
use crate::error::{RLibError, Result};
use crate::files::{DecodeableExtraData, Decodeable, EncodeableExtraData, Encodeable, table::{DecodedData, local::TableInMemory, Table}};
use crate::schema::*;
use crate::utils::check_size_mismatch;
const BYTEORDER_MARK: u16 = 65279;
const FILE_TYPE: &str = "LOC";
const HEADER_SIZE: usize = 14;
pub(crate) const TSV_NAME_LOC: &str = "Loc";
pub(crate) const TSV_NAME_LOC_OLD: &str = "Loc PackedFile";
pub const EXTENSION: &str = ".loc";
const VERSION: i32 = 1;
#[cfg(test)] mod loc_test;
#[derive(PartialEq, Clone, Debug, Getters, Setters, Serialize, Deserialize)]
#[getset(get = "pub", set = "pub")]
pub struct Loc {
table: TableInMemory,
}
impl Default for Loc {
fn default() -> Self {
Self::new()
}
}
impl Loc {
pub fn new() -> Self {
let definition = Self::new_definition();
Self {
table: TableInMemory::new(&definition, None, TSV_NAME_LOC),
}
}
pub(crate) fn new_definition() -> Definition {
let mut definition = Definition::new(VERSION, None);
let fields = vec![
Field::new("key".to_owned(), FieldType::StringU16, true, Some("PLACEHOLDER".to_owned()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None),
Field::new("text".to_owned(), FieldType::StringU16, false, Some("PLACEHOLDER".to_owned()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None),
Field::new("tooltip".to_owned(), FieldType::Boolean, false, Some("PLACEHOLDER".to_owned()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None),
];
definition.set_fields(fields);
definition
}
pub fn definition(&self) -> &Definition {
self.table.definition()
}
pub fn data(&'_ self) -> Cow<'_, [Vec<DecodedData>]> {
self.table.data()
}
pub fn data_mut(&mut self) -> &mut Vec<Vec<DecodedData>> {
self.table.data_mut()
}
pub fn new_row(&self) -> Vec<DecodedData> {
self.table().new_row()
}
pub fn set_data(&mut self, data: &[Vec<DecodedData>]) -> Result<()> {
self.table.set_data(data)
}
pub fn column_position_by_name(&self, column_name: &str) -> Option<usize> {
self.table().column_position_by_name(column_name)
}
pub fn len(&self) -> usize {
self.table.len()
}
pub fn is_empty(&self) -> bool {
self.table.is_empty()
}
pub fn set_definition(&mut self, new_definition: &Definition) {
self.table.set_definition(new_definition);
}
pub fn read_header<R: ReadBytes>(data: &mut R) -> Result<(i32, u32)> {
if data.len()? < HEADER_SIZE as u64 {
return Err(RLibError::DecodingLocNotALocTable)
}
if BYTEORDER_MARK != data.read_u16()? {
return Err(RLibError::DecodingLocNotALocTable)
}
if FILE_TYPE != data.read_string_u8(3)? {
return Err(RLibError::DecodingLocNotALocTable)
}
let _ = data.read_u8()?;
let version = data.read_i32()?;
let entry_count = data.read_u32()?;
Ok((version, entry_count))
}
pub fn merge(sources: &[&Self]) -> Result<Self> {
let mut new_table = Self::new();
let sources = sources.par_iter()
.map(|table| {
let mut table = table.table().clone();
table.set_definition(new_table.definition());
table
})
.collect::<Vec<_>>();
let new_data = sources.par_iter()
.map(|table| table.data().to_vec())
.flatten()
.collect::<Vec<_>>();
new_table.set_data(&new_data)?;
Ok(new_table)
}
pub fn tsv_import(records: StringRecordsIter<File>, field_order: &HashMap<u32, String>) -> Result<Self> {
let definition = Self::new_definition();
let table = TableInMemory::tsv_import(records, &definition, field_order, TSV_NAME_LOC, None)?;
let loc = Loc::from(table);
Ok(loc)
}
pub fn tsv_export(&self, writer: &mut Writer<File>, table_path: &str) -> Result<()> {
self.table.tsv_export(writer, table_path, true)
}
}
impl Decodeable for Loc {
fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
let (_version, entry_count) = Self::read_header(data)?;
let definition = Self::new_definition();
let table = TableInMemory::decode(data, &definition, &HashMap::new(), Some(entry_count), false, TSV_NAME_LOC)?;
check_size_mismatch(data.stream_position()? as usize, data.len()? as usize)?;
Ok(Self {
table,
})
}
}
impl Encodeable for Loc {
fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
buffer.write_u16(BYTEORDER_MARK)?;
buffer.write_string_u8(FILE_TYPE)?;
buffer.write_u8(0)?;
buffer.write_i32(*self.table.definition().version())?;
buffer.write_u32(self.table.len() as u32)?;
self.table.encode(buffer)
}
}
impl From<TableInMemory> for Loc {
fn from(mut table: TableInMemory) -> Self {
table.set_table_name(TSV_NAME_LOC.to_owned());
Self {
table,
}
}
}