use std::collections::HashMap;

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

use crate::entity::gateway::EntityGateway;
use crate::entity::character::CharacterEntity;
use crate::entity::item::{ItemEntity, ItemId, ItemDetail, ItemLocation};
use crate::entity::item::weapon::Weapon;
use crate::entity::item::armor::Armor;
use crate::entity::item::shield::Shield;
use crate::entity::item::unit::Unit;
use crate::entity::item::tool::Tool;
use crate::entity::item::mag::Mag;


#[derive(Debug, PartialEq)]
pub enum StackedItem {
    Individual(ItemEntity),
    Stacked(Vec<ItemEntity>),
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct ActiveItemId(u32);


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

impl ActiveItem {
    pub fn as_client_bytes(&self) -> [u8; 16] {
        match &self.item {
            StackedItem::Individual(i) => {
                match &i.item {
                    ItemDetail::Weapon(w) => w.as_bytes(),
                    ItemDetail::Armor(a) => a.as_bytes(),
                    ItemDetail::Shield(s) => s.as_bytes(),
                    ItemDetail::Unit(u) => u.as_bytes(),
                    ItemDetail::Tool(t) => t.as_individual_bytes(),
                    ItemDetail::TechniqueDisk(d) => d.as_bytes(),
                    ItemDetail::Mag(m) => m.as_bytes(),
                }
            },
            StackedItem::Stacked(i) => {
                let len = i.len();
                match &i[0].item {
                    ItemDetail::Tool(t) => t.as_stacked_bytes(len),
                    _ => panic!(),
                }
            }
        }
    }
}

pub struct ActiveInventory(Vec<ActiveItem>);

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

                // does this do anything?
                inventory[index].equipped = match item.item {
                    StackedItem::Individual(ItemEntity {location: ItemLocation::Inventory{ equipped: true, ..}, ..}) => 1,
                    _ => 0,
                };
                // because this actually equips the item
                inventory[index].flags |= match item.item {
                    StackedItem::Individual(ItemEntity {location: ItemLocation::Inventory{ equipped: true, ..}, ..}) => 8,
                    _ => 0,
                };
                inventory
            })
    }

    pub fn count(&self) -> usize {
        self.0.len()
    }
}

fn inventory_item_index(item: &StackedItem) -> usize {
    match item {
        StackedItem::Individual(i) => {
            match i.location {
                ItemLocation::Inventory{index: index, ..} => index,
                _ => panic!()
            }
        },
        StackedItem::Stacked(i) => {
            match i[0].location {
                ItemLocation::Inventory{index: index, ..} => index,
                _ => panic!()
            }
        }
    }
}

fn stack_items(items: Vec<ItemEntity>) -> Vec<StackedItem> {
    let mut stacks = HashMap::new();

    for item in items {
        stacks.entry(item.item.item_type()).or_insert(Vec::new()).push(item);
    }

    stacks.into_iter()
        .map(|(itype, items)| {
            match items[0].item.is_stackable() {
                true => {
                    vec![StackedItem::Stacked(items)]
                },
                false => {
                    items.into_iter().map(|i| {
                        StackedItem::Individual(i)
                    }).collect()
                }
            }
        })
        .flatten()
        .collect()
}

struct ActiveBank([Option<ActiveItemId>; 200]);

pub struct ActiveItemDatabase {
    id: u32,
}



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

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

    // deactivate item

    pub fn get_character_inventory<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> ActiveInventory {
        let items = entity_gateway.get_items_by_character(&character);
        let inventory_items = items.into_iter()
            .filter(|item| {
                match item.location {
                    ItemLocation::Inventory{..} => true,
                    _ => false,
                }
            }).collect();
        let mut stacked = stack_items(inventory_items);
        stacked.sort_by(|a, b| {
            inventory_item_index(a).partial_cmp(&inventory_item_index(b)).unwrap()
        });
        let activated = stacked.into_iter().map(|i| self.activate_item(i));
        ActiveInventory(activated.take(30).collect())
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::entity::character::CharacterEntityId;
    use crate::entity::item;
    use crate::entity::item::{ItemEntity, ItemDetail, ItemEntityId, ItemLocation};
    #[test]
    fn test_stack_items() {
        let item1 = ItemEntity {
            id: ItemEntityId(1),
            location: ItemLocation::Inventory {
                character_id: CharacterEntityId(0),
                index: 0,
                equipped: false,
            },
            item: ItemDetail::Weapon(item::weapon::Weapon {
                weapon: item::weapon::WeaponType::Saber,
                grind: 0,
                special: None,
                attrs: [None; 3],
                tekked: true,
            })
        };
        let item2 = ItemEntity {
            id: ItemEntityId(2),
            location: ItemLocation::Inventory {
                character_id: CharacterEntityId(0),
                index: 1,
                equipped: false,
            },
            item: ItemDetail::Tool(Tool {
                tool: item::tool::ToolType::Monofluid,
            })
        };
        let item3 = ItemEntity {
            id: ItemEntityId(3),
            location: ItemLocation::Inventory {
                character_id: CharacterEntityId(0),
                index: 2,
                equipped: false,
            },
            item: ItemDetail::Weapon(item::weapon::Weapon {
                weapon: item::weapon::WeaponType::Handgun,
                grind: 12,
                special: None,
                attrs: [None; 3],
                tekked: true,
            })
        };
        let item4 = ItemEntity {
            id: ItemEntityId(4),
            location: ItemLocation::Inventory {
                character_id: CharacterEntityId(0),
                index: 1,
                equipped: false,
            },
            item: ItemDetail::Tool(Tool {
                tool: item::tool::ToolType::Monofluid,
            })
        };
        let item5 = ItemEntity {
            id: ItemEntityId(5),
            location: ItemLocation::Inventory {
                character_id: CharacterEntityId(0),
                index: 1,
                equipped: false,
            },
            item: ItemDetail::Tool(Tool {
                tool: item::tool::ToolType::Monofluid,
            })
        };
        let item6 = ItemEntity {
            id: ItemEntityId(6),
            location: ItemLocation::Inventory {
                character_id: CharacterEntityId(0),
                index: 3,
                equipped: false,
            },
            item: ItemDetail::Weapon(item::weapon::Weapon {
                weapon: item::weapon::WeaponType::Handgun,
                grind: 12,
                special: None,
                attrs: [None; 3],
                tekked: true,
            })
        };
        let item7 = ItemEntity {
            id: ItemEntityId(7),
            location: ItemLocation::Inventory {
                character_id: CharacterEntityId(0),
                index: 4,
                equipped: false,
            },
            item: ItemDetail::Tool(Tool {
                tool: item::tool::ToolType::Monomate,
            })
        };
        let item8 = ItemEntity {
            id: ItemEntityId(8),
            location: ItemLocation::Inventory {
                character_id: CharacterEntityId(0),
                index: 4,
                equipped: false,
            },
            item: ItemDetail::Tool(Tool {
                tool: item::tool::ToolType::Monomate,
            })
        };
        let item9 = ItemEntity {
            id: ItemEntityId(9),
            location: ItemLocation::Inventory {
                character_id: CharacterEntityId(0),
                index: 4,
                equipped: false,
            },
            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);
    }
}