use crate::{
    grid::{Grid, PopulationConfig},
    util::wrap,
};

use rand::{seq::SliceRandom, Rng};
use std::f32::consts::TAU;
use fastapprox::faster::{cos, sin};
use std::fmt::{Display, Formatter};

// A single Physarum agent. The x and y positions are continuous, hence we use floating point numbers instead of integers.
#[derive(Debug)]
pub struct Agent {
    pub x: f32,
    pub y: f32,
    pub angle: f32,
    pub population_id: usize,
    pub i: usize,
}

impl Display for Agent {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{{\n (x,y): ({},{})\n angle: {}\n population id: {}, i: {} }}",
            self.x,
            self.y,
            self.angle,
            self.population_id,
            self.i,
        )
    }
}

impl Agent {
    // Construct a new agent with random parameters.
    pub fn new<R: Rng + ?Sized>(width: usize, height: usize, id: usize, rng: &mut R, i: usize) -> Self {
        let (x, y, angle) = rng.gen::<(f32, f32, f32)>();
        Agent {
            x: x * width as f32,
            y: y * height as f32,
            angle: angle * TAU,
            population_id: id,
            i,
        }
    }

    #[inline]
    pub fn tick(&mut self, grid: &Grid) {
        let (width, height) = (grid.width, grid.height);
        let PopulationConfig {
            sensor_distance,
            sensor_angle,
            rotation_angle,
            step_distance,
            ..
        } = grid.config;

        let xc = self.x + cos(self.angle) * sensor_distance;
        let yc = self.y + sin(self.angle) * sensor_distance;

        let agent_add_sens = self.angle + sensor_angle;
        let agent_sub_sens = self.angle - sensor_angle;

        let xl = self.x + cos(agent_sub_sens) * sensor_distance;
        let yl = self.y + sin(agent_sub_sens) * sensor_distance;
        let xr = self.x + cos(agent_add_sens) * sensor_distance;
        let yr = self.y + sin(agent_add_sens) * sensor_distance;

        // We sense from the buffer because this is where we previously combined data from all the grid.
        let center = grid.get_buf(xc, yc);
        let left = grid.get_buf(xl, yl);
        let right = grid.get_buf(xr, yr);

        // Rotate and move logic
        let mut rng = rand::thread_rng();
        let mut direction: f32 = 0.0;

        if (center > left) && (center > right) {
            direction = 0.0;
        } else if (center < left) && (center < right) {
            direction = *[-1.0, 1.0].choose(&mut rng).unwrap();
        } else if left < right {
            direction = 1.0;
        } else if right < left {
            direction = -1.0;
        }

        let delta_angle = rotation_angle * direction;

        self.angle = wrap(self.angle + delta_angle, TAU);
        self.x = wrap(
            self.x + step_distance * cos(self.angle),
            width as f32,
        );
        self.y = wrap(
            self.y + step_distance * sin(self.angle),
            height as f32,
        );
    }
}

impl Clone for Agent {
    fn clone(&self) -> Agent {
        Agent {
            x: self.x,
            y: self.y,
            angle: self.angle,
            population_id: self.population_id,
            i: self.i,
        }
    }
}

impl PartialEq for Agent {
    fn eq(&self, other: &Self) -> bool {
        self.x == other.x
            && self.y == other.y
            && self.angle == other.angle
            && self.population_id == other.population_id
            && self.i == other.i
    }
}