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
use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable};
use azalea_nbt::Nbt;
use std::io::{Cursor, Write};

/// Either an item in an inventory or nothing.
#[derive(Debug, Clone, Default, PartialEq)]
pub enum ItemSlot {
    #[default]
    Empty,
    Present(ItemSlotData),
}

impl ItemSlot {
    /// Check if the slot is ItemSlot::Empty, if the count is <= 0, or if the
    /// item is air.
    ///
    /// This is the opposite of [`ItemSlot::is_present`].
    pub fn is_empty(&self) -> bool {
        match self {
            ItemSlot::Empty => true,
            ItemSlot::Present(item) => item.is_empty(),
        }
    }
    /// Check if the slot is not ItemSlot::Empty, if the count is > 0, and if
    /// the item is not air.
    ///
    /// This is the opposite of [`ItemSlot::is_empty`].
    pub fn is_present(&self) -> bool {
        !self.is_empty()
    }

    /// Return the amount of the item in the slot, or 0 if the slot is empty.
    ///
    /// Note that it's possible for the count to be zero or negative when the
    /// slot is present.
    pub fn count(&self) -> i8 {
        match self {
            ItemSlot::Empty => 0,
            ItemSlot::Present(i) => i.count,
        }
    }

    /// Remove `count` items from this slot, returning the removed items.
    pub fn split(&mut self, count: u8) -> ItemSlot {
        match self {
            ItemSlot::Empty => ItemSlot::Empty,
            ItemSlot::Present(i) => {
                let returning = i.split(count);
                if i.is_empty() {
                    *self = ItemSlot::Empty;
                }
                ItemSlot::Present(returning)
            }
        }
    }

    /// Get the `kind` of the item in this slot, or
    /// [`azalea_registry::Item::Air`]
    pub fn kind(&self) -> azalea_registry::Item {
        match self {
            ItemSlot::Empty => azalea_registry::Item::Air,
            ItemSlot::Present(i) => i.kind,
        }
    }

    /// Update whether this slot is empty, based on the count.
    pub fn update_empty(&mut self) {
        if let ItemSlot::Present(i) = self {
            if i.is_empty() {
                *self = ItemSlot::Empty;
            }
        }
    }
}

/// An item in an inventory, with a count and NBT. Usually you want [`ItemSlot`]
/// or [`azalea_registry::Item`] instead.
#[derive(Debug, Clone, McBuf, PartialEq)]
pub struct ItemSlotData {
    pub kind: azalea_registry::Item,
    /// The amount of the item in this slot.
    ///
    /// The count can be zero or negative, but this is rare.
    pub count: i8,
    pub nbt: Nbt,
}

impl ItemSlotData {
    /// Remove `count` items from this slot, returning the removed items.
    pub fn split(&mut self, count: u8) -> ItemSlotData {
        let returning_count = i8::min(count as i8, self.count);
        let mut returning = self.clone();
        returning.count = returning_count;
        self.count -= returning_count;
        returning
    }

    /// Check if the count of the item is <= 0 or if the item is air.
    pub fn is_empty(&self) -> bool {
        self.count <= 0 || self.kind == azalea_registry::Item::Air
    }

    /// Whether this item is the same as another item, ignoring the count.
    ///
    /// ```
    /// # use azalea_inventory::ItemSlotData;
    /// # use azalea_registry::Item;
    /// let mut a = ItemSlotData {
    ///    kind: Item::Stone,
    ///    count: 1,
    ///    nbt: Default::default(),
    /// };
    /// let mut b = ItemSlotData {
    ///   kind: Item::Stone,
    ///   count: 2,
    ///   nbt: Default::default(),
    /// };
    /// assert!(a.is_same_item_and_nbt(&b));
    ///
    /// b.kind = Item::Dirt;
    /// assert!(!a.is_same_item_and_nbt(&b));
    /// ```
    pub fn is_same_item_and_nbt(&self, other: &ItemSlotData) -> bool {
        self.kind == other.kind && self.nbt == other.nbt
    }
}

impl McBufReadable for ItemSlot {
    fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
        let slot = Option::<ItemSlotData>::read_from(buf)?;
        Ok(slot.map_or(ItemSlot::Empty, ItemSlot::Present))
    }
}

impl McBufWritable for ItemSlot {
    fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
        match self {
            ItemSlot::Empty => false.write_into(buf)?,
            ItemSlot::Present(i) => {
                true.write_into(buf)?;
                i.write_into(buf)?;
            }
        };
        Ok(())
    }
}