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
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
use crate::std;

use crate::{
    error::{Error, Result},
    impl_default, impl_extended_ops, impl_message_ops, impl_omnibus_extended_command,
    len::SET_EXTENDED_NOTE_INHIBITS_BASE,
    std::fmt,
    ExtendedCommand, ExtendedCommandOps, ExtendedNoteReporting, MessageOps, MessageType,
    OmnibusCommandOps,
};

/// CFSC device extended note enable byte length, see section 7.5.3
pub const CFSC_ENABLE_LEN: usize = 8;
/// SC device extended note enable byte length, see section 7.5.3
pub const SC_ENABLE_LEN: usize = 19;

mod bitmask {
    pub const ENABLE_NOTE: u8 = 0b111_1111;
}

pub mod index {
    pub const ENABLE_NOTE: usize = 7;
}

bitfield! {
    /// Represents enabled notes in the extended note table.
    #[derive(Clone, Copy, Debug, Default, PartialEq)]
    pub struct EnableNote(u16);
    u16;
    pub note1, set_note1: 0;
    pub note2, set_note2: 1;
    pub note3, set_note3: 2;
    pub note4, set_note4: 3;
    pub note5, set_note5: 4;
    pub note6, set_note6: 5;
    pub note7, set_note7: 6;
    pub note_index, set_note_index: 15, 7;
}

impl EnableNote {
    pub const LEN: usize = 7;

    /// Creates an [EnableNote] with no bits set.
    pub const fn none() -> Self {
        Self(0)
    }

    /// Creates an [EnableNote] with all bits set.
    pub const fn all() -> Self {
        Self(bitmask::ENABLE_NOTE as u16)
    }

    /// Get the length of the [EnableNote] bitfield.
    pub const fn len() -> usize {
        Self::LEN
    }

    /// Sets an index to enable.
    ///
    /// Valid range is [1, 7] (inclusive).
    pub fn set_index(&mut self, index: usize) -> Result<()> {
        match index {
            1 => self.set_note1(true),
            2 => self.set_note2(true),
            3 => self.set_note3(true),
            4 => self.set_note4(true),
            5 => self.set_note5(true),
            6 => self.set_note6(true),
            7 => self.set_note7(true),
            _ => return Err(Error::failure("invalid enable index")),
        }
        Ok(())
    }
}

impl From<&[bool]> for EnableNote {
    fn from(b: &[bool]) -> Self {
        let mut inner = 0u16;
        // only allow a max of
        let end = std::cmp::min(b.len(), Self::len());
        for (i, &set) in b[..end].iter().enumerate() {
            let bit = if set { 1 } else { 0 };
            inner |= bit << i;
        }
        Self(inner)
    }
}

impl<const N: usize> From<&[bool; N]> for EnableNote {
    fn from(b: &[bool; N]) -> Self {
        b.as_ref().into()
    }
}

impl<const N: usize> From<[bool; N]> for EnableNote {
    fn from(b: [bool; N]) -> Self {
        (&b).into()
    }
}

impl From<u8> for EnableNote {
    fn from(b: u8) -> Self {
        Self((b & bitmask::ENABLE_NOTE) as u16)
    }
}

impl From<&EnableNote> for u8 {
    fn from(e: &EnableNote) -> u8 {
        (e.0 & 0xff) as u8
    }
}

impl From<EnableNote> for u8 {
    fn from(e: EnableNote) -> u8 {
        (&e).into()
    }
}

impl fmt::Display for EnableNote {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let idx = self.note_index();

        write!(f, "{{")?;
        write!(f, r#""note_{idx}": {}, "#, self.note1())?;
        write!(f, r#""note_{}": {}, "#, idx.saturating_add(1), self.note2())?;
        write!(f, r#""note_{}": {}, "#, idx.saturating_add(2), self.note3())?;
        write!(f, r#""note_{}": {}, "#, idx.saturating_add(3), self.note4())?;
        write!(f, r#""note_{}": {}, "#, idx.saturating_add(4), self.note5())?;
        write!(f, r#""note_{}": {}, "#, idx.saturating_add(5), self.note6())?;
        write!(f, r#""note_{}": {}"#, idx.saturating_add(6), self.note7())?;
        write!(f, "}}")
    }
}

/// Set Extended Note Inhibits - Request (Subtype 0x03)
///
/// This command is used to control the acceptance of bank notes on a note type basis. It is only used when
/// the device is running in extended note mode (section 4.2.2).
///
/// The generic parameter is the sum of
/// [SET_EXTENDED_NOTE_INHIBITS_BASE](crate::len::SET_EXTENDED_NOTE_INHIBITS_BASE),
/// and the number of enable note bytes (either [CFSC_ENABLE_LEN] or [SC_ENABLE_LEN]).
///
/// The Set Extended Note Inhibits is formatted as follows:
///
/// | Name  | STX  | LEN  | CTRL | Subtype | Data 0 | Data 1 | Data 2 | Enable 1 | ...    | Enable N | ETX    | CHK    |
/// |:------|:----:|:----:|:----:|:-------:|:------:|:------:|:------:|:--------:|:------:|:--------:|:------:|:------:|
/// | Byte  | 0    | 1    | 2    | 3       | 4      | 5      | 6      | 7        | ...    | LL - 3   | LL - 2 | LL - 1 |
/// | Value | 0x02 | LL   | 0x7n | 0x03    | nn     | nn     | nn     | nn       | nn     | nn       | 0x03   | zz     |
///
/// | **CFSC** |
/// |:--------:|
///
/// Supports up to 50 denomination types, therefore the command requires 8 extended data bytes.
///
/// This will make the message length 0x11:
///
/// | Byte     | Bit 6   | Bit 5   | Bit 4   | Bit 3   | Bit 2   | Bit 1   | Bit 0   |
/// |:---------|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
/// | Enable 1 | Note 7  | Note 6  | Note 5  | Note 4  | Note 3  | Note 2  | Note 1  |
/// | Enable 2 | Note 14 | Note 13 | Note 12 | Note 11 | Note 10 | Note 9  | Note 8  |
/// | Enable 3 | Note 21 | Note 20 | Note 19 | Note 18 | Note 17 | Note 16 | Note 15 |
/// | Enable 4 | Note 28 | Note 27 | Note 26 | Note 25 | Note 24 | Note 23 | Note 22 |
/// | Enable 5 | Note 35 | Note 34 | Note 33 | Note 32 | Note 31 | Note 30 | Note 29 |
/// | Enable 6 | Note 42 | Note 41 | Note 40 | Note 39 | Note 38 | Note 37 | Note 36 |
/// | Enable 7 | Note 49 | Note 48 | Note 47 | Note 46 | Note 45 | Note 44 | Note 43 |
/// | Enable 8 | -       | -       | -       | -       | -       | -       | Note 50 |
///
/// | **SC Adv** | **SCR** |
/// |:----------:|:-------:|
///
/// Supports up to 128 denomination types, therefore the command requires 19 extended
/// data bytes.
///
/// This will make the message length 0x1C:
///
/// | Byte      | Bit 6    | Bit 5    | Bit 4    | Bit 3    | Bit 2    | Bit 1    | Bit 0    |
/// |:----------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|
/// | Enable 1  | Note 7   | Note 6   | Note 5   | Note 4   | Note 3   | Note 2   | Note 1   |
/// | Enable 2  | Note 14  | Note 13  | Note 12  | Note 11  | Note 10  | Note 9   | Note 8   |
/// | Enable 3  | Note 21  | Note 20  | Note 19  | Note 18  | Note 17  | Note 16  | Note 15  |
/// | Enable 4  | Note 28  | Note 27  | Note 26  | Note 25  | Note 24  | Note 23  | Note 22  |
/// | Enable 5  | Note 35  | Note 34  | Note 33  | Note 32  | Note 31  | Note 30  | Note 29  |
/// | Enable 6  | Note 42  | Note 41  | Note 40  | Note 39  | Note 38  | Note 37  | Note 36  |
/// | Enable 7  | Note 49  | Note 48  | Note 47  | Note 46  | Note 45  | Note 44  | Note 43  |
/// | Enable 8  | Note 56  | Note 55  | Note 54  | Note 53  | Note 52  | Note 51  | Note 50  |
/// | Enable 9  | Note 63  | Note 62  | Note 61  | Note 60  | Note 59  | Note 58  | Note 57  |
/// | Enable 10 | Note 70  | Note 69  | Note 68  | Note 67  | Note 66  | Note 65  | Note 64  |
/// | Enable 11 | Note 77  | Note 76  | Note 75  | Note 74  | Note 73  | Note 72  | Note 71  |
/// | Enable 12 | Note 84  | Note 83  | Note 82  | Note 81  | Note 80  | Note 79  | Note 78  |
/// | Enable 13 | Note 91  | Note 90  | Note 89  | Note 88  | Note 87  | Note 86  | Note 85  |
/// | Enable 14 | Note 98  | Note 97  | Note 98  | Note 97  | Note 96  | Note 95  | Note 94  |
/// | Enable 15 | Note 105 | Note 104 | Note 103 | Note 102 | Note 101 | Note 100 | Note 99  |
/// | Enable 16 | Note 112 | Note 111 | Note 110 | Note 109 | Note 108 | Note 107 | Note 106 |
/// | Enable 17 | Note 119 | Note 118 | Note 117 | Note 116 | Note 115 | Note 114 | Note 113 |
/// | Enable 18 | Note 126 | Note 125 | Note 124 | Note 123 | Note 122 | Note 121 | Note 120 |
/// | Enable 19 | -        | -        | -        | -        | -        | Note 128 | Note 127 |
///
/// If the bit equals 1 then the note is enabled.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SetExtendedNoteInhibits<const M: usize, const N: usize> {
    buf: [u8; M],
}

impl<const M: usize, const N: usize> SetExtendedNoteInhibits<M, N> {
    /// The length of enable note bytes (N - [SET_EXTENDED_NOTE_INHIBITS_BASE]).
    pub const ENABLE_NOTE_LEN: usize = N;

    /// Creates a new [SetExtendedNoteInhibits] message.
    pub fn new() -> Self {
        assert!(
            M == SET_EXTENDED_NOTE_INHIBITS_BASE + CFSC_ENABLE_LEN
                || M == SET_EXTENDED_NOTE_INHIBITS_BASE + SC_ENABLE_LEN
        );

        let mut message = Self { buf: [0u8; M] };

        message.init();
        message.set_message_type(MessageType::Extended);
        message.set_extended_note(ExtendedNoteReporting::Set);
        message.set_extended_command(ExtendedCommand::SetExtendedNoteInhibits);

        message
    }

    /// Get the table of enabled note bytes.
    pub fn enabled_notes(&self) -> [EnableNote; N] {
        let mut ret = [EnableNote::none(); N];

        for (&note, set_note) in self.buf
            [index::ENABLE_NOTE..index::ENABLE_NOTE + Self::ENABLE_NOTE_LEN]
            .iter()
            .zip(ret.iter_mut())
        {
            *set_note = EnableNote::from(note);
        }

        ret
    }

    /// Sets the enable note bytes
    ///
    /// Example: `notes[0]` sets `Enable 1`, `notes[1]` sets `Enable 2` etc.
    ///
    /// Note: maximum of [ENABLE_NOTE_LEN](Self::ENABLE_NOTE_LEN) [EnableNote]s can be set, any extra supplied are ignored.
    pub fn set_enabled_notes(&mut self, notes: &[EnableNote]) {
        let max_len = std::cmp::min(notes.len(), Self::ENABLE_NOTE_LEN);

        for (i, note) in notes[..max_len].iter().enumerate() {
            self.buf[index::ENABLE_NOTE + i] = note.into();
        }
    }
}

impl<const M: usize, const N: usize> fmt::Display for SetExtendedNoteInhibits<M, N> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{{")?;
        write!(f, r#""message_type": {}, "#, self.message_type())?;
        write!(f, r#""extended_command": {}, "#, self.extended_command())?;
        write!(f, r#""denomination": {}, "#, self.denomination())?;
        write!(f, r#""operational_mode": {}, "#, self.operational_mode())?;
        write!(f, r#""configuration": {}, "#, self.configuration())?;
        write!(f, r#""enabled_notes": ["#)?;

        let mut notes = self.enabled_notes();
        for (i, note) in notes.iter_mut().enumerate() {
            if i != 0 {
                write!(f, ", ")?;
            }
            note.set_note_index(((i * 7) + 1) as u16);
            write!(f, "{note}")?;
        }

        write!(f, "]}}")
    }
}

pub const CFSC_ENABLE_FULL_LEN: usize = SET_EXTENDED_NOTE_INHIBITS_BASE + CFSC_ENABLE_LEN;
pub const SC_ENABLE_FULL_LEN: usize = SET_EXTENDED_NOTE_INHIBITS_BASE + SC_ENABLE_LEN;

pub type SetExtendedNoteInhibitsCFSC =
    SetExtendedNoteInhibits<CFSC_ENABLE_FULL_LEN, CFSC_ENABLE_LEN>;
pub type SetExtendedNoteInhibitsSC = SetExtendedNoteInhibits<SC_ENABLE_FULL_LEN, SC_ENABLE_LEN>;

impl_default!(SetExtendedNoteInhibits, M, N);
impl_message_ops!(SetExtendedNoteInhibits, M, N);
impl_extended_ops!(SetExtendedNoteInhibits, M, N);
impl_omnibus_extended_command!(SetExtendedNoteInhibits, M, N);

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Result;

    #[test]
    #[rustfmt::skip]
    fn test_query_set_extended_note_inhibits_from_bytes() -> Result<()> {

        // CFSC note table
        let msg_bytes = [
            // STX | LEN | Message type | Subtype
            0x02, 0x11, 0x70, 0x03,
            // Data
            0x00, 0x00, 0x00,
            // Enable
            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            // ETX | Checksum
            0x03, 0x63,
        ];

        let mut msg = SetExtendedNoteInhibitsCFSC::new();
        msg.from_buf(msg_bytes.as_ref())?;

        assert_eq!(msg.message_type(), MessageType::Extended);
        assert_eq!(msg.extended_command(), ExtendedCommand::SetExtendedNoteInhibits);

        let exp_enabled = [
            EnableNote::from(1), EnableNote::none(), EnableNote::none(), EnableNote::none(),
            EnableNote::none(), EnableNote::none(), EnableNote::none(),  EnableNote::none(),
        ];

        assert_eq!(msg.enabled_notes(), exp_enabled);

        // SC note table
        let msg_bytes = [
            // STX | LEN | Message type | Subtype
            0x02, 0x1c, 0x70, 0x03,
            // Data
            0x00, 0x00, 0x00,
            // Enable
            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00,
            // ETX | Checksum
            0x03, 0x6e,
        ];

        let mut msg = SetExtendedNoteInhibitsSC::new();
        msg.from_buf(msg_bytes.as_ref())?;

        assert_eq!(msg.message_type(), MessageType::Extended);
        assert_eq!(msg.extended_command(), ExtendedCommand::SetExtendedNoteInhibits);

        let exp_enabled = [
            EnableNote::from(1), EnableNote::none(), EnableNote::none(), EnableNote::none(),
            EnableNote::none(), EnableNote::none(), EnableNote::none(),  EnableNote::none(),
            EnableNote::none(), EnableNote::none(), EnableNote::none(),  EnableNote::none(),
            EnableNote::none(), EnableNote::none(), EnableNote::none(),  EnableNote::none(),
            EnableNote::none(), EnableNote::none(), EnableNote::none(),
        ];

        assert_eq!(msg.enabled_notes(), exp_enabled);

        Ok(())
    }

    #[test]
    fn test_display() -> Result<()> {
        let enabled = SetExtendedNoteInhibitsCFSC::new();
        let enabled_disp = r#"{"message_type": "Extended", "extended_command": "SetExtendedNoteInhibits", "denomination": "None", "operational_mode": {"orientation_control": "one way", "escrow_mode": "unset", "document_stack": "unset", "document_return": "unset"}, "configuration": {"no_push": "unset", "barcode": "unset", "power_up": "a", "extended_note": "set", "extended_coupon": "unset"}, "enabled_notes": [{"note_1": false, "note_2": false, "note_3": false, "note_4": false, "note_5": false, "note_6": false, "note_7": false}, {"note_8": false, "note_9": false, "note_10": false, "note_11": false, "note_12": false, "note_13": false, "note_14": false}, {"note_15": false, "note_16": false, "note_17": false, "note_18": false, "note_19": false, "note_20": false, "note_21": false}, {"note_22": false, "note_23": false, "note_24": false, "note_25": false, "note_26": false, "note_27": false, "note_28": false}, {"note_29": false, "note_30": false, "note_31": false, "note_32": false, "note_33": false, "note_34": false, "note_35": false}, {"note_36": false, "note_37": false, "note_38": false, "note_39": false, "note_40": false, "note_41": false, "note_42": false}, {"note_43": false, "note_44": false, "note_45": false, "note_46": false, "note_47": false, "note_48": false, "note_49": false}, {"note_50": false, "note_51": false, "note_52": false, "note_53": false, "note_54": false, "note_55": false, "note_56": false}]}"#;

        assert_eq!(format!("{enabled}").as_str(), enabled_disp);

        Ok(())
    }
}