use rand::prelude::*;
use serde::{Deserialize, Serialize};
use crate::system::{Position, Storage};
use crate::system::spawner::Spawner;
use crate::system::sticker::Sticker;
use crate::system::walker::Walker;

#[derive(Serialize, Deserialize)]
pub struct HistoryLine<P: Position> {
    pub frame: usize,
    pub cluster_radius: f32,
    pub fd: f32,
    pub position: P,
}

pub struct DLASystem<R: Rng, P: Position, S: Storage<P>, W: Walker<P>, Sp: Spawner<P>, St: Sticker<P, S>> {
    rng: R,

    /*
     * These object encapsulate the behaviour of our particular model.
     */

    /*
     * The space, along with the chosen position choose the embedding space that the cluster grows
     * in.
     */
    space: S,

    /*
     * Walkers allow us to choose between different particle movement behaviours, eg random or
     * biased walker
     */
    walker: W,
    spawner: Sp,
    sticker: St,

    pub frame: usize,
    pub running: bool,
    pub history: Vec<HistoryLine<P>>,

    max_particles: usize,
    particles: Vec<P>,
    active_particle: Option<P>,

    add_ratio: f32,
    add_circle: f32,
    kill_ratio: f32,
    kill_circle: f32,
    cluster_radius: f32,
}

impl<R: Rng, P: Position, S: Storage<P>, W: Walker<P>, Sp: Spawner<P>, St: Sticker<P, S>> DLASystem<R, P, S, W, Sp, St> {
    pub fn new(rng: R, space: S, walker: W, spawner: Sp, sticker: St, max_particles: usize) -> Self {
        let mut sys = DLASystem {
            rng,
            max_particles,
            running: true,
            spawner,
            sticker,

            space,
            walker,

            frame: 0,
            particles: vec![],
            history: vec![],
            active_particle: None,

            add_ratio: 1.2,
            kill_ratio: 1.7,

            add_circle: 10.0,
            kill_circle: 20.0,
            cluster_radius: 0.0,
        };

        sys.deposit(&P::zero());

        sys
    }

    pub fn update(&mut self) {
        self.frame += 1;
        if self.active_particle.is_some() {
            self.move_particle();
        } else if self.particles.len() < self.max_particles {
            self.spawn_particle();
        } else {
            self.running = false;
        }
    }

    fn move_particle(&mut self) {
        let current_position = &self
            .active_particle
            .clone()
            .expect("No active particle");

        let next_position = self.walker.walk(&mut self.rng, current_position);
        let distance = next_position.abs();

        if distance > self.kill_circle {
            self.active_particle = None;
        } else if !self.space.is_occupied(&next_position) {
            if self.sticker.should_stick(&mut self.rng, &self.space, &next_position) {
                self.deposit(&next_position);
                self.active_particle = None;
                return;
            } else {
                self.active_particle.replace(next_position);
            }
        }
    }

    fn spawn_particle(&mut self) {
        let position = self.spawner.spawn(&mut self.rng, self.add_circle);

        if !self.space.is_occupied(&position) {
            self.active_particle = Some(position);
        }
    }

    fn deposit(&mut self, p0: &P) {
        self.particles.push(p0.clone());
        self.space.deposit(p0);

        let distance = p0.abs();
        if distance > self.cluster_radius {
            self.cluster_radius = distance;

            let new_add_circle = (self.cluster_radius * self.add_ratio).max(self.cluster_radius + 5.0);
            if self.add_circle < new_add_circle {
                self.add_circle = new_add_circle;
                self.kill_circle = self.kill_ratio * self.add_circle;
            }
        }

        self.history.push(HistoryLine { frame: self.frame, position: p0.clone(), cluster_radius: self.cluster_radius, fd: (self.particles.len() as f32).ln() / self.cluster_radius.ln() });
    }
}
