use std::mem::MaybeUninit;
use std::ops::Deref;

use crate::common::{interpolate_fix9_scale, Direction};
use crate::components::flash::Flash;
use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::npc::list::NPCList;
use crate::npc::NPC;
use crate::player::Player;
use crate::shared_game_state::SharedGameState;
use crate::stage::Stage;
use crate::weapon::bullet::BulletManager;

pub mod balfrog;
pub mod ballos;
pub mod core;
pub mod heavy_press;
pub mod ironhead;
pub mod monster_x;
pub mod omega;
pub mod sisters;
pub mod undead_core;

pub struct BossNPC {
    pub boss_type: u16,
    pub parts: [NPC; 20],
    pub hurt_sound: [u8; 20],
    pub death_sound: [u8; 20],
}

impl BossNPC {
    pub fn new() -> BossNPC {
        let mut parts = unsafe {
            let mut parts_uninit: [NPC; 20] = MaybeUninit::uninit().assume_init();

            for part in &mut parts_uninit {
                *part = NPC::empty();
                part.cond.set_drs_boss(true);
            }

            parts_uninit
        };

        parts[0].cond.set_alive(true);

        for (i, part) in parts.iter_mut().enumerate() {
            part.rng.load_state(
                ((i as u32)
                    .wrapping_add(3271284409)
                    .rotate_left(5)
                    .wrapping_mul(3815776271)
                    .rotate_right(9)
                    .wrapping_sub(2626817629)
                    & 0xffffffff) as u32,
            );
        }

        BossNPC { boss_type: 0, parts, hurt_sound: [0; 20], death_sound: [0; 20] }
    }
}

impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Flash)> for BossNPC {
    fn tick(
        &mut self,
        state: &mut SharedGameState,
        (players, npc_list, stage, bullet_manager, flash): (
            [&mut Player; 2],
            &NPCList,
            &mut Stage,
            &BulletManager,
            &mut Flash,
        ),
    ) -> GameResult {
        if !self.parts[0].cond.alive() {
            return Ok(());
        }

        match self.boss_type {
            1 => self.tick_b01_omega(state, players, npc_list, bullet_manager, flash),
            2 => self.tick_b02_balfrog(state, players, npc_list),
            3 => self.tick_b03_monster_x(state, players, npc_list, flash),
            4 => self.tick_b04_core(state, players, npc_list, stage),
            5 => self.tick_b05_ironhead(state, players, npc_list),
            6 => self.tick_b06_sisters(state, players, npc_list, flash),
            7 => self.tick_b07_undead_core(state, npc_list, stage, flash),
            8 => self.tick_b08_heavy_press(state, npc_list, stage),
            9 => self.tick_b09_ballos(state, players, npc_list, flash),
            _ => {}
        }

        for part in &mut self.parts {
            if part.shock > 0 {
                part.shock -= 1;
            }
            part.popup.x = part.x;
            part.popup.y = part.y;
            part.popup.tick(state, ())?;
        }
        Ok(())
    }

    fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult {
        let batch = state.texture_set.get_or_load_batch(
            ctx,
            &state.constants,
            &state.npc_table.stage_textures.deref().borrow().npc2,
        )?;

        for npc in self.parts.iter().rev() {
            if !npc.cond.alive() || npc.cond.hidden() {
                continue;
            }

            let off_x =
                if npc.direction == Direction::Left { npc.display_bounds.left } else { npc.display_bounds.right }
                    as i32;
            let shock = if npc.shock > 0 { (2 * ((npc.shock as i32 / 2) & 1) - 1) as f32 } else { 0.0 };
            let (frame_x, frame_y) = frame.xy_interpolated(state.frame_time);

            batch.add_rect(
                interpolate_fix9_scale(npc.prev_x - off_x, npc.x - off_x, state.frame_time) + shock - frame_x,
                interpolate_fix9_scale(
                    npc.prev_y - npc.display_bounds.top as i32,
                    npc.y - npc.display_bounds.top as i32,
                    state.frame_time,
                ) - frame_y,
                &npc.anim_rect,
            );
        }

        batch.draw(ctx)?;

        Ok(())
    }
}
