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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
// Strict encoding library for deterministic binary serialization.
//
// SPDX-License-Identifier: Apache-2.0
//
// Written in 2019-2023 by
//     Dr. Maxim Orlovsky <orlovsky@ubideco.org>
//
// Copyright 2022-2023 UBIDECO Institute
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fmt::{self, Display, Formatter, Write};
use std::hash::Hash;

use amplify::Wrapper;

use crate::STRICT_TYPES_LIB;

pub mod constants {
    use super::Primitive;

    pub const U8: Primitive = Primitive::unsigned(1);
    pub const U16: Primitive = Primitive::unsigned(2);
    pub const U24: Primitive = Primitive::unsigned(3);
    pub const U32: Primitive = Primitive::unsigned(4);
    pub const U48: Primitive = Primitive::unsigned(6);
    pub const U64: Primitive = Primitive::unsigned(8);
    pub const U128: Primitive = Primitive::unsigned(16);
    pub const U160: Primitive = Primitive::unsigned(20);
    pub const U256: Primitive = Primitive::unsigned(32);
    pub const U512: Primitive = Primitive::unsigned(64);
    pub const U1024: Primitive = Primitive::unsigned(128);

    pub const I8: Primitive = Primitive::signed(1);
    pub const I16: Primitive = Primitive::signed(2);
    pub const I24: Primitive = Primitive::signed(3);
    pub const I32: Primitive = Primitive::signed(4);
    pub const I48: Primitive = Primitive::signed(6);
    pub const I64: Primitive = Primitive::signed(8);
    pub const I128: Primitive = Primitive::signed(16);
    pub const I256: Primitive = Primitive::signed(32);
    pub const I512: Primitive = Primitive::signed(64);
    pub const I1024: Primitive = Primitive::signed(128);

    pub const N8: Primitive = Primitive::non_zero(1);
    pub const N16: Primitive = Primitive::non_zero(2);
    pub const N24: Primitive = Primitive::non_zero(3);
    pub const N32: Primitive = Primitive::non_zero(4);
    pub const N48: Primitive = Primitive::non_zero(6);
    pub const N64: Primitive = Primitive::non_zero(8);
    pub const N128: Primitive = Primitive::non_zero(16);

    pub const F16: Primitive = Primitive::float(2);
    pub const F32: Primitive = Primitive::float(4);
    pub const F64: Primitive = Primitive::float(8);
    pub const F80: Primitive = Primitive::float(10);
    pub const F128: Primitive = Primitive::float(16);
    pub const F256: Primitive = Primitive::float(32);

    pub const UNIT: Primitive = Primitive(0x00);
    pub const BYTE: Primitive = Primitive(0x40);
    pub const RESERVED: Primitive = Primitive(0x80);
    pub const F16B: Primitive = Primitive(0xC0);

    pub const FLOAT_RESERVED_1: Primitive = Primitive(0xC1);
    pub const FLOAT_RESERVED_2: Primitive = Primitive(0xC3);
    pub const FLOAT_RESERVED_3: Primitive = Primitive(0xC5);
    pub const FLOAT_RESERVED_4: Primitive = Primitive(0xC6);
    pub const FLOAT_RESERVED_5: Primitive = Primitive(0xC7);
    pub const FLOAT_RESERVED_6: Primitive = Primitive(0xC9);
    pub const FLOAT_RESERVED_7: Primitive = Primitive(0xCB);
    pub const FLOAT_RESERVED_8: Primitive = Primitive(0xCC);
    pub const FLOAT_RESERVED_9: Primitive = Primitive(0xCD);
    pub const FLOAT_RESERVED_10: Primitive = Primitive(0xCE);
    pub const FLOAT_RESERVED_11: Primitive = Primitive(0xCF);
    pub const FLOAT_RESERVED_12: Primitive = Primitive(0xD1);
    pub const FLOAT_RESERVED_13: Primitive = Primitive(0xD2);
    pub const FLOAT_RESERVED_14: Primitive = Primitive(0xD3);
    pub const FLOAT_RESERVED_15: Primitive = Primitive(0xD4);
    pub const FLOAT_RESERVED_16: Primitive = Primitive(0xD5);
    pub const FLOAT_RESERVED_17: Primitive = Primitive(0xD6);
    pub const FLOAT_RESERVED_18: Primitive = Primitive(0xD7);
    pub const FLOAT_RESERVED_19: Primitive = Primitive(0xD8);
    pub const FLOAT_RESERVED_20: Primitive = Primitive(0xD9);
    pub const FLOAT_RESERVED_21: Primitive = Primitive(0xDA);
    pub const FLOAT_RESERVED_22: Primitive = Primitive(0xDB);
    pub const FLOAT_RESERVED_23: Primitive = Primitive(0xDC);
    pub const FLOAT_RESERVED_24: Primitive = Primitive(0xDE);
    pub const FLOAT_RESERVED_25: Primitive = Primitive(0xDF);

    pub const FLOAT_RESERVED_26: Primitive = Primitive(0xE1);
    pub const FLOAT_RESERVED_27: Primitive = Primitive(0xE2);
    pub const FLOAT_RESERVED_28: Primitive = Primitive(0xE3);
    pub const FLOAT_RESERVED_29: Primitive = Primitive(0xE4);
    pub const FLOAT_RESERVED_30: Primitive = Primitive(0xE5);
    pub const FLOAT_RESERVED_31: Primitive = Primitive(0xE6);
    pub const FLOAT_RESERVED_32: Primitive = Primitive(0xE7);
    pub const FLOAT_RESERVED_33: Primitive = Primitive(0xE8);
    pub const FLOAT_RESERVED_34: Primitive = Primitive(0xE9);
    pub const FLOAT_RESERVED_35: Primitive = Primitive(0xEA);
    pub const FLOAT_RESERVED_36: Primitive = Primitive(0xEB);
    pub const FLOAT_RESERVED_37: Primitive = Primitive(0xEC);
    pub const FLOAT_RESERVED_38: Primitive = Primitive(0xEE);
    pub const FLOAT_RESERVED_39: Primitive = Primitive(0xEF);

    pub const FLOAT_RESERVED_40: Primitive = Primitive(0xF0);
    pub const FLOAT_RESERVED_41: Primitive = Primitive(0xF1);
    pub const FLOAT_RESERVED_42: Primitive = Primitive(0xF2);
    pub const FLOAT_RESERVED_43: Primitive = Primitive(0xF3);
    pub const FLOAT_RESERVED_44: Primitive = Primitive(0xF4);
    pub const FLOAT_RESERVED_45: Primitive = Primitive(0xF5);
    pub const FLOAT_RESERVED_46: Primitive = Primitive(0xF6);
    pub const FLOAT_RESERVED_47: Primitive = Primitive(0xF7);
    pub const FLOAT_RESERVED_48: Primitive = Primitive(0xF8);
    pub const FLOAT_RESERVED_49: Primitive = Primitive(0xF9);
    pub const FLOAT_RESERVED_50: Primitive = Primitive(0xFA);
    pub const FLOAT_RESERVED_51: Primitive = Primitive(0xFB);
    pub const FLOAT_RESERVED_52: Primitive = Primitive(0xFC);
    pub const FLOAT_RESERVED_53: Primitive = Primitive(0xFE);
    pub const FLOAT_RESERVED_54: Primitive = Primitive(0xFF);
}
use self::constants::*;

#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub struct Primitive(u8);

impl Primitive {
    pub const fn unsigned(bytes: u16) -> Self {
        Primitive(
            NumInfo {
                ty: NumCls::Unsigned,
                size: NumSize::from_bytes(bytes),
            }
            .into_code(),
        )
    }

    pub const fn signed(bytes: u16) -> Self {
        Primitive(
            NumInfo {
                ty: NumCls::Signed,
                size: NumSize::from_bytes(bytes),
            }
            .into_code(),
        )
    }

    pub const fn non_zero(bytes: u16) -> Self {
        Primitive(
            NumInfo {
                ty: NumCls::NonZero,
                size: NumSize::from_bytes(bytes),
            }
            .into_code(),
        )
    }

    pub const fn float(bytes: u16) -> Self {
        Primitive(
            NumInfo {
                ty: NumCls::Float,
                size: NumSize::from_bytes(bytes),
            }
            .into_code(),
        )
    }

    pub const fn from_code(code: u8) -> Self { Primitive(code) }
    pub const fn into_code(self) -> u8 { self.0 }

    pub const fn info(self) -> NumInfo { NumInfo::from_code(self.0) }

    pub const fn byte_size(self) -> u16 { self.info().byte_size() }
}

impl Display for Primitive {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match *self {
            UNIT => return f.write_str("()"),
            BYTE => return f.write_str("Byte"),
            F16B => return f.write_str("F16b"),
            RESERVED => unreachable!("reserved primitive value"),
            _ => {}
        }

        let info = self.info();
        f.write_char(match info.ty {
            NumCls::Unsigned => 'U',
            NumCls::Signed => 'I',
            NumCls::NonZero => 'N',
            NumCls::Float => 'F',
        })?;

        write!(f, "{}", info.byte_size() * 8)
    }
}

impl_strict_newtype!(Primitive, STRICT_TYPES_LIB);

/// Information about numeric type
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct NumInfo {
    /// Class of the number
    pub ty: NumCls,
    /// Size of the number, in bytes
    pub size: NumSize,
}

impl NumInfo {
    pub const fn from_code(id: u8) -> Self {
        NumInfo {
            ty: NumCls::from_code(id),
            size: NumSize::from_code(id),
        }
    }

    pub const fn into_code(self) -> u8 { self.ty.into_code() | self.size.into_code() }

    pub const fn byte_size(self) -> u16 { self.size.byte_size() }
}

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct NumSize(NumSizeInner);

impl NumSize {
    pub(super) const fn from_bytes(bytes: u16) -> Self {
        NumSize(if bytes < 0x20 {
            NumSizeInner::Bytes(bytes as u8)
        } else if bytes % 16 != 0 {
            unreachable!()
        } else {
            NumSizeInner::Factored((bytes / 16 - 2) as u8)
        })
    }

    pub(super) const fn from_code(id: u8) -> Self {
        let code = id & 0x1F;
        NumSize(match (id & 0x20) / 0x20 {
            0 => NumSizeInner::Bytes(code),
            1 => NumSizeInner::Factored(code),
            _ => unreachable!(),
        })
    }

    pub(super) const fn into_code(self) -> u8 {
        match self.0 {
            NumSizeInner::Bytes(bytes) => bytes,
            NumSizeInner::Factored(factor) => factor | 0x20,
        }
    }

    pub const fn byte_size(self) -> u16 {
        match self.0 {
            NumSizeInner::Bytes(bytes) => bytes as u16,
            NumSizeInner::Factored(factor) => 2 * (factor as u16 + 1),
        }
    }
}

/// The way how the size is computed and encoded in the type id
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum NumSizeInner {
    /// Lowest 5 bits contain type size in bytes
    Bytes(u8),
    /// Lowest 5 bits contain a factor defining the size according to the
    /// equation `16 * (2 + factor)`
    // TODO: Ensure that U256 doesn't have two encodings with both variants
    Factored(u8),
}

/// Class of the number type
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum NumCls {
    Unsigned = 0x00,
    Signed = 0x40,
    NonZero = 0x80,
    Float = 0xC0,
}

impl NumCls {
    pub const fn from_code(id: u8) -> Self {
        match id & 0xC0 {
            x if x == NumCls::Unsigned as u8 => NumCls::Unsigned,
            x if x == NumCls::Signed as u8 => NumCls::Signed,
            x if x == NumCls::NonZero as u8 => NumCls::NonZero,
            x if x == NumCls::Float as u8 => NumCls::Float,
            _ => unreachable!(),
        }
    }

    pub const fn into_code(self) -> u8 { self as u8 }
}

#[cfg(test)]
mod test {
    use super::constants::U8;

    #[test]
    fn u8() {
        let prim = U8;
        assert_eq!(prim.byte_size(), 1);
    }
}