use std::collections::HashMap;

use shrev::ReaderId;
use space_crush_common::{
    components::{FleetOwned, PlayerOwned, Ship},
    misc::ComponentEvent,
};
use specs::{
    hibitset::BitSetLike, prelude::*, world::Index, Entities, ReadExpect, ReadStorage, System,
    World, WriteStorage,
};

use crate::{components::FleetInfo, resources::PlayerState};

pub struct FleetInfoUpdate {
    modified: HashMap<Index, Modified>,
    need_update: BitSet,
    fleet_owned_id: ReaderId<ComponentEvent<FleetOwned>>,
    player_owned_id: ReaderId<ComponentEvent<PlayerOwned>>,
}

#[derive(Default)]
struct Modified {
    old_fleet_id: Option<Entity>,
    old_player_id: Option<Entity>,
}

impl FleetInfoUpdate {
    pub fn new(world: &mut World) -> Self {
        let modified = HashMap::new();
        let need_update = BitSet::new();

        let fleet_owned_id = unsafe {
            WriteStorage::<FleetOwned>::setup(world);

            let mut fleet_owned = world.system_data::<WriteStorage<FleetOwned>>();
            fleet_owned
                .unprotected_storage_mut()
                .channel_mut()
                .register_reader()
        };

        let player_owned_id = unsafe {
            WriteStorage::<PlayerOwned>::setup(world);

            let mut player_owned = world.system_data::<WriteStorage<PlayerOwned>>();
            player_owned
                .unprotected_storage_mut()
                .channel_mut()
                .register_reader()
        };

        Self {
            modified,
            need_update,
            fleet_owned_id,
            player_owned_id,
        }
    }
}

#[derive(SystemData)]
pub struct FleetInfoUpdateData<'a> {
    entities: Entities<'a>,
    player_state: ReadExpect<'a, PlayerState>,
    ships: ReadStorage<'a, Ship>,
    fleet_infos: WriteStorage<'a, FleetInfo>,
    fleet_owned: ReadStorage<'a, FleetOwned>,
    player_owned: ReadStorage<'a, PlayerOwned>,
}

impl<'a> System<'a> for FleetInfoUpdate {
    type SystemData = FleetInfoUpdateData<'a>;

    fn run(&mut self, data: Self::SystemData) {
        let FleetInfoUpdateData {
            entities,
            player_state,
            ships,
            mut fleet_infos,
            fleet_owned,
            player_owned,
        } = data;

        self.modified.clear();
        self.need_update.clear();

        /* player owned events */
        let events = player_owned
            .unprotected_storage()
            .channel()
            .read(&mut self.player_owned_id);
        for event in events {
            match event {
                ComponentEvent::Inserted(id) => {
                    self.need_update.add(*id);
                }
                ComponentEvent::Modified(id, player_owned)
                | ComponentEvent::Removed(id, player_owned) => {
                    self.need_update.add(*id);
                    self.modified.entry(*id).or_default().old_player_id = Some(player_owned.owner);
                }
            }
        }

        /* fleet owned events */
        let events = fleet_owned
            .unprotected_storage()
            .channel()
            .read(&mut self.fleet_owned_id);
        for event in events {
            match event {
                ComponentEvent::Inserted(id) => {
                    self.need_update.add(*id);
                }
                ComponentEvent::Modified(id, fleet_owned)
                | ComponentEvent::Removed(id, fleet_owned) => {
                    self.need_update.add(*id);
                    self.modified.entry(*id).or_default().old_fleet_id = Some(fleet_owned.owner);
                }
            }
        }

        if self.need_update.is_empty() {
            return;
        }

        /* update fleets */
        let player_id = player_state.player_id;
        for (fleet_id, fleet_info) in (&entities, &mut fleet_infos).join() {
            for (ship_id, ship, fleet_owned, player_owned, _) in (
                &entities,
                &ships,
                &fleet_owned,
                &player_owned,
                &self.need_update,
            )
                .join()
            {
                let new_match = fleet_owned.owner == fleet_id && player_owned.owner == player_id;
                let old_match = match self.modified.get(&ship_id.id()) {
                    None => false,
                    Some(modified) => match (modified.old_fleet_id, modified.old_player_id) {
                        (Some(old_fleet_id), Some(old_player_id)) => {
                            old_fleet_id != fleet_id && old_player_id == player_id
                        }
                        (Some(old_fleet_id), None) => {
                            old_fleet_id != fleet_id && player_owned.owner == player_id
                        }
                        (None, Some(old_player_id)) => {
                            fleet_owned.owner != fleet_id && old_player_id == player_id
                        }
                        (None, None) => false,
                    },
                };

                if old_match && !new_match {
                    fleet_info.count[ship.type_] = fleet_info.count[ship.type_].saturating_sub(1);
                } else if !old_match && new_match {
                    fleet_info.count[ship.type_] += 1;
                }
            }
        }
    }
}
