#![allow(dead_code)]
use std::collections::HashMap;
use libpso::character::character::InventoryItem;
use crate::entity::gateway::EntityGateway;
use crate::entity::character::CharacterEntity;
use crate::entity::item::{ItemEntity, ItemDetail, ItemLocation};
use crate::entity::item::{Meseta, NewItemEntity};
use crate::ship::map::MapArea;
use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::ship::ShipError;


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

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


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

impl ActiveItem {
    pub fn as_client_bytes(&self) -> [u8; 16] {
        match &self.item {
            ItemInstance::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(),
                }
            },
            ItemInstance::Stacked(i) => {
                let len = i.len();
                match &i[0].item {
                    ItemDetail::Tool(t) => t.as_stacked_bytes(len),
                    _ => panic!(),
                }
            },
            ItemInstance::Meseta(m) => {
                m.as_bytes()
            }
        }
    }
}

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 {
                    ItemInstance::Individual(ItemEntity {location: ItemLocation::Inventory{ equipped: true, ..}, ..}) => 1,
                    _ => 0,
                };
                // because this actually equips the item
                inventory[index].flags |= match item.item {
                    ItemInstance::Individual(ItemEntity {location: ItemLocation::Inventory{ equipped: true, ..}, ..}) => 8,
                    _ => 0,
                };
                inventory
            })
    }

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

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

pub struct ActiveItemOnFloor {
    pub map_area: MapArea,
    pub x: f32,
    pub y: f32,
    pub z: f32,
    pub item: ActiveItem,
}

fn stack_items(items: Vec<ItemEntity>) -> Vec<ItemInstance> {
    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(|(_, items)| {
            match items[0].item.is_stackable() {
                true => {
                    vec![ItemInstance::Stacked(items)]
                },
                false => {
                    items.into_iter().map(|i| {
                        ItemInstance::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: ItemInstance) -> ActiveItem {
        self.id += 1;
        ActiveItem {
            id: ActiveItemId(self.id),
            item: item,
        }
    }

    // TODO: 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())
    }

    pub fn activate_item_drop<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, item_drop: ItemDrop) -> Result<ActiveItemOnFloor, ShipError> {
        let item_detail = match item_drop.item {
            ItemDropType::Weapon(w) => Some(ItemDetail::Weapon(w)),
            ItemDropType::Armor(w) => Some(ItemDetail::Armor(w)),
            ItemDropType::Shield(w) => Some(ItemDetail::Shield(w)),
            ItemDropType::Unit(w) => Some(ItemDetail::Unit(w)),
            ItemDropType::Tool(w) => Some(ItemDetail::Tool(w)),
            ItemDropType::TechniqueDisk(w) => Some(ItemDetail::TechniqueDisk(w)),
            ItemDropType::Mag(w) => Some(ItemDetail::Mag(w)),
            ItemDropType::Meseta(_) => None
        };
        let item_instance = match item_detail {
            Some(item) => {
                let item_entity = entity_gateway.create_item(NewItemEntity {
                    item: item,
                    location: ItemLocation::Floor {
                        map_area: item_drop.map_area,
                        x: item_drop.x,
                        y: item_drop.y,
                        z: item_drop.z,
                    }
                }).unwrap();
                stack_items(vec![item_entity]).pop().ok_or(ShipError::ItemError)?
            },
            None => {
                let meseta = match item_drop.item {
                    ItemDropType::Meseta(m) => m,
                    _ => panic!(),
                };
                ItemInstance::Meseta(Meseta(meseta))
            }
        };
        let active_item = self.activate_item(item_instance);

        Ok(ActiveItemOnFloor {
            map_area: item_drop.map_area,
            x: item_drop.x,
            y: item_drop.y,
            z: item_drop.z,
            item: active_item,
        })
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::entity::character::CharacterEntityId;
    use crate::entity::item;
    use crate::entity::item::{ItemEntity, ItemDetail, ItemEntityId, ItemLocation};
    use crate::entity::item::tool::Tool;
    #[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 == ItemInstance::Individual(item6.clone())
        }).count() == 1);

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

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

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

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