use std::f32::consts::PI;
use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};

use libremetaverse_types::{Color4, Quaternion, Vector3};

use crate::{
    Error, LiveParticle, PrimitiveParticleSystem,
    PrimitiveParticleSystemParticleDataFlags as DataFlags,
    PrimitiveParticleSystemParticleFlags as ParticleFlags,
    PrimitiveParticleSystemSourcePattern as SourcePattern,
};

const MAX_PARTICLES: usize = 4096;

struct State {
    source_position: Vector3,
    source_rotation: Quaternion,
    target_position: Vector3,
    wind: Vector3,
    system_age: f32,
    burst_timer: f32,
    active: bool,
    particles: Vec<LiveParticle>,
    random: u64,
}

pub struct ParticleSimulator {
    system: PrimitiveParticleSystem,
    state: Mutex<State>,
}

impl ParticleSimulator {
    pub fn new(system: PrimitiveParticleSystem, seed: Option<i32>) -> Result<Self, Error> {
        let random = match seed.unwrap_or_default() {
            0 => {
                let elapsed = SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .map_err(|_| Error::InvalidOperation)?;
                elapsed.as_secs() ^ u64::from(elapsed.subsec_nanos())
            }
            seed => seed as i64 as u64,
        }
        .max(1);
        Ok(Self {
            system,
            state: Mutex::new(State {
                source_position: Vector3::zero(),
                source_rotation: Quaternion::identity(),
                target_position: Vector3::zero(),
                wind: Vector3::zero(),
                system_age: 0.0,
                burst_timer: 0.0,
                active: true,
                particles: Vec::with_capacity(256),
                random,
            }),
        })
    }

    pub fn get_particles(&self) -> Result<Vec<LiveParticle>, Error> {
        Ok(self.lock().particles.clone())
    }

    pub fn tick(&self, dt: f32) -> Result<(), Error> {
        if !dt.is_finite() {
            return Err(Error::Argument);
        }
        if dt <= 0.0 {
            return Ok(());
        }
        let mut state = self.lock();
        state.system_age += dt;
        if self.system.max_age > 0.0 && state.system_age > self.system.max_age {
            state.active = false;
        }

        let flags = self.system.part_data_flags.0;
        let wind_enabled = flags & DataFlags::WIND.0 != 0;
        let bounce = flags & DataFlags::BOUNCE.0 != 0;
        let target_pos = flags & DataFlags::TARGET_POS.0 != 0;
        let target_linear = flags & DataFlags::TARGET_LINEAR.0 != 0;
        let interp_color = flags & DataFlags::INTERP_COLOR.0 != 0;
        let interp_scale = flags & DataFlags::INTERP_SCALE.0 != 0;
        let wind = state.wind;
        let source = state.source_position;
        let target = state.target_position;

        let mut index = state.particles.len();
        while index > 0 {
            index -= 1;
            let particle = &mut state.particles[index];
            particle.age += dt;
            if particle.age >= self.system.part_max_age {
                state.particles.remove(index);
                continue;
            }
            particle.velocity.x += self.system.part_acceleration.x * dt;
            particle.velocity.y += self.system.part_acceleration.y * dt;
            particle.velocity.z += self.system.part_acceleration.z * dt;
            if wind_enabled {
                particle.velocity.x += (wind.x - particle.velocity.x) * dt * 2.0;
                particle.velocity.y += (wind.y - particle.velocity.y) * dt * 2.0;
                particle.velocity.z += (wind.z - particle.velocity.z) * dt * 2.0;
            }
            particle.position.x += particle.velocity.x * dt;
            particle.position.y += particle.velocity.y * dt;
            particle.position.z += particle.velocity.z * dt;
            if bounce && particle.position.z < 0.0 {
                particle.position.z = 0.0;
                particle.velocity.z *= -0.5;
            }
            if target_pos || target_linear {
                let direction = normalize(Vector3 {
                    x: target.x - source.x - particle.position.x,
                    y: target.y - source.y - particle.position.y,
                    z: target.z - source.z - particle.position.z,
                });
                if target_linear {
                    particle.velocity = scale(
                        direction,
                        (self.system.burst_speed_min + self.system.burst_speed_max) * 0.5,
                    );
                } else {
                    particle.velocity.x += direction.x * dt * 2.0;
                    particle.velocity.y += direction.y * dt * 2.0;
                    particle.velocity.z += direction.z * dt * 2.0;
                }
            }
            let age = if self.system.part_max_age > 0.0 {
                (particle.age / self.system.part_max_age).clamp(0.0, 1.0)
            } else {
                0.0
            };
            particle.color = if interp_color {
                Color4::lerp(
                    self.system.part_start_color,
                    self.system.part_end_color,
                    age,
                )?
            } else {
                self.system.part_start_color
            };
            particle.scale_x = if interp_scale {
                lerp(
                    self.system.part_start_scale_x,
                    self.system.part_end_scale_x,
                    age,
                )
            } else {
                self.system.part_start_scale_x
            };
            particle.scale_y = if interp_scale {
                lerp(
                    self.system.part_start_scale_y,
                    self.system.part_end_scale_y,
                    age,
                )
            } else {
                self.system.part_start_scale_y
            };
            particle.glow = lerp(self.system.part_start_glow, self.system.part_end_glow, age);
            particle.normalized_age = age;
        }

        if state.active && state.system_age >= self.system.start_age {
            state.burst_timer += dt;
            let rate = self.system.burst_rate.max(0.01);
            while state.burst_timer >= rate {
                state.burst_timer -= rate;
                self.emit_burst(&mut state);
            }
        }
        Ok(())
    }

    pub fn is_active(&self) -> bool {
        self.lock().active
    }

    pub fn source_position(&self) -> Vector3 {
        self.lock().source_position
    }

    pub fn set_source_position(&mut self, value: Vector3) {
        self.lock().source_position = value;
    }

    pub fn source_rotation(&self) -> Quaternion {
        self.lock().source_rotation
    }

    pub fn set_source_rotation(&mut self, value: Quaternion) {
        self.lock().source_rotation = value;
    }

    pub fn system_age(&self) -> f32 {
        self.lock().system_age
    }

    pub fn target_position(&self) -> Vector3 {
        self.lock().target_position
    }

    pub fn set_target_position(&mut self, value: Vector3) {
        self.lock().target_position = value;
    }

    pub fn wind(&self) -> Vector3 {
        self.lock().wind
    }

    pub fn set_wind(&mut self, value: Vector3) {
        self.lock().wind = value;
    }

    fn emit_burst(&self, state: &mut State) {
        for _ in 0..self.system.burst_part_count {
            if state.particles.len() >= MAX_PARTICLES {
                break;
            }
            let position = self.initial_position(state);
            let velocity = self.initial_velocity(state);
            state.particles.push(LiveParticle {
                age: 0.0,
                color: self.system.part_start_color,
                glow: self.system.part_start_glow,
                normalized_age: 0.0,
                position,
                scale_x: self.system.part_start_scale_x,
                scale_y: self.system.part_start_scale_y,
                velocity,
            });
        }
    }

    fn initial_position(&self, state: &mut State) -> Vector3 {
        if self.system.burst_radius <= 0.0 {
            return Vector3::zero();
        }
        let radius = random_f32(state) * self.system.burst_radius;
        let theta = random_f32(state) * 2.0 * PI;
        let phi = (random_f32(state) - 0.5) * PI;
        Vector3 {
            x: radius * phi.cos() * theta.cos(),
            y: radius * phi.cos() * theta.sin(),
            z: radius * phi.sin(),
        }
    }

    fn initial_velocity(&self, state: &mut State) -> Vector3 {
        let speed = lerp(
            self.system.burst_speed_min,
            self.system.burst_speed_max,
            random_f32(state),
        );
        let mut direction = if self.system.pattern == SourcePattern::DROP {
            Vector3::zero()
        } else if self.system.pattern == SourcePattern::EXPLODE {
            random_unit_sphere(state)
        } else if self.system.pattern == SourcePattern::ANGLE
            || self.system.pattern == SourcePattern::ANGLE_CONE
        {
            cone_direction(state, self.system.inner_angle, self.system.outer_angle)
        } else if self.system.pattern == SourcePattern::ANGLE_CONE_EMPTY {
            cone_direction(state, self.system.outer_angle, PI)
        } else {
            Vector3::unit_z()
        };
        if self.system.part_flags & ParticleFlags::OBJECT_RELATIVE.0 != 0
            && direction != Vector3::zero()
        {
            direction = Vector3::mul_with_vector3_quaternion(direction, state.source_rotation);
        }
        scale(direction, speed)
    }

    fn lock(&self) -> std::sync::MutexGuard<'_, State> {
        self.state
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
    }
}

fn random_f32(state: &mut State) -> f32 {
    let mut value = state.random;
    value ^= value << 13;
    value ^= value >> 7;
    value ^= value << 17;
    state.random = value;
    (value >> 40) as f32 / (1_u32 << 24) as f32
}

fn random_unit_sphere(state: &mut State) -> Vector3 {
    let theta = random_f32(state) * 2.0 * PI;
    let phi = (2.0 * random_f32(state) - 1.0).acos();
    Vector3 {
        x: phi.sin() * theta.cos(),
        y: phi.sin() * theta.sin(),
        z: phi.cos(),
    }
}

fn cone_direction(state: &mut State, inner: f32, outer: f32) -> Vector3 {
    let angle = lerp(inner, outer, random_f32(state));
    let rotation = random_f32(state) * 2.0 * PI;
    Vector3 {
        x: angle.sin() * rotation.cos(),
        y: angle.sin() * rotation.sin(),
        z: angle.cos(),
    }
}

fn normalize(value: Vector3) -> Vector3 {
    let length = (value.x * value.x + value.y * value.y + value.z * value.z).sqrt();
    if length > 0.0 && length.is_finite() {
        scale(value, length.recip())
    } else {
        Vector3::zero()
    }
}

const fn scale(value: Vector3, amount: f32) -> Vector3 {
    Vector3 {
        x: value.x * amount,
        y: value.y * amount,
        z: value.z * amount,
    }
}

fn lerp(start: f32, end: f32, amount: f32) -> f32 {
    start + (end - start) * amount
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn emits_ages_and_expires_particles_deterministically() {
        let mut system = PrimitiveParticleSystem::default();
        system.pattern = SourcePattern::DROP;
        system.burst_part_count = 2;
        system.burst_rate = 0.1;
        system.part_max_age = 0.25;
        system.part_start_scale_x = 1.0;
        system.part_start_scale_y = 2.0;
        let simulator = ParticleSimulator::new(system, Some(7)).unwrap();
        simulator.tick(0.1).unwrap();
        assert_eq!(simulator.get_particles().unwrap().len(), 2);
        simulator.tick(0.1).unwrap();
        assert_eq!(simulator.get_particles().unwrap().len(), 4);
        simulator.tick(0.2).unwrap();
        assert_eq!(simulator.get_particles().unwrap().len(), 6);
    }
}
