use crate::ship::items::ClientItemId;
use libpso::character::character;//::InventoryItem;
use crate::entity::item::{ItemEntityId, ItemEntity, ItemDetail, ItemLocation, BankEntity, BankItemEntity, BankName};
use crate::entity::character::CharacterEntityId;
use crate::entity::item::tool::Tool;
use crate::ship::items::inventory::{InventoryItemHandle, InventoryItem};

const BANK_CAPACITY: usize = 200;

#[derive(Debug, Clone)]
pub struct IndividualBankItem {
    pub entity_id: ItemEntityId,
    pub item_id: ClientItemId,
    pub item: ItemDetail,
}

#[derive(Debug, Clone)]
pub struct StackedBankItem {
    pub entity_ids: Vec<ItemEntityId>,
    pub item_id: ClientItemId,
    pub tool: Tool,
}

impl StackedBankItem {
    pub fn count(&self) -> usize {
        self.entity_ids.len()
    }

    pub fn take_entity_ids(&mut self, amount: usize) -> Option<Vec<ItemEntityId>> {
        if amount <= self.count() {
            Some(self.entity_ids.drain(..amount).collect())
        }
        else {
            None
        }
    }
}

#[derive(Debug, Clone)]
pub enum BankItem {
    Individual(IndividualBankItem),
    Stacked(StackedBankItem),
}


impl std::cmp::PartialEq for BankItem {
    fn eq(&self, other: &BankItem) -> bool {
        let mut self_bytes = [0u8; 4];
        let mut other_bytes = [0u8; 4];
        self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]);
        other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]);

        let self_value = u32::from_be_bytes(self_bytes);
        let other_value = u32::from_be_bytes(other_bytes);

        self_value.eq(&other_value)
    }
}

impl std::cmp::Eq for BankItem {}

impl std::cmp::PartialOrd for BankItem {
    fn partial_cmp(&self, other: &BankItem) -> Option<std::cmp::Ordering> {
        //let self_bytes = self.as_client_bytes();
        //let other_bytes = other.as_client_bytes();
        let mut self_bytes = [0u8; 4];
        let mut other_bytes = [0u8; 4];
        self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]);
        other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]);


        let self_value = u32::from_be_bytes(self_bytes);
        let other_value = u32::from_be_bytes(other_bytes);

        self_value.partial_cmp(&other_value)
    }
}

impl std::cmp::Ord for BankItem {
    fn cmp(&self, other: &BankItem) -> std::cmp::Ordering {
        //let self_bytes = self.as_client_bytes();
        //let other_bytes = other.as_client_bytes();
        let mut self_bytes = [0u8; 4];
        let mut other_bytes = [0u8; 4];
        self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]);
        other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]);


        let self_value = u32::from_le_bytes(self_bytes);
        let other_value = u32::from_le_bytes(other_bytes);

        self_value.cmp(&other_value)
    }
}

impl BankItem {
    pub fn set_item_id(&mut self, item_id: ClientItemId) {
        match self {
            BankItem::Individual(individual_bank_item) => {
                individual_bank_item.item_id = item_id
            },
            BankItem::Stacked(stacked_bank_item) => {
                stacked_bank_item.item_id = item_id
            }
        }
    }

    pub fn item_id(&self) -> ClientItemId {
        match self {
            BankItem::Individual(individual_bank_item) => {
                individual_bank_item.item_id
            },
            BankItem::Stacked(stacked_bank_item) => {
                stacked_bank_item.item_id
            }
        }
    }

    pub fn as_client_bytes(&self) -> [u8; 16] {
        match self {
            BankItem::Individual(item) => {
                match &item.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(),
                    ItemDetail::ESWeapon(e) => e.as_bytes(),
                }
            },
            BankItem::Stacked(item) => {
                item.tool.as_stacked_bytes(item.entity_ids.len())
            },
        }
    }
}


pub struct BankItemHandle<'a> {
    bank: &'a mut CharacterBank,
    index: usize
}

impl<'a> BankItemHandle<'a> {
    pub fn item(&'a self) -> Option<&'a BankItem> {
        self.bank.items.get(self.index)
    }

    pub fn item_mut(&mut self) -> Option<&mut BankItem> {
        self.bank.items.get_mut(self.index)
    }

    pub fn remove_from_bank(self) {
        self.bank.items.remove(self.index);
    }
}

pub struct CharacterBank {
    item_id_counter: u32,
    items: Vec<BankItem>
}

impl CharacterBank {
    pub fn new(mut items: Vec<BankItem>) -> CharacterBank {
        items.sort();
        CharacterBank {
            item_id_counter: 0,
            items,
        }
    }

    pub fn initialize_item_ids(&mut self, base_item_id: u32) {
        for (i, item) in self.items.iter_mut().enumerate() {
            item.set_item_id(ClientItemId(base_item_id + i as u32));
        }
        self.item_id_counter = base_item_id + self.items.len() as u32 + 1;
    }

    pub fn get_item_handle_by_id(&mut self, item_id: ClientItemId) -> Option<BankItemHandle> {
        let (index, _) = self.items.iter()
            .enumerate()
            .find(|(_, item)| {
                item.item_id() == item_id
            })?;
        Some(BankItemHandle {
            bank: self,
            index,
        })
    }

    pub fn as_client_bank_items(&self) -> character::Bank {
        self.items.iter()
            .enumerate()
            .fold(character::Bank::default(), |mut bank, (slot, item)| {
                bank.item_count = (slot + 1) as u32;
                let bytes = item.as_client_bytes();
                bank.items[slot].data1.copy_from_slice(&bytes[0..12]);
                bank.items[slot].data2.copy_from_slice(&bytes[12..16]);
                bank.items[slot].item_id = item.item_id().0;

                bank
            })
    }

    pub fn as_client_bank_request(&self) -> Vec<character::BankItem> {
        self.items.iter()
            .map(|item| {
                let bytes = item.as_client_bytes();
                let mut data1 = [0; 12];
                let mut data2 = [0; 4];
                data1.copy_from_slice(&bytes[0..12]);
                data2.copy_from_slice(&bytes[12..16]);
                let amount = match item {
                    BankItem::Individual(_individual_bank_item) => {
                        1
                    },
                    BankItem::Stacked(stacked_bank_item) => {
                        stacked_bank_item.count()
                    },
                };
                character::BankItem {
                    data1,
                    data2,
                    item_id: item.item_id().0,
                    amount: amount as u16,
                    flags: 1,
                }
            })
            .collect()
    }

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

    pub fn deposit_item(&mut self, mut inventory_item: InventoryItemHandle, amount: usize) -> Option<&BankItem> {
        let remove = match inventory_item.item_mut()? {
            InventoryItem::Individual(individual_inventory_item) => {
                if self.items.len() >= BANK_CAPACITY {
                    return None
                }
                self.items.push(BankItem::Individual(IndividualBankItem {
                    entity_id: individual_inventory_item.entity_id,
                    item_id: individual_inventory_item.item_id,
                    item: individual_inventory_item.item.clone(),
                }));
                true
            },
            InventoryItem::Stacked(stacked_inventory_item) => {
                let existing_bank_item = self.items.iter_mut()
                    .find_map(|item| {
                        if let BankItem::Stacked(stacked_bank_item) = item {
                            if stacked_bank_item.tool == stacked_inventory_item.tool {
                                return Some(stacked_bank_item)
                            }
                        }
                        None
                    });

                match existing_bank_item {
                    Some(stacked_bank_item) => {
                        if stacked_bank_item.count() + stacked_inventory_item.count() > stacked_inventory_item.tool.max_stack() {
                            return None
                        }

                        let mut deposited_entity_ids = stacked_inventory_item.take_entity_ids(amount)?;
                        stacked_bank_item.entity_ids.append(&mut deposited_entity_ids);
                    }
                    None => {
                        if self.items.len() >= BANK_CAPACITY {
                            return None
                        }
                        let deposited_entity_ids = stacked_inventory_item.take_entity_ids(amount)?;

                        self.item_id_counter += 1;
                        self.items.push(BankItem::Stacked(StackedBankItem {
                            entity_ids: deposited_entity_ids,
                            item_id: ClientItemId(self.item_id_counter),
                            tool: stacked_inventory_item.tool,
                        }))
                    }
                }
                stacked_inventory_item.count() == 0
            }
        };

        if remove {
            inventory_item.remove_from_inventory();
        }

        self.items.last()
    }

    pub fn as_bank_entity(&self, character_id: &CharacterEntityId, bank_name: &BankName) -> BankEntity {
        BankEntity {
            items: self.items.iter()
                .map(|item| {
                    match item {
                        BankItem::Individual(item) => {
                            BankItemEntity::Individual(ItemEntity {
                                id: item.entity_id,
                                location: ItemLocation::Bank {
                                    character_id: *character_id,
                                    name: bank_name.clone(),
                                },
                                item: item.item.clone(),
                            })
                        },
                        BankItem::Stacked(items) => {
                            BankItemEntity::Stacked(items.entity_ids.iter()
                                                         .map(|id| {
                                                             ItemEntity {
                                                                 id: *id,
                                                                 location: ItemLocation::Bank {
                                                                     character_id: *character_id,
                                                                     name: bank_name.clone(),
                                                                 },
                                                                 item: ItemDetail::Tool(items.tool)
                                                             }
                                                         })
                                                         .collect())
                        },
                    }
                })
                .collect()
        }
    }
}