use std::collections::HashMap;
use std::hash::{Hash, Hasher};

use libpso::character::character::InventoryItem;

use crate::entity::item::{Item, ItemDetail, ItemLocation, Weapon, Armor, Shield};
use crate::entity::item::tool::StackedTool;


fn are_items_same_type(itema: &Item, itemb: &Item) -> bool {
    match (&itema.item, &itemb.item) {
        (ItemDetail::Weapon(a), ItemDetail::Weapon(b)) => a.weapon.weapon == b.weapon.weapon,
        (ItemDetail::Armor(a), ItemDetail::Armor(b)) => a.armor.armor == b.armor.armor,
        (ItemDetail::Shield(a), ItemDetail::Shield(b)) => a.shield.shield == b.shield.shield,
        (ItemDetail::Tool(a), ItemDetail::Tool(b)) => a.tool == b.tool,
        _ => false
    }
}

#[derive(Debug)]
pub struct ActiveItemId(u32);

// TODO: Stacked(count, itemtype Vec<ItemId>)?
#[derive(Debug, PartialEq)]
pub enum StackedItem {
    Individual(Item),
    Stacked(Vec<Item>),
}

impl StackedItem {
    fn is_same_item_type(&self, item: &Item) -> bool {
        match self {
            StackedItem::Individual(i) => are_items_same_type(i, item),
            StackedItem::Stacked(i) => i.iter().all(|k| are_items_same_type(k, item))
        }
    }

    fn index(&self) -> usize {
        match self {
            StackedItem::Individual(Item {location: ItemLocation::Inventory {index, ..}, ..}) => *index,
            StackedItem::Stacked(items) => {
                match items[0].location {
                    ItemLocation::Inventory {index, ..} => index,
                    _ => panic!()
                }
            }
            _ => panic!()
        }
    }

    fn as_bytes(&self) -> [u8; 16] {
        match self {
            StackedItem::Individual(item) => {
                item.item.as_bytes()
            }
            StackedItem::Stacked(items) => {
                let count = items.len();
                match &items[0].item {
                    ItemDetail::Tool(tool) => {
                        StackedTool {
                            tool: tool.tool,
                            count: count
                        }.as_bytes()
                    },
                    _ => panic!()
                }
            }
        }
    }
    
}


pub struct ItemActivator {
    id: u32,

}

impl ItemActivator {
    pub fn new() -> ItemActivator {
        ItemActivator {
            id: 0
        }
    }

    pub fn activate_item(&mut self, item: StackedItem) -> ActiveItem {
        self.id += 1;
        ActiveItem {
            id: ActiveItemId(self.id),
            item: item,
        }
    }
}


#[derive(Debug)]
pub struct ActiveItem {
    pub id: ActiveItemId,
    pub item: StackedItem,
}


struct StackedItemKey(Item);

impl Hash for StackedItemKey {
    fn hash<H: Hasher>(&self, hasher: &mut H) {
        match &self.0.item {
            ItemDetail::Weapon(w) => w.weapon.weapon.value().hash(hasher),
            ItemDetail::Armor(a) => a.armor.armor.value().hash(hasher),
            ItemDetail::Shield(s) => s.shield.shield.value().hash(hasher),
            ItemDetail::Tool(t) => t.tool.value().hash(hasher),
        }
    }
}

impl std::cmp::PartialEq for StackedItemKey {
    fn eq(&self, other: &StackedItemKey) -> bool {
        are_items_same_type(&self.0, &other.0)
    }
}

impl Eq for StackedItemKey {}

pub fn stack_items(items: Vec<Item>) -> Vec<StackedItem> {
    items.into_iter()
        .fold(HashMap::new(), |mut stacked: HashMap<StackedItemKey, Vec<Item>>, item| {
            stacked.entry(StackedItemKey(item.clone()))
                .and_modify(|stack| stack.push(item.clone()))
                .or_insert_with(|| {
                    vec![item]
                });

            stacked
        })
        .into_iter()
        .map(|(_k, v)| {
            v
        })
        .fold(Vec::new(), |mut stacked, item| {
            if item[0].item.is_stackable() {
                stacked.push(StackedItem::Stacked(item))
            }
            else {
                stacked.append(&mut item.into_iter().map(|k| StackedItem::Individual(k)).collect())
            }
            
            stacked
        })
}


pub enum InventoryAddError {
    InventoryFull,
    FullToolStack,
}

pub enum InventoryRemoveError {
    NoItemInInventory,
}


pub struct Inventory([Option<ActiveItem>; 30]);

impl Inventory {
    pub fn new(items: Vec<ActiveItem>) -> Inventory {
        items.into_iter()
            .fold(Inventory([None; 30]), |mut inventory, item| {
                let index = item.item.index();
                inventory.0[index] = Some(item);
                inventory
                    
            })
    }

    pub fn count(&self) -> usize {
        self.0.iter()
            .filter(|k| k.is_some())
            .count()
    }

    pub fn as_client_inventory_items(&self) -> [InventoryItem; 30] {
        self.0.iter()
            .enumerate()
            .fold([InventoryItem::default(); 30], |mut inventory, (index, item)| {
                if let Some(i) = item {
                    let bytes = i.item.as_bytes();
                    inventory[index].data1.copy_from_slice(&bytes[0..12]);
                    inventory[index].item_id = i.id.0;

                    // does this do anything?
                    inventory[index].equipped = match i.item {
                        StackedItem::Individual(Item {item: ItemDetail::Weapon(Weapon {equipped: true, ..}), ..}) => 1,
                        StackedItem::Individual(Item {item: ItemDetail::Armor(Armor {equipped: true, ..}), ..}) => 1,
                        StackedItem::Individual(Item {item: ItemDetail::Shield(Shield {equipped: true, ..}), ..}) => 1,
                        _ => 0,
                    };
                    // because this actually equips the item
                    inventory[index].flags |= match i.item {
                        StackedItem::Individual(Item {item: ItemDetail::Weapon(Weapon {equipped: true, ..}), ..}) => 8,
                        StackedItem::Individual(Item {item: ItemDetail::Armor(Armor {equipped: true, ..}), ..}) => 8,
                        StackedItem::Individual(Item {item: ItemDetail::Shield(Shield {equipped: true, ..}), ..}) => 8,
                        _ => 0,
                    };
                }
                inventory
            })
    }
}


pub struct Bank {}


pub fn split_items_into_inventory_and_bank(items: Vec<Item>) -> (Vec<Item>, Vec<Item>) {
    items.into_iter().partition(|item| {
        match item.location {
            ItemLocation::Inventory{..} => true,
            ItemLocation::Bank{..} => false,
            ItemLocation::Floor{..} => panic!("oh god what happened"),
        }
    })
}



mod test {
    use super::*;
    use crate::entity::item::{Item, ItemDetail, ItemEntityId, ItemLocation, Weapon, Tool};
    use libpso::item;
    
    #[test]
    fn test_stacked_items() {
        let item1 = Item {
            id: ItemEntityId(1),
            location: ItemLocation::Inventory {
                character_id: 0,
                index: 0,
            },
            item: ItemDetail::Weapon(Weapon {
                equipped: false,
                weapon: item::weapon::Weapon {
                    weapon: item::weapon::WeaponType::Saber,
                    grind: 0,
                    special: None,
                    attrs: [None; 3]
                }
            })
        };
        let item2 = Item {
            id: ItemEntityId(2),
            location: ItemLocation::Inventory {
                character_id: 0,
                index: 1
            },
            item: ItemDetail::Tool(Tool {
                tool: item::tool::ToolType::Monofluid,
            })
        };
        let item3 = Item {
            id: ItemEntityId(3),
            location: ItemLocation::Inventory {
                character_id: 0,
                index: 2,
            },
            item: ItemDetail::Weapon(Weapon {
                equipped: false,
                weapon: item::weapon::Weapon {
                    weapon: item::weapon::WeaponType::Handgun,
                    grind: 12,
                    special: None,
                    attrs: [None; 3]
                }
            })
        };
        let item4  = Item {
            id: ItemEntityId(4),
            location: ItemLocation::Inventory {
                character_id: 0,
                index: 1
            },
            item: ItemDetail::Tool(Tool {
                tool: item::tool::ToolType::Monofluid,
            })
        };
        let item5 = Item {
            id: ItemEntityId(5),
            location: ItemLocation::Inventory {
                character_id: 0,
                index: 1
            },
            item: ItemDetail::Tool(Tool {
                tool: item::tool::ToolType::Monofluid,
            })
        };
        let item6 = Item {
            id: ItemEntityId(6),
            location: ItemLocation::Inventory {
                character_id: 0,
                index: 3,
            },
            item: ItemDetail::Weapon(Weapon {
                equipped: false,
                weapon: item::weapon::Weapon {
                    weapon: item::weapon::WeaponType::Handgun,
                    grind: 12,
                    special: None,
                    attrs: [None; 3]
                }
            })
        };
        let item7 = Item {
            id: ItemEntityId(7),
            location: ItemLocation::Inventory {
                character_id: 0,
                index: 4
            },
            item: ItemDetail::Tool(Tool {
                tool: item::tool::ToolType::Monomate,
            })
        };
        let item8 = Item {
            id: ItemEntityId(8),
            location: ItemLocation::Inventory {
                character_id: 0,
                index: 4
            },
            item: ItemDetail::Tool(Tool {
                tool: item::tool::ToolType::Monomate,
            })
        };
        let item9 = Item {
            id: ItemEntityId(9),
            location: ItemLocation::Inventory {
                character_id: 0,
                index: 4
            },
            item: ItemDetail::Tool(Tool {
                tool: item::tool::ToolType::Monomate,
            })
        };

        let item_vec = vec![item1.clone(), item2.clone(), item3.clone(), item4.clone(), item5.clone(), item6.clone(), item7.clone(), item8.clone(), item9.clone()];

        let stacked = stack_items(item_vec);

        assert!(stacked.len() == 5);
        assert!(stacked.iter().filter(|k| {
            **k == StackedItem::Individual(item6.clone())
        }).count() == 1);

        assert!(stacked.iter().filter(|k| {
            **k == StackedItem::Individual(item3.clone())
        }).count() == 1);

        assert!(stacked.iter().filter(|k| {
            **k == StackedItem::Individual(item1.clone())
        }).count() == 1);

        assert!(stacked.iter().filter(|k| {
            **k == StackedItem::Stacked(vec![item2.clone(), item4.clone(), item5.clone()])
        }).count() == 1);

        assert!(stacked.iter().filter(|k| {
            **k == StackedItem::Stacked(vec![item7.clone(), item8.clone(), item9.clone()])
        }).count() == 1);
    }
}