use bevy_ecs::prelude::*;
use libm::{cosf, sinf};

use crate::components::*;
use crate::protocol::*;

#[derive(Resource, Default)]
pub struct SimTime {
    pub dt: f32,
    pub time: f32,
}

pub struct Simulation {
    pub player: Option<Entity>,
    pub tick: u64,
    pub time: f32,
    pub world: World,
}

impl Simulation {
    pub fn new() -> Self {
        let mut world = World::new();
        world.insert_resource(SimTime::default());
        Self {
            player: None,
            tick: 0,
            time: 0.0,
            world,
        }
    }

    pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) {
        self.tick += 1;
        self.time += dt;

        if let Some(mut sim_time) = self.world.get_resource_mut::<SimTime>() {
            sim_time.dt = dt;
            sim_time.time = self.time;
        }

        for input in inputs {
            self.apply_input(input, dt);
        }
    }

    fn apply_input(&mut self, input: &pxl8_input_msg, dt: f32) {
        let Some(player) = self.player else { return };
        let speed = 200.0;

        let yaw = input.yaw;
        let sin_yaw = sinf(yaw);
        let cos_yaw = cosf(yaw);

        let move_x = input.move_x;
        let move_y = input.move_y;
        let len_sq = move_x * move_x + move_y * move_y;

        if len_sq > 0.0 {
            let len = libm::sqrtf(len_sq);
            let norm_x = move_x / len;
            let norm_y = move_y / len;

            let forward_x = -sin_yaw * norm_y * speed * dt;
            let forward_z = -cos_yaw * norm_y * speed * dt;
            let strafe_x = cos_yaw * norm_x * speed * dt;
            let strafe_z = -sin_yaw * norm_x * speed * dt;

            if let Some(mut pos) = self.world.get_mut::<Position>(player) {
                pos.x += forward_x + strafe_x;
                pos.z += forward_z + strafe_z;
            }
        }

        if let Some(mut rot) = self.world.get_mut::<Rotation>(player) {
            rot.yaw = yaw;
            rot.pitch = (rot.pitch - input.look_dy * 0.008).clamp(-1.5, 1.5);
        }
    }

    pub fn spawn_entity(&mut self) -> Entity {
        self.world.spawn((
            Position::default(),
            Rotation::default(),
            Velocity::default(),
        )).id()
    }

    pub fn spawn_player(&mut self, x: f32, y: f32, z: f32) -> Entity {
        let entity = self.world.spawn((
            Player,
            Position { x, y, z },
            Rotation::default(),
            Velocity::default(),
            Health::default(),
        )).id();
        self.player = Some(entity);
        entity
    }

    pub fn generate_snapshot<F>(&mut self, player_id: u64, mut writer: F)
    where
        F: FnMut(&pxl8_entity_state),
    {
        let mut query = self.world.query::<(Entity, &Position, &Rotation)>();
        for (entity, pos, rot) in query.iter(&self.world) {
            let mut state = pxl8_entity_state {
                entity_id: entity.to_bits(),
                userdata: [0u8; 56],
            };

            let bytes = &mut state.userdata;
            bytes[0..4].copy_from_slice(&pos.x.to_be_bytes());
            bytes[4..8].copy_from_slice(&pos.y.to_be_bytes());
            bytes[8..12].copy_from_slice(&pos.z.to_be_bytes());
            bytes[12..16].copy_from_slice(&rot.yaw.to_be_bytes());
            bytes[16..20].copy_from_slice(&rot.pitch.to_be_bytes());

            writer(&state);
        }

        let _ = player_id;
    }

    pub fn entity_count(&self) -> usize {
        self.world.entities().len() as usize
    }
}

impl Default for Simulation {
    fn default() -> Self {
        Self::new()
    }
}
