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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/*
    This file is part of Donet.

    Copyright © 2024 Max Rodriguez

    Donet is free software; you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License,
    as published by the Free Software Foundation, either version 3
    of the License, or (at your option) any later version.

    Donet is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public
    License along with Donet. If not, see <https://www.gnu.org/licenses/>.
*/

//! Structure representing data types supported in the DC
//! language and enforcing numeric limits through constraints.

use crate::datagram::datagram::{Datagram, DatagramIterator};
use crate::dctype::*;
use crate::hashgen::DCHashGenerator;
use std::mem::size_of;

/// Numeric Range structs are used to represent a range of signed/unsigned
/// integers or floating point numbers. Used for enforcing numeric limits
/// within constraints of array, string, or blob sized types.
#[derive(Clone)]
pub struct DCNumericRange {
    range_type: DCNumberType,
    pub min: DCNumber,
    pub max: DCNumber,
}

impl Default for DCNumericRange {
    fn default() -> Self {
        let mut default_min: DCNumber = DCNumber::new_floating_point(f64::NEG_INFINITY);
        let mut default_max: DCNumber = DCNumber::new_floating_point(f64::INFINITY);

        default_min.number_type = DCNumberType::None;
        default_max.number_type = DCNumberType::None;

        Self {
            range_type: DCNumberType::None,
            min: default_min,
            max: default_max,
        }
    }
}

impl DCNumericRange {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn new_integer_range(min: i64, max: i64) -> Self {
        Self {
            range_type: DCNumberType::Int,
            min: DCNumber::new_integer(min),
            max: DCNumber::new_integer(max),
        }
    }

    pub fn new_unsigned_integer_range(min: u64, max: u64) -> Self {
        Self {
            range_type: DCNumberType::UInt,
            min: DCNumber::new_unsigned_integer(min),
            max: DCNumber::new_unsigned_integer(max),
        }
    }

    pub fn new_floating_point_range(min: f64, max: f64) -> Self {
        Self {
            range_type: DCNumberType::Float,
            min: DCNumber::new_floating_point(min),
            max: DCNumber::new_floating_point(max),
        }
    }

    pub fn contains(&self, num: DCNumber) -> bool {
        match self.min.number_type {
            DCNumberType::None => true,
            DCNumberType::Int => unsafe {
                /* NOTE: All reads of unions require an unsafe block due to potential UB.
                 * As the developer, we have to make sure every read of this union guarantees
                 * that it will contain the expected data type at the point we read.
                 * We make use of the DCNumberType enumerator to guarantee this safety.
                 */
                self.min.value.integer <= num.value.integer && num.value.integer <= self.max.value.integer
            },
            DCNumberType::UInt => unsafe {
                self.min.value.unsigned_integer <= num.value.unsigned_integer
                    && num.value.unsigned_integer <= self.max.value.unsigned_integer
            },
            DCNumberType::Float => unsafe {
                self.min.value.floating_point <= num.value.floating_point
                    && num.value.floating_point <= self.max.value.floating_point
            },
        }
    }

    pub fn is_empty(&self) -> bool {
        self.range_type.is_none() // using strum macro
    }
}

// ---------- Numeric Type ---------- //

pub struct DCNumericType {
    base_type: DCTypeDefinition,
    divisor: u16,
    // These are the original range and modulus values from the file, unscaled by the divisor.
    orig_modulus: f64,
    orig_range: DCNumericRange,
    // These are the range and modulus values after scaling by the divisor.
    modulus: DCNumber,
    range: DCNumericRange,
    // Specific to Donet's DC language
    explicit_cast: Option<DCTypeDefinition>,
}

impl DCNumericType {
    pub fn new(base_type: DCTypeEnum) -> Self {
        Self {
            base_type: {
                let mut parent_struct = DCTypeDefinition::new();
                parent_struct.data_type = base_type;

                macro_rules! set_parent_size {
                    ($t:ty) => {
                        parent_struct.size = size_of::<$t>().try_into().unwrap()
                    };
                }
                match parent_struct.data_type {
                    DCTypeEnum::TChar | DCTypeEnum::TInt8 | DCTypeEnum::TUInt8 => {
                        set_parent_size!(u8)
                    }
                    DCTypeEnum::TInt16 | DCTypeEnum::TUInt16 => {
                        set_parent_size!(u16)
                    }
                    DCTypeEnum::TInt32 | DCTypeEnum::TUInt32 => {
                        set_parent_size!(u32)
                    }
                    DCTypeEnum::TInt64 | DCTypeEnum::TUInt64 => {
                        set_parent_size!(u64)
                    }
                    DCTypeEnum::TFloat32 => {
                        set_parent_size!(f32)
                    }
                    DCTypeEnum::TFloat64 => {
                        set_parent_size!(f64)
                    }
                    _ => parent_struct.data_type = DCTypeEnum::TInvalid,
                }
                parent_struct
            },
            divisor: 1_u16,
            orig_modulus: 0.0_f64,
            orig_range: DCNumericRange::new(),
            modulus: DCNumber::new(),
            range: DCNumericRange::new(),
            explicit_cast: None,
        }
    }

    /// Accumulates the properties of this DC element into the file hash.
    pub fn generate_hash(&self, hashgen: &mut DCHashGenerator) {
        self.base_type.generate_hash(hashgen);
        hashgen.add_int(i32::from(self.divisor));

        if self.has_modulus() {
            // unsafe block required for accessing unions
            unsafe {
                hashgen.add_int(self.modulus.value.integer.try_into().unwrap());
            }
        }
        if self.has_range() {
            unsafe {
                hashgen.add_int(self.range.min.value.integer.try_into().unwrap());
                hashgen.add_int(self.range.max.value.integer.try_into().unwrap());
            }
        }
    }

    #[inline]
    pub fn has_modulus(&self) -> bool {
        self.orig_modulus != 0.0
    }
    #[inline]
    pub fn has_range(&self) -> bool {
        self.orig_range.is_empty()
    }
    #[inline]
    pub fn get_divisor(&self) -> u16 {
        self.divisor
    }
    #[inline]
    pub fn get_modulus(&self) -> f64 {
        self.orig_modulus
    }
    #[inline]
    pub fn get_range(&self) -> DCNumericRange {
        self.orig_range.clone()
    }
    #[inline]
    pub fn get_explicit_cast(&self) -> Option<DCTypeDefinition> {
        self.explicit_cast.clone()
    }

    pub fn set_divisor(&mut self, divisor: u16) -> Result<(), String> {
        if divisor == 0 {
            return Err("Cannot set the divisor to 0.".to_owned());
        }
        self.divisor = divisor;
        if self.has_range() {
            self.set_range(self.orig_range.clone())?;
        }
        if self.has_modulus() {
            self.set_modulus(self.orig_modulus)?;
        }
        Ok(())
    }

    pub fn set_modulus(&mut self, modulus: f64) -> Result<(), String> {
        if modulus <= 0.0_f64 {
            return Err("Modulus value cannot be less than or equal to 0.0.".to_owned());
        }
        self.orig_modulus = modulus;
        self.modulus.value.floating_point = modulus * f64::from(self.divisor);
        Ok(()) // TODO: properly validate modulus range
    }

    pub fn set_range(&mut self, range: DCNumericRange) -> Result<(), String> {
        self.range = range; // TODO: validate
        Ok(())
    }

    pub fn set_explicit_cast(&mut self, dtype: DCTypeDefinition) -> Result<(), String> {
        self.explicit_cast = Some(dtype);
        Ok(()) // TODO: do some sort of type check
    }

    pub fn within_range(&self, _data: Vec<u8>, _length: u64) -> Result<(), String> {
        todo!();
    }

    fn data_to_number(&self, data: Vec<u8>) -> (bool, DCNumber) {
        if self.base_type.size != data.len().try_into().unwrap() {
            return (false, DCNumber::new_integer(0_i64));
        }

        let mut dg = Datagram::new();
        let _ = dg.add_data(data);
        let mut dgi = DatagramIterator::new(dg);

        match self.base_type.data_type {
            DCTypeEnum::TInt8 => (true, DCNumber::new_integer(i64::from(dgi.read_i8()))),
            DCTypeEnum::TInt16 => (true, DCNumber::new_integer(i64::from(dgi.read_i16()))),
            DCTypeEnum::TInt32 => (true, DCNumber::new_integer(i64::from(dgi.read_i32()))),
            DCTypeEnum::TInt64 => (true, DCNumber::new_integer(dgi.read_i64())),
            DCTypeEnum::TChar | DCTypeEnum::TUInt8 => {
                (true, DCNumber::new_unsigned_integer(u64::from(dgi.read_u8())))
            }
            DCTypeEnum::TUInt16 => (true, DCNumber::new_unsigned_integer(u64::from(dgi.read_u16()))),
            DCTypeEnum::TUInt32 => (true, DCNumber::new_unsigned_integer(u64::from(dgi.read_u32()))),
            DCTypeEnum::TUInt64 => (true, DCNumber::new_unsigned_integer(dgi.read_u64())),
            DCTypeEnum::TFloat32 => (true, DCNumber::new_floating_point(f64::from(dgi.read_f32()))),
            DCTypeEnum::TFloat64 => (true, DCNumber::new_floating_point(dgi.read_f64())),
            _ => (false, DCNumber::new_integer(0_i64)),
        }
    }
}