use anyhow::Result;
use engine::prelude::*;
use paste;

use std::collections::HashMap;

use crate::{
    config::{items::ItemSettings, save_game::SaveGame},
    items::{Item, ItemAffix, ItemSlots, ItemSystem, Rarities},
};

use super::{
    attributes::{Agility, Attribute, Attributes, Intelligence, Strength},
    statistic_types::StatisticType,
};

macro_rules! load_item {
    ($me:ident, $var_name:ident, $slot:ident, $save_game:ident, $item_system:ident) => {
        if $save_game.$var_name.used {
            let attributes = Attributes::load(
                $save_game.$var_name.strength,
                $save_game.$var_name.agility,
                $save_game.$var_name.intelligence,
            );

            let mut $var_name = $item_system.item(
                $save_game.$var_name.rarity,
                ItemSlots::$slot,
                $save_game.$var_name.level,
                attributes,
                $save_game.$var_name.affixes.clone(),
            );

            $var_name.affixes.iter_mut().for_each(|affix| {
                if let ItemAffix::Socket(Some(jewel)) = affix {
                    jewel.icon =
                        Some($item_system.jewel_icon(jewel.rarity, jewel.level, jewel.attribute));
                    jewel.update_stat(&$item_system.item_settings);
                }
            });

            $me.$var_name = Some($var_name);
        }
    };
    ($me:ident, $var_name:ident, $index:expr, $slot:ident, $save_game:ident, $item_system:ident) => {
        paste::expr! {
            if $save_game.[<$var_name _ $index>].used {
                let attributes = Attributes::load(
                    $save_game.[<$var_name _ $index>].strength,
                    $save_game.[<$var_name _ $index>].agility,
                    $save_game.[<$var_name _ $index>].intelligence,
                );

                let mut $var_name = $item_system.item(
                    $save_game.[<$var_name _ $index>].rarity,
                    ItemSlots::$slot,
                    $save_game.[<$var_name _ $index>].level,
                    attributes,
                    $save_game.[<$var_name _ $index>].affixes.clone(),
                );

                $var_name.affixes.iter_mut().for_each(|affix| {
                    if let ItemAffix::Socket(Some(jewel)) = affix {
                        jewel.icon =
                            Some($item_system.jewel_icon(jewel.rarity, jewel.level, jewel.attribute));
                        jewel.update_stat(&$item_system.item_settings);
                    }
                });

                $me.[<$var_name s>][$index] = Some($var_name);
            }
        }
    };
}

macro_rules! store_item {
    ($me:ident, $save_game:ident, $var_name:ident) => {
        match &$me.$var_name {
            Some($var_name) => {
                $save_game.$var_name.used = true;
                $save_game.$var_name.level = $var_name.level;
                $save_game.$var_name.strength = $var_name.attributes.strength().raw();
                $save_game.$var_name.agility = $var_name.attributes.agility().raw();
                $save_game.$var_name.intelligence = $var_name.attributes.intelligence().raw();
                $save_game.$var_name.rarity = $var_name.rarity;
                $save_game.$var_name.affixes = $var_name.affixes.clone();
            }
            None => {
                $save_game.$var_name.used = false;
                $save_game.$var_name.level = 0;
                $save_game.$var_name.strength = 0;
                $save_game.$var_name.agility = 0;
                $save_game.$var_name.intelligence = 0;
                $save_game.$var_name.rarity = Rarities::default();
                $save_game.$var_name.affixes = Vec::new();
            }
        }
    };
    ($me:ident, $save_game:ident, $var_name:ident, $index:expr) => {
        paste::expr! {
            match &$me.[<$var_name s>][$index] {
                Some($var_name) => {
                    $save_game.[<$var_name _ $index>].used = true;
                    $save_game.[<$var_name _ $index>].level = $var_name.level;
                    $save_game.[<$var_name _ $index>].strength = $var_name.attributes.strength().raw();
                    $save_game.[<$var_name _ $index>].agility = $var_name.attributes.agility().raw();
                    $save_game.[<$var_name _ $index>].intelligence = $var_name.attributes.intelligence().raw();
                    $save_game.[<$var_name _ $index>].rarity = $var_name.rarity;
                    $save_game.[<$var_name _ $index>].affixes = $var_name.affixes.clone();
                }
                None => {
                    $save_game.[<$var_name _ $index>].used = false;
                    $save_game.[<$var_name _ $index>].level = 0;
                    $save_game.[<$var_name _ $index>].strength = 0;
                    $save_game.[<$var_name _ $index>].agility = 0;
                    $save_game.[<$var_name _ $index>].intelligence = 0;
                    $save_game.[<$var_name _ $index>].rarity = Rarities::default();
                    $save_game.[<$var_name _ $index>].affixes = Vec::new();
                }
            }
        }
    };
}

macro_rules! setter {
    ($item:ident) => {
        paste::paste! {
            fn [<set_ $item>](&mut self, $item: Option<Item>, multi_mut: &mut MultiMut<'_>) {
                self.$item = $item;

                self.[<apply_ $item _change>](multi_mut).unwrap();
            }
        }
    };
}

pub const RING_COUNT: usize = 4;
pub const AMULET_COUNT: usize = 2;

pub struct ItemSlotContainer {
    // main slots
    helmet: Option<Item>,
    chest_plate: Option<Item>,
    belt: Option<Item>,
    gloves: Option<Item>,
    boots: Option<Item>,

    // hand slots
    main_hand: Option<Item>,
    off_hand: Option<Item>,

    // jewelry slots
    rings: [Option<Item>; RING_COUNT],
    amulets: [Option<Item>; AMULET_COUNT],

    slot_changed_callback: Option<
        Box<dyn Fn(ItemSlots, Rarities, bool, &mut MultiMut<'_>) -> Result<()> + Send + Sync>,
    >,
}

impl ItemSlotContainer {
    pub fn new() -> Self {
        Self {
            helmet: None,
            chest_plate: None,
            belt: None,
            gloves: None,
            boots: None,

            main_hand: None,
            off_hand: None,

            rings: [None, None, None, None],
            amulets: [None, None],

            slot_changed_callback: None,
        }
    }

    pub fn load(save_game: &SaveGame, item_system: &ItemSystem) -> Result<Self> {
        let mut me = Self::new();

        load_item!(me, chest_plate, ChestPlate, save_game, item_system);
        load_item!(me, helmet, Helmet, save_game, item_system);
        load_item!(me, belt, Belt, save_game, item_system);
        load_item!(me, gloves, Gloves, save_game, item_system);
        load_item!(me, boots, Boots, save_game, item_system);

        load_item!(me, main_hand, MainHand, save_game, item_system);
        load_item!(me, off_hand, OffHand, save_game, item_system);

        load_item!(me, ring, 0, Ring, save_game, item_system);
        load_item!(me, ring, 1, Ring, save_game, item_system);
        load_item!(me, ring, 2, Ring, save_game, item_system);
        load_item!(me, ring, 3, Ring, save_game, item_system);

        load_item!(me, amulet, 0, Amulet, save_game, item_system);
        load_item!(me, amulet, 1, Amulet, save_game, item_system);

        Ok(me)
    }

    pub fn store(&self, save_game: &mut SaveGame) {
        store_item!(self, save_game, helmet);
        store_item!(self, save_game, chest_plate);
        store_item!(self, save_game, belt);
        store_item!(self, save_game, gloves);
        store_item!(self, save_game, boots);

        store_item!(self, save_game, main_hand);
        store_item!(self, save_game, off_hand);

        store_item!(self, save_game, ring, 0);
        store_item!(self, save_game, ring, 1);
        store_item!(self, save_game, ring, 2);
        store_item!(self, save_game, ring, 3);

        store_item!(self, save_game, amulet, 0);
        store_item!(self, save_game, amulet, 1);
    }

    pub fn as_vec(&self) -> Vec<&Option<Item>> {
        vec![
            &self.helmet,
            &self.chest_plate,
            &self.belt,
            &self.gloves,
            &self.boots,
            &self.main_hand,
            &self.off_hand,
            &self.rings[0],
            &self.rings[1],
            &self.rings[2],
            &self.rings[3],
            &self.amulets[0],
            &self.amulets[1],
        ]
    }

    fn _create_rarity_color_items(
        item_meshes: HashMap<ItemSlots, AssetMesh>,
        item_settings: &ItemSettings,
    ) -> Result<HashMap<ItemSlots, HashMap<Rarities, AssetMesh>>> {
        let mut slot_meshes = HashMap::new();

        for (slot, mesh) in item_meshes.into_iter() {
            let mut rarity_meshes = HashMap::new();

            for rarity in Rarities::iter() {
                let mut copied_mesh = mesh.separated_copy()?;
                let rarity_color = item_settings.rarity_color_settings.from_rarity(*rarity);

                let buffers = copied_mesh
                    .primitive_types
                    .iter()
                    .map(|primitive| match primitive {
                        PrimitiveDataTypes::PositionNormalColorUVBone(data) => {
                            data.remove_uv_from_buffer()
                        }
                        PrimitiveDataTypes::PositionNormalBone(d) => Ok(d.vertex_buffer.clone()),

                        PrimitiveDataTypes::PositionOnly(_) => todo!(),
                        PrimitiveDataTypes::PositionNormal(_) => todo!(),
                        PrimitiveDataTypes::PositionNormalColor(_) => todo!(),
                        PrimitiveDataTypes::PositionColorUV(_) => todo!(),
                        PrimitiveDataTypes::PositionNormalColorUV(_) => todo!(),
                        PrimitiveDataTypes::PositionNormalColorUVNormalUV(_) => todo!(),
                        PrimitiveDataTypes::PositionNormalColorUVNormalUVBone(_) => todo!(),
                    })
                    .collect::<Result<Vec<_>>>()?;

                copied_mesh.primitive_types.clear();

                buffers.into_iter().try_for_each(|buffer| {
                    copied_mesh.add_primitive(
                        buffer,
                        None,
                        None,
                        PrimitiveMaterial {
                            color: {
                                let c: [f32; 3] = rarity_color.into();
                                [c[0], c[1], c[2], 1.0]
                            },

                            ..Default::default()
                        },
                        true,
                    )
                })?;

                rarity_meshes.insert(*rarity, copied_mesh);
            }

            slot_meshes.insert(slot, rarity_meshes);
        }

        Ok(slot_meshes)
    }

    pub fn set_item_change_callback(
        &mut self,
        _draw: &mut Draw,
        _location: &Location,
    ) -> Result<()> {
        // TODO

        // item_meshes: if query_meshes {
        //     Arc::new(
        //         Self::find_items(draw)
        //             .map({
        //                 let item_settings = item_settings.clone();

        //                 move |item_meshes| {
        //                     Self::create_rarity_color_items(item_meshes, &item_settings)
        //                 }
        //             })
        //             .transpose()?
        //             .unwrap_or_default(),
        //     )
        // } else {
        //     Default::default()
        // },

        // let item_rarity_meshes = self.item_meshes.clone();

        // // only enable items that are equipped
        // self.slot_changed_callback = Some(Box::new(move |slot, rarity, is_some, multi_mut| {
        //     Self::equipment_changed(multi_mut, &item_rarity_meshes, slot, rarity, is_some)
        // }));

        // Self::check_items(draw, location, self, &self.item_meshes)?;

        Ok(())
    }

    setter!(helmet);
    setter!(belt);
    setter!(chest_plate);
    setter!(gloves);
    setter!(boots);
    setter!(main_hand);
    setter!(off_hand);

    fn set_rings(&mut self, ring: Option<Item>, index: usize) {
        self.rings[index] = ring;
    }

    fn set_amulets(&mut self, amulet: Option<Item>, index: usize) {
        self.amulets[index] = amulet;
    }

    fn apply_helmet_change(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        self.apply_item_change(
            ItemSlots::Helmet,
            match &self.helmet {
                Some(helmet) => helmet.rarity,
                None => Rarities::Common,
            },
            self.helmet.is_some(),
            multi_mut,
        )
    }

    fn apply_belt_change(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        self.apply_item_change(
            ItemSlots::Belt,
            match &self.belt {
                Some(belt) => belt.rarity,
                None => Rarities::Common,
            },
            self.belt.is_some(),
            multi_mut,
        )
    }

    fn apply_chest_plate_change(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        self.apply_item_change(
            ItemSlots::ChestPlate,
            match &self.chest_plate {
                Some(chest_plate) => chest_plate.rarity,
                None => Rarities::Common,
            },
            self.chest_plate.is_some(),
            multi_mut,
        )
    }

    fn apply_gloves_change(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        self.apply_item_change(
            ItemSlots::Gloves,
            match &self.gloves {
                Some(gloves) => gloves.rarity,
                None => Rarities::Common,
            },
            self.gloves.is_some(),
            multi_mut,
        )
    }

    fn apply_boots_change(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        self.apply_item_change(
            ItemSlots::Boots,
            match &self.boots {
                Some(boots) => boots.rarity,
                None => Rarities::Common,
            },
            self.boots.is_some(),
            multi_mut,
        )
    }

    fn apply_main_hand_change(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        self.apply_item_change(
            ItemSlots::MainHand,
            match &self.main_hand {
                Some(main_hand) => main_hand.rarity,
                None => Rarities::Common,
            },
            self.main_hand.is_some(),
            multi_mut,
        )
    }

    fn apply_off_hand_change(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        self.apply_item_change(
            ItemSlots::OffHand,
            match &self.off_hand {
                Some(off_hand) => off_hand.rarity,
                None => Rarities::Common,
            },
            self.off_hand.is_some(),
            multi_mut,
        )
    }

    fn apply_item_change(
        &self,
        item_slot: ItemSlots,
        rarity: Rarities,
        is_some: bool,
        multi_mut: &mut MultiMut<'_>,
    ) -> Result<()> {
        if let Some(slot_changed_callback) = &self.slot_changed_callback {
            slot_changed_callback(item_slot, rarity, is_some, multi_mut)?;
        }

        Ok(())
    }
}

// getter
impl ItemSlotContainer {
    pub fn helmet(&self) -> &Option<Item> {
        &self.helmet
    }

    pub fn chest(&self) -> &Option<Item> {
        &self.chest_plate
    }

    pub fn belt(&self) -> &Option<Item> {
        &self.belt
    }

    pub fn gloves(&self) -> &Option<Item> {
        &self.gloves
    }

    pub fn boots(&self) -> &Option<Item> {
        &self.boots
    }

    pub fn primary_hand(&self) -> &Option<Item> {
        &self.main_hand
    }

    pub fn secondary_hand(&self) -> &Option<Item> {
        &self.off_hand
    }

    pub fn ring(&self, index: usize) -> &Option<Item> {
        &self.rings[index]
    }

    pub fn amulet(&self, index: usize) -> &Option<Item> {
        &self.amulets[index]
    }

    pub fn item_mut(&mut self, slot: ItemSlots, index: Option<usize>) -> &mut Option<Item> {
        match slot {
            ItemSlots::Helmet => &mut self.helmet,
            ItemSlots::ChestPlate => &mut self.chest_plate,
            ItemSlots::Belt => &mut self.belt,
            ItemSlots::Gloves => &mut self.gloves,
            ItemSlots::Boots => &mut self.boots,
            ItemSlots::Ring => &mut self.rings[index.unwrap()],
            ItemSlots::Amulet => &mut self.amulets[index.unwrap()],
            ItemSlots::MainHand => &mut self.main_hand,
            ItemSlots::OffHand => &mut self.off_hand,
        }
    }
}

// setter
impl ItemSlotContainer {
    fn _update_helmet(&mut self, helmet: Item, multi_mut: &mut MultiMut<'_>) {
        debug_assert!(helmet.slot == ItemSlots::Helmet);

        self.set_helmet(Some(helmet), multi_mut);
    }

    pub fn unset_helmet(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        if self.helmet.is_some() {
            self.set_helmet(None, multi_mut);
        }

        Ok(())
    }

    fn update_chest(&mut self, chest: Item, multi_mut: &mut MultiMut<'_>) {
        debug_assert!(chest.slot == ItemSlots::ChestPlate);

        self.set_chest_plate(Some(chest), multi_mut);
    }

    pub fn unset_chest(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        if self.chest_plate.is_some() {
            self.set_chest_plate(None, multi_mut);
        }

        Ok(())
    }

    fn _update_belt(&mut self, belt: Item, multi_mut: &mut MultiMut<'_>) {
        debug_assert!(belt.slot == ItemSlots::Belt);

        self.set_belt(Some(belt), multi_mut);
    }

    pub fn unset_belt(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        if self.belt.is_some() {
            self.set_belt(None, multi_mut);
        }

        Ok(())
    }

    fn _update_gloves(&mut self, gloves: Item, multi_mut: &mut MultiMut<'_>) {
        debug_assert!(gloves.slot == ItemSlots::Gloves);

        self.set_gloves(Some(gloves), multi_mut);
    }

    pub fn unset_gloves(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        if self.gloves.is_some() {
            self.set_gloves(None, multi_mut);
        }

        Ok(())
    }

    fn _update_boots(&mut self, boots: Item, multi_mut: &mut MultiMut<'_>) {
        debug_assert!(boots.slot == ItemSlots::Boots);

        self.set_boots(Some(boots), multi_mut);
    }

    pub fn unset_boots(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        if self.boots.is_some() {
            self.set_boots(None, multi_mut);
        }

        Ok(())
    }

    fn update_primary_hand(&mut self, primary_hand: Item, multi_mut: &mut MultiMut<'_>) {
        debug_assert!(primary_hand.slot == ItemSlots::MainHand);

        self.set_main_hand(Some(primary_hand), multi_mut);
    }

    pub fn unset_primary_hand(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        if self.main_hand.is_some() {
            self.set_main_hand(None, multi_mut);
        }

        Ok(())
    }

    fn update_secondary_hand(&mut self, secondary_hand: Item, multi_mut: &mut MultiMut<'_>) {
        debug_assert!(secondary_hand.slot == ItemSlots::OffHand);

        self.set_off_hand(Some(secondary_hand), multi_mut);
    }

    pub fn unset_secondary_hand(&mut self, multi_mut: &mut MultiMut<'_>) -> Result<()> {
        if self.off_hand.is_some() {
            self.set_off_hand(None, multi_mut);
        }

        Ok(())
    }

    pub fn unset_ring(&mut self, index: usize) -> Result<()> {
        if self.rings[index].is_some() {
            self.set_rings(None, index);
        }

        Ok(())
    }

    pub fn unset_amulet(&mut self, index: usize) -> Result<()> {
        if self.amulets[index].is_some() {
            self.set_amulets(None, index);
        }

        Ok(())
    }
}

impl ItemSlotContainer {
    #[inline]
    fn _item(
        item_meshes: &mut HashMap<ItemSlots, AssetMesh>,
        mesh: &AssetMesh,
        name: &str,
        slot: ItemSlots,
    ) {
        if let Some(mesh_name) = &mesh.name {
            if name == mesh_name {
                assert!(item_meshes.insert(slot, mesh.clone()).is_none());
            }
        }
    }

    #[inline]
    fn _find_items(draw: &Draw) -> Option<HashMap<ItemSlots, AssetMesh>> {
        let mut item_meshes = HashMap::new();

        for mesh in draw.iter() {
            Self::_item(&mut item_meshes, mesh, "Helmet", ItemSlots::Helmet);
            Self::_item(&mut item_meshes, mesh, "Shield", ItemSlots::OffHand);
            Self::_item(&mut item_meshes, mesh, "Sword", ItemSlots::MainHand);
            Self::_item(&mut item_meshes, mesh, "ChestPlate", ItemSlots::ChestPlate);
            Self::_item(&mut item_meshes, mesh, "Boots", ItemSlots::Boots);
            Self::_item(&mut item_meshes, mesh, "Gloves", ItemSlots::Gloves);
        }

        if !item_meshes.is_empty() {
            Some(item_meshes)
        } else {
            None
        }
    }

    #[inline]
    fn _undress(draw: &mut Draw, name: &str) {
        draw.remove_by_name(name);
    }

    #[inline]
    fn _dress(
        draw: &mut Draw,
        location: &Location,
        item_meshes: &HashMap<ItemSlots, HashMap<Rarities, AssetMesh>>,
        name: &str,
        slot: ItemSlots,
        rarity: Rarities,
    ) -> Result<()> {
        // check that the item is not already equipped
        if !draw.contains(name) {
            match item_meshes
                .get(&slot)
                .map(|rarity_items| rarity_items.get(&rarity))
                .flatten()
            {
                Some(mesh) => {
                    mesh.model_buffer()
                        .fill(&[(location.transformation_matrix() * mesh.model_matrix()).into()])?;

                    draw.push(mesh.clone());
                }
                None => println!("couldnt find {:?} in item_meshes", slot),
            }
        }

        Ok(())
    }

    #[inline]
    fn _check_items(
        draw: &mut Draw,
        location: &Location,
        items: &ItemSlotContainer,
        item_meshes: &HashMap<ItemSlots, HashMap<Rarities, AssetMesh>>,
    ) -> Result<()> {
        macro_rules! check {
            ($func:ident, $name:expr) => {
                match items.$func() {
                    Some(item) => {
                        Self::_dress(draw, location, item_meshes, $name, item.slot, item.rarity)?;
                    }
                    None => Self::_undress(draw, $name),
                }
            };
        }

        check!(primary_hand, "Sword");
        check!(secondary_hand, "Shield");
        check!(helmet, "Helmet");
        check!(chest, "ChestPlate");
        check!(boots, "Boots");
        check!(gloves, "Gloves");

        Ok(())
    }

    fn _equipment_changed(
        multi_mut: &mut MultiMut<'_>,
        item_meshes: &HashMap<ItemSlots, HashMap<Rarities, AssetMesh>>,
        slot: ItemSlots,
        rarity: Rarities,
        is_some: bool,
    ) -> Result<()> {
        let draw = multi_mut.get::<Draw>().unwrap();
        let location = multi_mut.get::<Location>().unwrap();

        let item_name = match slot {
            ItemSlots::Helmet => "Helmet",
            ItemSlots::ChestPlate => "ChestPlate",
            ItemSlots::Gloves => "Gloves",
            ItemSlots::MainHand => "Sword",
            ItemSlots::OffHand => "Shield",
            ItemSlots::Boots => "Boots",

            ItemSlots::Belt => return Ok(()),
            ItemSlots::Ring => return Ok(()),
            ItemSlots::Amulet => return Ok(()),
        };

        if is_some {
            Self::_dress(draw, location, item_meshes, item_name, slot, rarity)?;
        } else {
            Self::_undress(draw, item_name);
        }

        Ok(())
    }
}

impl ItemSlotContainer {
    pub fn has_space_for(&self, item: &Item) -> bool {
        match item.slot {
            ItemSlots::Helmet => self.helmet.is_none(),
            ItemSlots::ChestPlate => self.chest_plate.is_none(),
            ItemSlots::Belt => self.belt.is_none(),
            ItemSlots::Gloves => self.gloves.is_none(),
            ItemSlots::Boots => self.boots.is_none(),
            ItemSlots::Ring => {
                for i in 0..RING_COUNT {
                    if self.rings[i].is_none() {
                        return true;
                    }
                }

                false
            }
            ItemSlots::Amulet => {
                for i in 0..AMULET_COUNT {
                    if self.amulets[i].is_none() {
                        return true;
                    }
                }

                false
            }
            ItemSlots::MainHand => self.main_hand.is_none(),
            ItemSlots::OffHand => self.off_hand.is_none(),
        }
    }

    /// inserts the item, returns the current item at this slot
    pub fn insert(
        &mut self,
        item: Item,
        attributes: &Attributes,
        multi_mut: &mut MultiMut<'_>,
    ) -> Result<Option<Item>> {
        // check attribute requirements
        if attributes < &item.attributes {
            return Ok(Some(item));
        }

        self.insert_unchecked(item, multi_mut)
    }

    pub fn insert_unchecked(
        &mut self,
        item: Item,
        multi_mut: &mut MultiMut<'_>,
    ) -> Result<Option<Item>> {
        Ok(match item.slot {
            ItemSlots::Helmet => {
                let current_helmet = self.helmet.clone();
                self._update_helmet(item, multi_mut);

                current_helmet
            }
            ItemSlots::ChestPlate => {
                let current_chest_plate = self.chest_plate.clone();
                self.update_chest(item, multi_mut);

                current_chest_plate
            }
            ItemSlots::Belt => {
                let current_belt = self.belt.clone();
                self._update_belt(item, multi_mut);

                current_belt
            }
            ItemSlots::Gloves => {
                let current_gloves = self.gloves.clone();
                self._update_gloves(item, multi_mut);

                current_gloves
            }
            ItemSlots::Boots => {
                let current_boots = self.boots.clone();
                self._update_boots(item, multi_mut);

                current_boots
            }
            ItemSlots::Ring => {
                if let Some(index) = self.rings.iter().position(|ring| ring.is_none()) {
                    self.set_rings(Some(item), index);
                    return Ok(None);
                }

                // return first ring, if there is no slot left
                let current_ring = self.rings[0].clone();
                self.set_rings(Some(item), 0);

                return Ok(current_ring);
            }
            ItemSlots::Amulet => {
                if let Some(index) = self.amulets.iter().position(|amulet| amulet.is_none()) {
                    self.set_amulets(Some(item), index);
                    return Ok(None);
                }

                // return first amulet, if there is no slot left
                let current_amulet = self.amulets[0].clone();
                self.set_amulets(Some(item), 0);

                return Ok(current_amulet);
            }
            ItemSlots::MainHand => {
                let current_main_hand = self.main_hand.clone();
                self.update_primary_hand(item, multi_mut);

                current_main_hand
            }
            ItemSlots::OffHand => {
                let current_off_hand = self.off_hand.clone();
                self.update_secondary_hand(item, multi_mut);

                current_off_hand
            }
        })
    }

    pub fn item_at(&self, slot: ItemSlots) -> Option<&Item> {
        match slot {
            ItemSlots::Helmet => self.helmet().as_ref(),
            ItemSlots::ChestPlate => self.chest().as_ref(),
            ItemSlots::Belt => self.belt().as_ref(),
            ItemSlots::Gloves => self.gloves().as_ref(),
            ItemSlots::Boots => self.boots().as_ref(),
            ItemSlots::Ring => {
                for ring in self.rings.iter() {
                    if ring.is_some() {
                        return ring.as_ref();
                    }
                }

                None
            }
            ItemSlots::Amulet => {
                for amulet in self.amulets.iter() {
                    if amulet.is_some() {
                        return amulet.as_ref();
                    }
                }

                None
            }
            ItemSlots::MainHand => self.primary_hand().as_ref(),
            ItemSlots::OffHand => self.secondary_hand().as_ref(),
        }
    }

    pub fn collect_attribute_bonuses(
        &self,
        item_settings: &ItemSettings,
    ) -> (Strength, Agility, Intelligence) {
        let mut strength = Strength::default();
        let mut agility = Agility::default();
        let mut intelligence = Intelligence::default();

        for item in self.as_vec().iter().copied().flatten() {
            for affix in item.affixes.iter() {
                if let ItemAffix::Socket(Some(jewel)) = affix {
                    let value = item_settings
                        .jewel_rarity_multiplier
                        .from_rarity(jewel.rarity)
                        * jewel.level
                        * item_settings.general.jewel_level_multiplier;

                    match jewel.attribute {
                        Attribute::Agility => agility += Agility::from(value),
                        Attribute::Intelligence => intelligence += Intelligence::from(value),
                        Attribute::Strength => strength += Strength::from(value),
                    }
                }
            }
        }

        (strength, agility, intelligence)
    }

    pub fn collect_stat_bonuses(&self) -> Vec<StatisticType> {
        let mut affixes = Vec::new();
        let items = self.as_vec();

        for item in items.iter().copied().flatten() {
            for affix in item.affixes.iter() {
                affixes.push(affix.clone());
            }
        }

        Self::squash_stats(affixes)
    }

    fn squash_stats(affixes: Vec<ItemAffix>) -> Vec<StatisticType> {
        let mut result = Vec::new();

        for stat in affixes.iter() {
            match stat {
                ItemAffix::Socket(jewel) => {
                    if let Some(jewel) = jewel {
                        Self::insert_stat(&jewel.stat, &mut result);
                    }
                }
                ItemAffix::Stat(stat) => Self::insert_stat(stat, &mut result),
            }
        }

        result
    }

    fn insert_stat(val: &StatisticType, vec: &mut Vec<StatisticType>) {
        match vec
            .iter_mut()
            .find(|stat| StatisticType::same_type(stat, val))
        {
            Some(stat) => {
                *stat += val.clone();
            }
            None => vec.push(val.clone()),
        }
    }
}

impl EntityComponent for ItemSlotContainer {
    fn name(&self) -> &str {
        Self::debug_name()
    }
}

impl ComponentDebug for ItemSlotContainer {
    fn debug_name() -> &'static str {
        "ItemSlotContainer"
    }
}

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

    use anyhow::Result;
    use assetpath::AssetPath;

    #[test]
    fn create_rarity_textures() -> Result<()> {
        const OBJECT_PATH: &str = "gltf_objects/blender_test/blender_test.glb";

        let path = AssetPath::from((std::env::var("GAVANIA_DATA_DIR").unwrap(), OBJECT_PATH));

        let (device, queue) = create_vk_handles()?;

        let gltf_asset = GltfAsset::new(&path)?;
        let asset = Asset::new(
            &device,
            &queue,
            SceneType::Rasterizer,
            gltf_asset,
            path.path_without_prefix(),
        )?;

        if std::path::Path::new("mesh_images").exists() {
            std::fs::remove_dir_all("mesh_images")?;
        }

        if !std::path::Path::new("mesh_images").exists() {
            std::fs::create_dir("mesh_images")?;
        }

        for (x, mesh) in asset.meshes.iter().enumerate() {
            for (y, primitive_type) in mesh.primitive_types.iter().enumerate() {
                if let Some(image) = primitive_type.color_texture() {
                    image.to_file(&format!("mesh_images/{}_{}_source.png", x, y))?;
                }
            }
        }

        Ok(())
    }
}