use crate::base::constants::MINIMUM_PRECISION;
use crate::toolbox::matrix::linspace;
use crate::VX;
use std::ops::Index;

/// The time manager
///
/// # Definition
///
/// [`Time`] creates the time vector for the simulation. It is defined by a duration and a time step.
///
/// # Example
///
/// ```
/// use kalast::{DAY, Time};
///
/// let time = Time::new(50. * DAY, 30.); // duration and time step
/// ```
pub struct Time {
    /// The time vector
    _data: VX<f64>,
    _time_step: f64,
}

impl Time {
    /// Time constructor from duration and time step.
    pub fn new(duration: f64, step: f64) -> Self {
        let _data = linspace(0., duration, step);
        Self {
            _data,
            _time_step: step,
        }
    }

    /// Data getter.
    pub fn data(&self) -> &VX<f64> {
        &self._data
    }

    /// Data getter as mutable.
    pub fn data_mut(&mut self) -> &mut VX<f64> {
        &mut self._data
    }

    /// Get the size of the time vector
    pub fn size(&self) -> usize {
        self._data.len()
    }

    /// Get the last time of the time vector
    pub fn last(&self) -> f64 {
        self[self.size() - 1]
    }

    /// Get the duration of the time vector
    pub fn duration(&self) -> f64 {
        self.last() - self[0]
    }

    /// Get the time step that was used to create the data vector.
    pub fn time_step(&self) -> f64 {
        self._time_step
    }

    /// Get the time step between two specific iterations
    pub fn time_step_between(&self, start: usize, end: usize) -> f64 {
        self[end] - self[start]
    }

    /// Get the time step at the index with the one forward.
    pub fn time_step_backward(&self, index: usize) -> f64 {
        self[index] - self[index - 1]
    }

    /// Get the time step at the index with the one forward.
    pub fn time_step_forward(&self, index: usize) -> f64 {
        self[index + 1] - self[index]
    }

    /// Ensure the time vector contains the first exact start time of the last revolution.
    pub fn recompute_to_match_last_revolution(&mut self, revolution_period: f64) {
        let time_last_revolution_start = self.duration() - revolution_period;
        if time_last_revolution_start <= 0.0 {
            return;
        }
        let mut index_last_revolution_should_start = 0;
        for (index, time) in self.data().iter().enumerate() {
            if *time >= time_last_revolution_start {
                index_last_revolution_should_start = index;
                break;
            }
        }
        self._data = self._data.clone().insert_row(
            index_last_revolution_should_start,
            time_last_revolution_start,
        );
    }

    /// Check if the time step with the previous time at this index is different that the one that
    /// was used to build the vector.
    pub fn check_modified_time_step(&self, index: usize) -> bool {
        (self.time_step() - self.time_step_backward(index)).abs() < MINIMUM_PRECISION
    }
}

/// # Accessing data
impl Index<usize> for Time {
    type Output = f64;
    /// To access the time vector from bracket indexing
    fn index(&self, index: usize) -> &f64 {
        &self._data[index]
    }
}
