use crate::float::*;

// ------------------------------------------------------------------------------------------------
// Plugins for read-only interactivity with the algorithms
// ------------------------------------------------------------------------------------------------

/// Plugin for interactivity.
/// Implement this trait to enable custom interactive behaviour.
///
/// If custom behaviour is not needed/wanted, then simply use struct 'DoNothing' as place-holder.
pub trait Plugin<T> {
    fn start(&mut self);
    fn peek(&mut self, iters: usize, x: &[T], p: &[T], alpha: T, residual: T);
    fn end(&mut self);
}

// ----------------------

/// A plugin implementation that does nothing.
/// This is the base struct for the decorator pattern employed for plugins.
pub struct DoNothing;

impl<T> Plugin<T> for DoNothing {
    #[inline(always)]
    fn start(&mut self) {}
    #[inline(always)]
    fn peek(&mut self, _iter: usize, _x: &[T], _p: &[T], _alpha: T, _residual: T) {}
    #[inline(always)]
    fn end(&mut self) {}
}

/// Template struct that implements the decorator pattern for plugins.
pub struct DoNothingDecorator<'a, T, P: Plugin<T>> {
    plugin: &'a mut P,
    phantom: core::marker::PhantomData<&'a T>,
}

impl<'a, T, P: Plugin<T>> DoNothingDecorator<'a, T, P> {
    pub fn extend(plugin: &'a mut P) -> Self {
        Self { plugin, phantom: core::marker::PhantomData }
    }
}

impl<T, P: Plugin<T>> Plugin<T> for DoNothingDecorator<'_, T, P> {
    #[inline(always)]
    fn start(&mut self) {
        self.plugin.start();
    }
    #[inline(always)]
    fn peek(&mut self, iter: usize, x: &[T], p: &[T], alpha: T, residual: T) {
        self.plugin.peek(iter, x, p, alpha, residual);
    }
    #[inline(always)]
    fn end(&mut self) {
        self.plugin.end();
    }
}

// --------------------------------------------------------------------------------------------

/// A plugin implementation that collects residuals using the decorator pattern.
///
/// This plugin collects the (2-Norm) value of || ***b*** - **A** ***x*** || at each iteration.
///
/// This struct does not own the data on which it operates, so the values are stored in an external mutable vector.
pub struct CollectResiduals<'a, T, P: Plugin<T>> {
    plugin: &'a mut P,
    residuals: &'a mut [T],
}

impl<'a, T, P: Plugin<T>> CollectResiduals<'a, T, P> {
    pub fn extend(plugin: &'a mut P, residuals: &'a mut [T]) -> Self {
        Self { plugin, residuals }
    }
}

impl<T: Copy, P: Plugin<T>> Plugin<T> for CollectResiduals<'_, T, P> {
    #[inline(always)]
    fn start(&mut self) {
        self.plugin.start();
    }
    #[inline(always)]
    fn peek(&mut self, iter: usize, x: &[T], p: &[T], alpha: T, residual: T) {
        self.residuals[iter] = residual;

        self.plugin.peek(iter, x, p, alpha, residual);
    }
    #[inline(always)]
    fn end(&mut self) {
        self.plugin.end();
    }
}

// --------------------------------------------------------------------------------------------

/// A plugin implementation that collects solution errors using the decorator pattern.
/// This plugin collects the value of || ***x*** - ***<ins>x</ins>*** || (2-Norm) at each
/// iteration, where ***<ins>x</ins>*** denotes the (known) solution of the problem.
///
/// This struct does not own the data on which it operates, so the values are stored in external mutable vectors.
pub struct CollectSolutionErrors<'a, T, P: Plugin<T>> {
    plugin: &'a mut P,
    //
    solution: &'a [T],
    errors: &'a mut [T],
    diff: &'a mut [T],
}

impl<'a, T, P: Plugin<T>> CollectSolutionErrors<'a, T, P> {
    pub fn extend(plugin: &'a mut P, solution: &'a [T], errors: &'a mut [T], diff: &'a mut [T]) -> Self {
        Self { plugin, solution, errors, diff }
    }
}

impl<T: Float, P: Plugin<T>> Plugin<T> for CollectSolutionErrors<'_, T, P> {
    #[inline(always)]
    fn start(&mut self) {
        self.plugin.start();
    }
    #[inline(always)]
    fn peek(&mut self, iter: usize, x: &[T], p: &[T], alpha: T, residual: T) {
        self.diff.reset();
        self.diff.copy_from_slice(x);
        self.diff.add(-T::one(), self.solution);

        self.errors[iter] = self.diff.norm_2();

        self.plugin.peek(iter, x, p, alpha, residual);
    }
    #[inline(always)]
    fn end(&mut self) {
        self.plugin.end();
    }
}

// --------------------------------------------------------------------------------------------

#[cfg(feature = "std")]
pub use std_plugins::*;

#[cfg(feature = "std")]
mod std_plugins {
    use super::*;

    /// A plugin implementation that measures execution time.
    /// This plugin uses 'Instant' and 'Duration' from the standard library.
    ///
    /// This plugin requires the feature 'std' enabled.
    pub struct StopWatch<'a, T> {
        plugin: &'a mut dyn Plugin<T>,
        start: std::time::Instant,
        elapsed_time: std::time::Duration,
    }

    impl<'a, T> StopWatch<'a, T> {
        pub fn extend(plugin: &'a mut dyn Plugin<T>) -> Self {
            Self { plugin, start: std::time::Instant::now(), elapsed_time: std::time::Duration::new(0, 0) }
        }
        pub fn elapsed_time_in_seconds(&self) -> f64 {
            self.elapsed_time.as_secs_f64()
        }
        pub fn elapsed_time_in_minutes(&self) -> f64 {
            self.elapsed_time.as_secs_f64() / 60.0
        }
    }

    impl<T> Plugin<T> for StopWatch<'_, T> {
        #[inline(always)]
        fn start(&mut self) {
            self.start = std::time::Instant::now();

            self.plugin.start();
        }
        #[inline(always)]
        fn peek(&mut self, iter: usize, x: &[T], p: &[T], alpha: T, residual: T) {
            self.plugin.peek(iter, x, p, alpha, residual);
        }
        #[inline(always)]
        fn end(&mut self) {
            self.elapsed_time = self.start.elapsed();

            self.plugin.end();
        }
    }

    // --------------------------------------------------------------------------------------------

    /// A plugin implementation that prints out some information at every iteration.
    ///
    /// This plugin requires the feature 'std' enabled.
    pub struct ConsoleLogger<'a, T> {
        plugin: &'a mut dyn Plugin<T>,
    }

    impl<'a, T> ConsoleLogger<'a, T> {
        pub fn extend(plugin: &'a mut dyn Plugin<T>) -> Self {
            Self { plugin }
        }
    }

    impl<T: Float> Plugin<T> for ConsoleLogger<'_, T> {
        fn start(&mut self) {
            self.plugin.start();
        }
        fn peek(&mut self, iter: usize, x: &[T], p: &[T], alpha: T, residual: T) {
            if iter.is_multiple_of(10) || iter == 1usize {
                println!();
                println!("{:>5}  {:^13}  {:^13}  {:^13}  {:^13}", "iter", "residual", "step", "alpha", "x[0]");
            }
            println!(
                "{iter:>5}  {residual:<13.7e}  {:<13.7e}  {:<13.7e}  {:<13.7e}",
                p.norm_2() * alpha.abs(),
                alpha.abs(),
                x[0]
            );

            self.plugin.peek(iter, x, p, alpha, residual);
        }
        fn end(&mut self) {
            self.plugin.end();
        }
    }
}
