#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#![expect(rustdoc::missing_crate_level_docs)] // it's an example

use std::{
    collections::VecDeque,
    time::{Duration, Instant},
};

use eframe::egui;
use egui::Color32;
use egui_plot::{Legend, Line, PlotPoint, PlotPoints, Points};
use mean_estimators::Estimator;
use rand::{RngExt, distr::StandardUniform};

fn main() -> eframe::Result {
    env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
        ..Default::default()
    };
    eframe::run_native(
        "My egui App",
        options,
        Box::new(|_cc| Ok(Box::<MyApp>::default())),
    )
}

struct DFrame {
    sample: f32,
    ema_sample: f32,
    msmes_sample: f32,
    is_firefly: bool,
}

struct MyApp {
    msmes: mean_estimators::msme::MultiscaleEstimator,

    ema: mean_estimators::ema::ExpEstimatorS,

    frames: VecDeque<DFrame>,

    target_value: f32,
    noise_scale: f32,
    firefly_probability: f32,

    last_frame: Instant,
    pause: bool,
}

impl Default for MyApp {
    fn default() -> Self {
        Self {
            frames: VecDeque::new(),
            msmes: mean_estimators::msme::MultiscaleEstimator::new(0.5),
            ema: mean_estimators::ema::ExpEstimatorS {
                mean: 0.5,
                ..Default::default()
            },
            firefly_probability: 0.1,
            target_value: 0.5,
            noise_scale: 0.1,
            last_frame: Instant::now(),
            pause: false,
        }
    }
}

impl MyApp {
    fn push_sample(&mut self) {
        let noise_sample: f32 = rand::rng().sample(StandardUniform);
        let noise_sized = ((noise_sample * 2.0) - 1.0) * self.noise_scale;
        let is_firefly = {
            let s: f32 = rand::rng().sample(StandardUniform);
            s < self.firefly_probability
        };

        let sample = if is_firefly {
            //if this is a firefly, just use as-is
            noise_sample
        } else {
            //otherwise use it via distribution
            self.target_value + noise_sized
        };

        //now generate a new frame and push-it!
        let msmes_sample = self.msmes.push(sample);
        let ema_sample = self.ema.push(sample);
        self.frames.push_back(DFrame {
            sample,
            ema_sample,
            msmes_sample,
            is_firefly,
        });
    }
}

impl eframe::App for MyApp {
    fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
        //Generate a new sample after 100ms
        if self.last_frame.elapsed().as_millis() > 60 && !self.pause {
            self.last_frame = Instant::now();

            //16 samples per update
            for _ in 0..16 {
                self.push_sample();
            }

            while self.frames.len() > 250 {
                let _ = self.frames.pop_front();
            }
        }

        let _ = egui::CentralPanel::default().show_inside(ui, |ui| {
            let plot = egui_plot::Plot::new("Plot")
                .legend(Legend::default().title("Estimators"))
                .auto_bounds([true, false])
                .default_y_bounds(0.0, 1.0);

            ui.horizontal(|ui| {
                ui.vertical(|ui| {
                    ui.add(
                        egui::Slider::new(&mut self.firefly_probability, 0.0f32..=1.0f32)
                            .logarithmic(true)
                            .text("Firefly probability"),
                    );
                    ui.add(
                        egui::Slider::new(&mut self.noise_scale, 0.0f32..=0.5f32)
                            .text("Noise scale")
                            .logarithmic(true),
                    );
                });

                ui.vertical(|ui| {
                    ui.add(
                        egui::Slider::new(&mut self.msmes.short_window_blend, 0.000001..=0.15)
                            .text("MSME short window blend"),
                    );
                    ui.add(
                        egui::Slider::new(&mut self.target_value, 0.0..=1.0).text("Target Value"),
                    );
                });

                ui.vertical(|ui| {
                    ui.add(egui::Slider::new(&mut self.ema.smoothing, 0.0..=1.0).text("EMA α"));
                });

                if ui
                    .add(egui::Button::selectable(self.pause, "Pause"))
                    .clicked()
                {
                    self.pause = !self.pause;
                }
            });

            //Show the plot, and the target slider next to each other
            plot.show(ui, |plot_ui| {
                let plot_points_normal = self
                    .frames
                    .iter()
                    .enumerate()
                    .filter_map(|(index, point)| {
                        if !point.is_firefly {
                            Some(PlotPoint::new(index as f64, point.sample as f64))
                        } else {
                            None
                        }
                    })
                    .collect::<Vec<_>>();

                let normal_plot_points =
                    Points::new("Input - Noise", PlotPoints::Owned(plot_points_normal))
                        .radius(5.0f32)
                        .color(Color32::DARK_GREEN);

                let plot_points_fire_flies = self
                    .frames
                    .iter()
                    .enumerate()
                    .filter_map(|(index, point)| {
                        if point.is_firefly {
                            Some(PlotPoint::new(index as f64, point.sample as f64))
                        } else {
                            None
                        }
                    })
                    .collect::<Vec<_>>();

                let fireflies_plot_points = Points::new(
                    "Input - Fireflies",
                    PlotPoints::Owned(plot_points_fire_flies),
                )
                .radius(5.0f32)
                .color(Color32::DARK_RED);

                let ema_points = self
                    .frames
                    .iter()
                    .enumerate()
                    .map(|(index, d)| PlotPoint::new(index as f64, d.ema_sample as f64));

                let ema_line = Line::new(
                    "Exponential Moving Average ",
                    PlotPoints::Owned(ema_points.collect()),
                )
                .color(Color32::from_rgba_premultiplied(255, 100, 100, 200))
                .width(3.0f32);

                let msmes_points = self
                    .frames
                    .iter()
                    .enumerate()
                    .map(|(index, d)| PlotPoint::new(index as f64, d.msmes_sample as f64));

                let msmes_line =
                    Line::new("Msme Scalar", PlotPoints::Owned(msmes_points.collect()))
                        .color(Color32::from_rgba_premultiplied(100, 100, 255, 200))
                        .width(3.0f32);

                plot_ui.points(normal_plot_points);
                plot_ui.points(fireflies_plot_points);
                plot_ui.line(ema_line);
                plot_ui.line(msmes_line);
            });
        });

        ui.ctx().request_repaint_after(Duration::from_millis(60));
    }
}
