use crate::legacy::float::BdatReal;
use crate::{BdatError, BdatResult, BdatVersion, Label};
use enum_kinds::EnumKind;
use num_enum::TryFromPrimitive;
use std::borrow::Cow;
use std::fmt::Display;
#[derive(Debug, Clone, PartialEq)]
pub enum Cell<'b> {
Single(Value<'b>),
List(Vec<Value<'b>>),
Flags(Vec<u32>),
}
#[derive(EnumKind, Debug, Clone, PartialEq)]
#[enum_kind(
ValueType,
derive(TryFromPrimitive),
repr(u8),
cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize)),
cfg_attr(feature = "serde", serde(into = "u8", try_from = "u8"))
)]
#[repr(u8)]
pub enum Value<'b> {
Unknown = 0,
UnsignedByte(u8) = 1,
UnsignedShort(u16) = 2,
UnsignedInt(u32) = 3,
SignedByte(i8) = 4,
SignedShort(i16) = 5,
SignedInt(i32) = 6,
String(Utf<'b>) = 7,
Float(BdatReal) = 8,
HashRef(u32) = 9,
Percent(u8) = 10,
DebugString(Utf<'b>) = 11,
Unknown12(u8) = 12,
MessageId(u16) = 13,
}
pub type Utf<'t> = Cow<'t, str>;
pub type ModernCell<'t, 'tb> = &'t Value<'tb>;
pub type LegacyCell<'t, 'tb> = &'t Cell<'tb>;
pub trait FromValue<'t, 'tb>
where
Self: Sized,
{
fn extract(value: &'t Value<'tb>) -> Option<Self>;
}
impl<'b> Cell<'b> {
pub fn as_single(&self) -> Option<&Value> {
match self {
Self::Single(v) => Some(v),
_ => None,
}
}
pub fn into_single(self) -> Option<Value<'b>> {
match self {
Self::Single(v) => Some(v),
_ => None,
}
}
pub fn as_list(&self) -> Option<&[Value<'b>]> {
match self {
Self::List(v) => Some(v),
_ => None,
}
}
pub fn into_list(self) -> Option<Vec<Value<'b>>> {
match self {
Self::List(v) => Some(v),
_ => None,
}
}
pub fn as_flags(&self) -> Option<&[u32]> {
match self {
Self::Flags(v) => Some(v),
_ => None,
}
}
pub fn into_flags(self) -> Option<Vec<u32>> {
match self {
Self::Flags(v) => Some(v),
_ => None,
}
}
}
impl<'b> Value<'b> {
pub fn get_as<'t, V: FromValue<'t, 'b>>(&'t self) -> V {
self.try_get_as().unwrap()
}
pub fn try_get_as<'t, V: FromValue<'t, 'b>>(&'t self) -> BdatResult<V> {
V::extract(self).ok_or_else(|| BdatError::ValueCast(self.into()))
}
pub fn to_integer(&self) -> u32 {
match self {
Self::SignedByte(b) => *b as u32,
Self::Percent(b) | Self::UnsignedByte(b) | Self::Unknown12(b) => *b as u32,
Self::SignedShort(s) => *s as u32,
Self::UnsignedShort(s) | Self::MessageId(s) => *s as u32,
Self::SignedInt(i) => *i as u32,
Self::UnsignedInt(i) | Self::HashRef(i) => *i,
_ => panic!("value is not an integer"),
}
}
pub fn to_float(&self) -> f32 {
match self {
Self::Float(f) => (*f).into(),
_ => panic!("value is not a float"),
}
}
pub fn into_string(self) -> String {
match self {
Self::String(s) | Self::DebugString(s) => s.into_owned(),
_ => panic!("value is not a string"),
}
}
pub fn as_str(&self) -> &str {
match self {
Self::String(s) | Self::DebugString(s) => s.as_ref(),
_ => panic!("value is not a string"),
}
}
}
impl ValueType {
pub fn data_len(self) -> usize {
use ValueType::*;
match self {
Unknown => 0,
UnsignedByte | SignedByte | Percent | Unknown12 => 1,
UnsignedShort | SignedShort | MessageId => 2,
UnsignedInt | SignedInt | String | Float | HashRef | DebugString => 4,
}
}
pub fn is_supported(self, version: BdatVersion) -> bool {
use ValueType::*;
match self {
Percent | Unknown12 | MessageId | HashRef | DebugString => version.is_modern(),
_ => true,
}
}
}
impl From<ValueType> for u8 {
fn from(t: ValueType) -> Self {
t as u8
}
}
macro_rules! default_display {
($fmt:expr, $val:expr, $($variants:tt ) *) => {
match $val {
$(
Value::$variants(a) => a.fmt($fmt),
)*
v => panic!("Unsupported Display {:?}", v)
}
};
}
impl<'b> Display for Value<'b> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unknown => Ok(()),
Self::HashRef(h) => Label::Hash(*h).fmt(f),
Self::Percent(v) => write!(f, "{}%", v),
v => {
default_display!(f, v, SignedByte SignedShort SignedInt UnsignedByte UnsignedShort UnsignedInt DebugString Unknown12 MessageId String Float)
}
}
}
}
impl<'b> Display for Cell<'b> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Single(val) => val.fmt(f),
Cell::List(list) => {
write!(f, "[")?;
for (i, value) in list.iter().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
value.fmt(f)?;
}
write!(f, "]")
}
Cell::Flags(nums) => {
write!(f, "{{")?;
for (i, value) in nums.iter().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
value.fmt(f)?;
}
write!(f, "}}")
}
}
}
}
macro_rules! from_value {
($val:ty, $($variants:path ) *) => {
impl<'t, 'tb> FromValue<'t, 'tb> for $val {
fn extract(value: &Value<'_>) -> Option<Self> {
match value {
$( | $variants(v) )* => Some(*v),
_ => None,
}
}
}
};
}
from_value!(u32, Value::UnsignedInt Value::HashRef);
from_value!(u16, Value::UnsignedShort Value::MessageId);
from_value!(u8, Value::UnsignedByte Value::Unknown12 Value::Percent);
from_value!(i32, Value::SignedInt);
from_value!(i16, Value::SignedShort);
from_value!(i8, Value::SignedByte);
from_value!(BdatReal, Value::Float);
impl<'t, 'tb> FromValue<'t, 'tb> for f32 {
fn extract(value: &Value<'_>) -> Option<Self> {
BdatReal::extract(value).map(Into::into)
}
}
impl<'t, 'tb> FromValue<'t, 'tb> for Utf<'tb> {
fn extract(value: &Value<'tb>) -> Option<Self> {
match value {
Value::String(s) | Value::DebugString(s) => Some(s.clone()),
_ => None,
}
}
}
impl<'t, 'tb> FromValue<'t, 'tb> for &'t str {
fn extract(value: &'t Value<'tb>) -> Option<Self> {
match value {
Value::String(s) | Value::DebugString(s) => Some(s.as_ref()),
_ => None,
}
}
}