1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use ruby_marshal::FromValue;
use ruby_marshal::FromValueContext;
use ruby_marshal::FromValueError;
use ruby_marshal::IntoValue;
use ruby_marshal::IntoValueError;
use ruby_marshal::SymbolValue;
use ruby_marshal::UserDefinedValue;
use ruby_marshal::Value;
use ruby_marshal::ValueArena;
use ruby_marshal::ValueHandle;

#[derive(Debug)]
pub enum TableFromValueError {
    TooShort { len: usize },
    OddSizedPayload { len: usize },
    ItemSizeMismatch { expected: i32, actual: usize },
}

impl std::fmt::Display for TableFromValueError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Self::TooShort { len } => {
                write!(f, "the table payload (len {len}) is too short")
            }
            Self::OddSizedPayload { len } => {
                write!(f, "the table payload (len {len}) is not a multiple of 2")
            }
            Self::ItemSizeMismatch { expected, actual } => {
                write!(f, "the item array length is mismatched, expected {expected} bytes but got {actual}")
            }
        }
    }
}

impl std::error::Error for TableFromValueError {}

#[derive(Debug)]
pub enum TableIntoValueError {
    TooManyItems {
        len: usize,
        error: std::num::TryFromIntError,
    },
}

impl std::fmt::Display for TableIntoValueError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Self::TooManyItems { len, .. } => {
                write!(f, "there are too many table items in table of len {len}")
            }
        }
    }
}

impl std::error::Error for TableIntoValueError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::TooManyItems { error, .. } => Some(error),
            // _ => None,
        }
    }
}

const HEADER_SIZE: usize = 4 * 5;

const USER_DEFINED_NAME: &[u8] = b"Table";

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Table {
    pub dimensions: i32,
    pub x_size: i32,
    pub y_size: i32,
    pub z_size: i32,
    pub items: Vec<i16>,
}

impl<'a> FromValue<'a> for Table {
    fn from_value(ctx: &FromValueContext<'a>, value: &Value) -> Result<Self, FromValueError> {
        let user_defined: &UserDefinedValue = FromValue::from_value(ctx, value)?;
        let name = user_defined.name();
        let name: &SymbolValue = ctx.from_value(name.into())?;
        let name = name.value();

        if name != USER_DEFINED_NAME {
            return Err(FromValueError::UnexpectedObjectName { name: name.into() });
        }

        let value = user_defined.value();

        let value_len = value.len();
        if value_len < HEADER_SIZE {
            return Err(FromValueError::new_other(TableFromValueError::TooShort {
                len: value_len,
            }));
        }
        if value_len % 2 != 0 {
            return Err(FromValueError::new_other(
                TableFromValueError::OddSizedPayload { len: value_len },
            ));
        }

        let (dimensions, value) = value.split_at(4);
        let dimensions = i32::from_le_bytes(dimensions.try_into().unwrap());

        let (x_size, value) = value.split_at(4);
        let x_size = i32::from_le_bytes(x_size.try_into().unwrap());

        let (y_size, value) = value.split_at(4);
        let y_size = i32::from_le_bytes(y_size.try_into().unwrap());

        let (z_size, value) = value.split_at(4);
        let z_size = i32::from_le_bytes(z_size.try_into().unwrap());

        let (size, value) = value.split_at(4);
        let size = i32::from_le_bytes(size.try_into().unwrap());

        let value_len = value.len();
        if i32::try_from(value_len)
            .ok()
            .map_or(true, |value_len| value_len != 2 * size)
        {
            return Err(FromValueError::new_other(
                TableFromValueError::ItemSizeMismatch {
                    expected: 2 * size,
                    actual: value_len,
                },
            ));
        }

        let items_len = usize::try_from(size).unwrap();
        let mut items = Vec::with_capacity(items_len);
        for chunk in value.chunks(2) {
            items.push(i16::from_le_bytes(chunk.try_into().unwrap()));
        }

        Ok(Self {
            dimensions,
            x_size,
            y_size,
            z_size,
            items,
        })
    }
}

impl IntoValue for Table {
    fn into_value(self, arena: &mut ValueArena) -> Result<ValueHandle, IntoValueError> {
        let name = arena.create_symbol(USER_DEFINED_NAME.into());
        let mut value = Vec::with_capacity(HEADER_SIZE + self.items.len());

        value.extend(self.dimensions.to_le_bytes());
        value.extend(self.x_size.to_le_bytes());
        value.extend(self.y_size.to_le_bytes());
        value.extend(self.z_size.to_le_bytes());

        let items_len = self.items.len();
        let size = i32::try_from(items_len).map_err(|error| {
            IntoValueError::new_other(TableIntoValueError::TooManyItems {
                len: items_len,
                error,
            })
        })?;
        value.extend(size.to_le_bytes());

        for item in self.items.iter() {
            value.extend(item.to_le_bytes());
        }

        let user_defined = arena.create_user_defined(name, value);
        Ok(user_defined.into())
    }
}