// TODO
// - AutoImport ausschalten
// - Reduziertes Integral aus Paper zu numerisch berechenbarer Form umformen

use indicatif;
use std::time::Instant;

use egui_plot::{log_grid_spacer, AxisHints, GridInput, GridMark, Legend, Line, Plot};
use num::{complex::ComplexFloat, Float};

// use scc::HashMap;

mod calc;
mod gq_storage;

struct App {
    plot_data: Vec<PlotCurve>,
    plots_available: [String; 7],
    calc_cache: calc::Cache,
    scaling_type: ScalingType,
    a: f64,
    b: f64,
    n_points: u32,
    n_omnes: u32,
    n_phi0: u32,
    use_tan_omnes: bool,
    use_tan_phi0: bool,
    use_xsub_phi0: bool,
    use_reduced_integrand: bool,
    use_cutoff: bool,
    cutoff_power: f64,
    cutoff_s: f64,
}

#[derive(Clone)]
struct PlotCurve {
    points: Vec<[f64; 2]>,
    name: String,
}

impl Default for App {
    fn default() -> Self {
        App {
            plot_data: Vec::default(),
            plots_available: [
                "Delta".to_string(),
                "Omnes".to_string(),
                "Phi0".to_string(),
                "Phi0 / delta".to_string(),
                "s^cutoff_power".to_string(),
                "phi0 integrand".to_string(),
                "Phi0_cut / Phi0".to_string(),
            ],
            calc_cache: calc::Cache::default(),
            scaling_type: ScalingType::default(),
            a: calc::S0,
            b: 9.0,
            n_points: 500,
            n_omnes: 500,
            n_phi0: 500,
            
            // Substitutions params
            use_tan_omnes: true,
            use_tan_phi0: true,
            use_xsub_phi0: true,
            use_reduced_integrand: true,

            // Cutoff params
            use_cutoff: false,
            cutoff_power: -1.0,
            cutoff_s: 100.0, 
        }
    }
}

#[derive(Debug, Default, PartialEq, Clone, Copy)]
enum ScalingType {
    Logarithmic,
    #[default]
    Linear,
}

impl eframe::App for App {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            let cur_scaling_type = self.scaling_type;

            ui.vertical(|ui| {
                ui.horizontal(|ui| {
                    ui.vertical(|ui| {
                        ui.label(egui::RichText::new("Boundary values"));
                        ui.add_space(10.0);
                        egui::Grid::new("grid1")
                            .min_col_width(125.0)
                            .show(ui, |ui| {
                                ui.label("calc interval start:").on_hover_text(
                                    "the lower bound of the interval that functions are calculated in",
                                );
                                float_text_edit_singleline(ui, &mut self.a, true);
                                ui.end_row();

                                ui.label("calc interval end:").on_hover_text(
                                    "the upper bound of the interval that functions are calculated in",
                                );
                                float_text_edit_singleline(ui, &mut self.b, true);
                                ui.end_row();

                                ui.label("N for omnes:").on_hover_text(
                                    "the number of points used in calculation of omnes function",
                                );
                                int_text_edit_singleline(ui, &mut self.n_omnes, true);
                                ui.end_row();

                                ui.label("N for phi0:").on_hover_text(
                                    "the number of points used in calculation of phi0 function",
                                );
                                int_text_edit_singleline(ui, &mut self.n_phi0, true);
                                ui.end_row();

                                ui.label("number of plotpoints:")
                                    .on_hover_text("the number of points shown in the plot");
                                int_text_edit_singleline(ui, &mut self.n_points, true);
                                ui.end_row();
                            });
                    });
                    ui.separator();

                    ui.vertical(|ui| {
                        ui.label(egui::RichText::new("Use substitutions"));
                        ui.add_space(10.0);
                        let omnes_tan_cb =
                            ui.checkbox(&mut self.use_tan_omnes, "Use tan()-subst. for Omnes ('ot')");
                        if omnes_tan_cb.clicked() {
                            self.calc_cache.omnes_lut = dashmap::DashMap::with_hasher(Default::default()); // TODO split omnes_lut so that this is no longer necessary
                        }
                        ui.checkbox(&mut self.use_tan_phi0, "Use tan()-subst. for phi0 ('pt')");
                        ui.checkbox(&mut self.use_xsub_phi0, "Use second subst. for phi0 ('ss')");
                        ui.checkbox(
                            &mut self.use_reduced_integrand,
                            "Use reduced integrand for phi0",
                        );
                        egui::ComboBox::from_label("pick y-scaling")
                            .selected_text(format!("{:?}", self.scaling_type))
                            .show_ui(ui, |ui| {
                                ui.selectable_value(
                                    &mut self.scaling_type,
                                    ScalingType::Logarithmic,
                                    "log10",
                                );
                                ui.selectable_value(
                                    &mut self.scaling_type,
                                    ScalingType::Linear,
                                    "linear",
                                );
                            });

                        ui.horizontal(|ui| {
                            let button_clear = ui.button("Clear canvas");
                            if button_clear.clicked() {
                                self.plot_data = Vec::new();
                            }
                            egui::widgets::global_dark_light_mode_switch(ui);
                        });
                    });

                    ui.separator();

                    ui.with_layout(egui::Layout::top_down(egui::Align::Min),|ui| {
                        ui.label(egui::RichText::new("Omnes Continuation with polynomial")).on_hover_text(
                            "If activated, from a user chosen cutoff value, the Omnes function will be replaced with s to a user chosen power, starting at the last Omnes value of the numerically calculated values",
                        );
                        ui.checkbox(&mut self.use_cutoff, "Use cutoff for phi0");
                        ui.add_space(10.0);
                        egui::Grid::new("grid2").min_col_width(125.0).show(ui, |ui| {
                            ui.label("Power of cont. polynomial").on_hover_text("Omnes will be replaced with s^{this power}.");
                            float_text_edit_singleline(ui, &mut self.cutoff_power, true);
                            ui.end_row();

                            ui.label("Cutoff point").on_hover_text("Omnes will be replaced from this s-value onward.");
                            float_text_edit_singleline(ui, &mut self.cutoff_s, true);
                            ui.end_row();
                        });
                    })
                });

                ui.separator();

                ui.label("You can calculate the following functions:");

                ui.horizontal(|ui| {
                    for i in 0..self.plots_available.len() {
                        let button = ui.button(format!("{}", self.plots_available[i].clone()));
                        if button.clicked() {
                            let x_values: Vec<f64> = (0..self.n_points)
                                .map(|x| -> f64 {
                                    f64::from(x) * ((self.b - self.a) / f64::from(self.n_points))
                                        + self.a
                                })
                                .collect();
                            match i {
                                0 => {
                                    let y_values_delta: Vec<f64> = if self.scaling_type
                                        == ScalingType::Linear
                                    {
                                        x_values
                                            .iter()
                                            .map(|&x| calc::delta_with_lut(&mut self.calc_cache, x))
                                            .collect()
                                    } else {
                                        x_values
                                            .iter()
                                            .map(|&x| {
                                                calc::delta_with_lut(&mut self.calc_cache, x)
                                                    .log10()
                                            })
                                            .collect()
                                    };

                                    self.plot_data.push(PlotCurve {
                                        points: x_values
                                            .iter()
                                            .zip(y_values_delta.iter())
                                            .map(|(&x, &y)| [x.powf(0.5), y])
                                            .collect(),
                                        name: format!("(#{}) Delta", self.plot_data.len() + 1),
                                    })
                                }
                                1 => {
                                    let y_values_omnes: Vec<f64> = if self.scaling_type
                                        == ScalingType::Linear
                                    {
                                        print!("Calculating all plot points: ");
                                        let bar =
                                            indicatif::ProgressBar::new(u64::from(self.n_points));
                                        let res = x_values
                                            .iter()
                                            .map(|&x| {
                                                bar.inc(1);
                                                calc::omnes(
                                                    &mut self.calc_cache,
                                                    x,
                                                    self.n_omnes,
                                                    self.use_tan_omnes,
                                                )
                                                .abs()
                                                .powi(2)
                                            })
                                            .collect();
                                        bar.finish();
                                        res
                                    } else {
                                        x_values
                                            .iter()
                                            .map(|&x| {
                                                calc::omnes(
                                                    &mut self.calc_cache,
                                                    x,
                                                    self.n_omnes,
                                                    self.use_tan_omnes,
                                                )
                                                .abs()
                                                .powi(2)
                                                .log10()
                                            })
                                            .collect()
                                    };
                                    self.plot_data.push(PlotCurve {
                                        points: x_values
                                            .iter()
                                            .zip(y_values_omnes.iter())
                                            .map(|(&x, &y)| [x.powf(0.5), y])
                                            .collect(),
                                        name: format!(
                                            "(#{}) Omnes, {} ot, n_o={}",
                                            self.plot_data.len() + 1,
                                            if self.use_tan_omnes { "w/" } else { "no " },
                                            self.n_omnes
                                        ),
                                    })
                                }
                                2 => {
                                    let y_values_phi0: Vec<f64> = {
                                        let bar =
                                        indicatif::ProgressBar::new(u64::from(self.n_points));
                                        let t0 = Instant::now();
                                        let values = x_values
                                            .iter()
                                            .map(|&x| {
                                                bar.inc(1);
                                                let val = calc::phi0(
                                                    &mut self.calc_cache,
                                                    x,
                                                    self.n_omnes,
                                                    self.n_phi0,
                                                    self.use_tan_omnes,
                                                    self.use_tan_phi0,
                                                    self.use_xsub_phi0,
                                                    self.use_reduced_integrand,
                                                    x == self.a,
                                                    self.use_cutoff,
                                                    self.cutoff_power,
                                                    self.cutoff_s,
                                                );
                                                match self.scaling_type {
                                                    ScalingType::Linear => {
                                                        val
                                                    }
                                                    ScalingType::Logarithmic => {
                                                        val.log10()
                                                    }
                                                }
                                            }
                                        ).collect();
                                        println!("{:?}", t0.elapsed());
                                        values
                                    };
                                    self.plot_data.push(PlotCurve {
                                        points: x_values
                                            .iter()
                                            .zip(y_values_phi0.iter())
                                            .map(|(&x, &y)| [x.powf(0.5), y])
                                            .collect(),
                                        name: format!(
                                            "(#{}) phi0, {} ot, {} pt, {} ss, n_o={}, n_p={}",
                                            self.plot_data.len() + 1,
                                            if self.use_tan_omnes { "w/" } else { "no " },
                                            if self.use_tan_phi0 { "w/" } else { "no " },
                                            if self.use_xsub_phi0 { "w/" } else { "no " },
                                            self.n_omnes,
                                            self.n_phi0
                                        ),
                                    });
                                }
                                3 => {
                                    let y_values_phi0: Vec<f64> = {
                                        x_values
                                            .iter()
                                            .map(|&x| {
                                                let val = calc::phi0(
                                                    &mut self.calc_cache,
                                                    x,
                                                    self.n_omnes,
                                                    self.n_phi0,
                                                    self.use_tan_omnes,
                                                    self.use_tan_phi0,
                                                    self.use_xsub_phi0,
                                                    self.use_reduced_integrand,
                                                    x == self.a,
                                                    self.use_cutoff,
                                                    self.cutoff_power,
                                                    self.cutoff_s,
                                                ) / calc::delta_with_lut(
                                                    &mut self.calc_cache,
                                                    x,
                                                );
                                                match self.scaling_type {
                                                    ScalingType::Linear => {
                                                        val
                                                    }
                                                    ScalingType::Logarithmic => {
                                                        val.log10()
                                                    }
                                                }
                                            })
                                            .collect()
                                        };
                                    self.plot_data.push(PlotCurve {
                                        points: x_values
                                            .iter()
                                            .zip(y_values_phi0.iter())
                                            .map(|(&x, &y)| [x.powf(0.5), y])
                                            .collect(),
                                        name: format!(
                                            "(#{}) phi0 / delta, {} ot, {} pt, {} ss, n_o={}, n_p={}",
                                            self.plot_data.len() + 1,
                                            if self.use_tan_omnes { "w/" } else { "no " },
                                            if self.use_tan_phi0 { "w/" } else { "no " },
                                            if self.use_xsub_phi0 { "w/" } else { "no " },
                                            self.n_omnes,
                                            self.n_phi0
                                        ),

                                    });
                                }
                                4 => {
                                    let cutoff_factor = calc::omnes(&mut self.calc_cache, self.cutoff_s, self.n_omnes, self.use_tan_omnes).abs() * self.cutoff_s;
                                    println!("{:?}", self.scaling_type);
                                    let y_values_func: Vec<f64> = x_values.iter().map(|&x| {
                                        let val = (cutoff_factor*x.powf(self.cutoff_power)).powi(2);
                                        match self.scaling_type {
                                            ScalingType::Linear => {
                                                val
                                            }
                                            ScalingType::Logarithmic => {
                                                val.log10()
                                            }
                                        }
                                    }).collect();
                                    self.plot_data.push(PlotCurve {
                                        points: x_values
                                            .iter()
                                            .zip(y_values_func.iter())
                                            .map(|(&x, &y)| [x.powf(0.5), y])
                                            .collect(),
                                        name:"cont. polynomial".to_string()
                                    });
                                }
                                5 => {
                                    let cutoff_factor = calc::omnes(&mut self.calc_cache, self.cutoff_s, self.n_omnes, self.use_tan_omnes) * self.cutoff_s;
                                    println!("{:?}", cutoff_factor);
                                    let y_values_func: Vec<f64> = x_values
                                        .iter()
                                        .map(|&x| { 
                                            let val = calc::phi0_integrand(
                                                &mut self.calc_cache, 
                                                x, 
                                                2.0, 
                                                self.n_omnes, 
                                                self.use_tan_omnes, 
                                                self.use_tan_phi0, 
                                                self.use_xsub_phi0, 
                                                self.use_reduced_integrand, 
                                                self.use_cutoff, 
                                                self.cutoff_power, 
                                                self.cutoff_s, 
                                                cutoff_factor);
                                            match self.scaling_type {
                                                ScalingType::Linear => {
                                                    val
                                                }
                                                ScalingType::Logarithmic => {
                                                    val.log10()
                                                }
                                            }
                                        }
                                    ).collect();
                                    self.plot_data.push(PlotCurve {
                                        points: x_values
                                            .iter()
                                            .zip(y_values_func.iter())
                                            .map(|(&x, &y)| [x.powf(0.5), y])
                                            .collect(),
                                        name: format!(
                                            "(#{}) phi0 integrand",
                                            self.plot_data.len() + 1,
                                        ),
                                    })
                                }
                                6 => {
                                    let bar = indicatif::ProgressBar::new(u64::from(self.n_points));
                                    let y_values: Vec<f64> =
                                        x_values.iter().map(|&x| {
                                            bar.inc(1);
                                            let phi0_nocut = calc::phi0(
                                                &mut self.calc_cache,
                                                3.0,
                                                self.n_omnes,
                                                self.n_phi0,
                                                self.use_tan_omnes,
                                                self.use_tan_phi0,
                                                self.use_xsub_phi0,
                                                self.use_reduced_integrand,
                                                false,
                                                false,
                                                self.cutoff_power,
                                                x 
                                            );
                                            let phi0_cut = calc::phi0(
                                                &mut self.calc_cache,
                                                3.0,
                                                self.n_omnes,
                                                self.n_phi0,
                                                self.use_tan_omnes,
                                                self.use_tan_phi0,
                                                self.use_xsub_phi0,
                                                self.use_reduced_integrand,
                                                false,
                                                true,
                                                self.cutoff_power,
                                                x
                                            );
                                            match self.scaling_type {
                                                ScalingType::Linear => {
                                                    (phi0_cut / phi0_nocut - 1.0).abs()
                                                }
                                                ScalingType::Logarithmic => {
                                                    (phi0_cut / phi0_nocut - 1.0).abs().log10()
                                                }
                                            }
                                        }).collect();
                                    self.plot_data.push(PlotCurve {
                                        points: x_values
                                            .iter()
                                            .zip(y_values.iter())
                                            .map(|(&x, &y)| [x.powf(0.5), y])
                                            .collect(),
                                        name: format!(
                                            "(#{}) phi0_cut / phi0, cutoff power: {}",
                                            self.plot_data.len() + 1,
                                            self.cutoff_power,
                                        ),
                                    });
                                }
                                7_usize.. => {
                                    panic!("This button has no assigned purpose!");
                                }
                            }
                        }
                    }
                });
            });

            if self.scaling_type != cur_scaling_type {
                for curve in self.plot_data.iter_mut() {
                    for [_x, y] in curve.points.iter_mut() {
                        if self.scaling_type == ScalingType::Linear {
                            *y = 10.0f64.powf(*y);
                        } else if self.scaling_type == ScalingType::Logarithmic {
                            *y = y.log10();
                        }
                    }
                }
            }

            let plot = Plot::new("my_plot")
                .grid_spacing(5.0f32..=300.0f32)
                .x_axis_label("sqrt(s) [GeV]")
                .legend(Legend::default());

            let plot = match self.scaling_type {
                ScalingType::Linear => plot.custom_y_axes(vec![AxisHints::new_y()
                    .formatter(|mark, _l, _| scientific_notation(mark.value, false))]),
                ScalingType::Logarithmic => plot
                    .y_grid_spacer(|input| {
                        let input_exp = GridInput {
                            bounds: (
                                inverse_log_map(input.bounds.0),
                                inverse_log_map(input.bounds.1),
                            ),
                            base_step_size: input.base_step_size,
                        };
                        let lin_gridmarks = (log_grid_spacer(10))(input_exp);
                        let mut log_gridmarks: Vec<GridMark> = Vec::new();
                        for v in lin_gridmarks.iter() {
                            if v.value - v.value.floor() < 0.9 {
                                log_gridmarks.push(GridMark {
                                    value: log_map(v.value),
                                    step_size: v.step_size / 1.14,
                                });
                            }
                        }
                        log_gridmarks
                    })
                    .label_formatter(|_, point| {
                        format!("x = {:.5}\ny = {:.5}", point.x, 10.0f64.powf(point.y)).to_string()
                    })
                    .custom_y_axes(vec![AxisHints::new_y()
                        .formatter(|mark, _l, _| scientific_notation(mark.value, true))]),
            };

            plot.show(ui, |plot_ui| {
                for i in 0..self.plot_data.len() {
                    plot_ui.line(
                        Line::new(self.plot_data[i].points.clone())
                            .name(self.plot_data[i].name.clone()),
                    ); // is clone necessary?
                }
            });
        });
    }
}

#[cfg(not(target_arch = "wasm32"))]
fn main() {
    let mut app = App::default();
    app.calc_cache = calc::Cache::from_file("./gauss_quad_lut.morello").unwrap();

    // let x_values: Vec<f64> = (0..app.n_points)
    //     .map(|x| -> f64 { f64::from(x) * ((app.b - app.a) / f64::from(app.n_points)) + app.a })
    //     .collect();
    // let bar = indicatif::ProgressBar::new(u64::from(app.n_points));
    // let values: Vec<f64> = x_values
    //     .iter()
    //     .map(|&x| {
    //         bar.inc(1);
    //         calc::phi0(
    //             &mut app.calc_cache,
    //             x,
    //             1000,
    //             1000,
    //             app.use_tan_omnes,
    //             app.use_tan_phi0,
    //             app.use_xsub_phi0,
    //             app.use_reduced_integrand,
    //             x == app.a,
    //         )
    //     })
    //     .collect();


    let accuracies = [100,500,1000,2000,5000,7500,10000,15000,25000,50000];
    let mut results = [[0.0;10];10];
    for (o,n_o) in accuracies.iter().enumerate() {
        println!("{:?}", n_o);
        for (p,n_p) in accuracies.iter().enumerate() {
            let phi0 = calc::phi0(&mut app.calc_cache, 10000.0, *n_o, *n_p , true, true, true, true, true, false, 0.0, 0.0);
            results[o][p] = phi0 / calc::delta(10000.0) - 1.0;
        }
    }

    println!("{:#?}", results);
    
    // let phi0 = calc::phi0(&mut app.calc_cache, 10000.0, 10000, 10000, true, true, true, true, true, false, 0.0, 0.0);
    // let phi0 = calc::phi0(&mut app.calc_cache, 10000.0, 100000, 100000, true, true, true, true, true, false, 0.0, 0.0);
    // println!("relative difference: {:e}", phi0 / calc::delta(10000.0) - 1.0);

    // eframe::run_native(
    //     "Omnes Calculator",
    //     eframe::NativeOptions::default(),
    //     Box::new(|_cc| Box::new(app)),
    // )
    // .unwrap();
}

#[cfg(target_arch = "wasm32")]
fn main() {
    let mut app = App::default();

    wasm_bindgen_futures::spawn_local(async {
        let res = gloo_net::http::RequestBuilder::new("/lut/gauss_quad_lut.morello")
            .method(gloo_net::http::Method::POST)
            .send()
            .await
            .unwrap();

        app.calc_cache = calc::Cache::from_slice(&res.binary().await.unwrap()).unwrap();
        let web_options = eframe::WebOptions::default();

        eframe::WebRunner::new()
            .start(
                "morellus_canvas_id",
                web_options,
                Box::new(|_cc| Box::new(app)),
            )
            .await
            .expect("failed to start eframe");
    });
}

fn log_map(x: f64) -> f64 {
    x.floor() + (1.0 + (x - x.floor()) * 10.0).log10()
}

fn inverse_log_map(x: f64) -> f64 {
    x.floor() + (10f64.powf(x - x.floor()) - 1.0) / 10.0
}

fn scientific_notation(x: f64, log: bool) -> String {
    if log {
        format!(
            "{}*10^{}",
            format!("{:.8}", 10f64.powf(x - x.floor()))
                .trim_end_matches('0')
                .trim_end_matches('.')
                .to_string(),
            x.floor()
        )
        .to_string()
    } else {
        format!("{:.9}", x)
            .trim_end_matches('0')
            .trim_end_matches('.')
            .to_string()
    }
}

fn int_text_edit_singleline(ui: &mut egui::Ui, value: &mut u32, enabled: bool) -> egui::Response {
    let mut tmp_value = format!("{}", value);
    let textedit = egui::TextEdit::singleline(&mut tmp_value);
    let res = ui.add_enabled(enabled, textedit);
    if let Ok(result) = tmp_value.parse::<u32>() {
        *value = result;
    } else if tmp_value == "" {
        *value = 0;
    }
    res
}

fn float_text_edit_singleline(ui: &mut egui::Ui, value: &mut f64, enabled: bool) -> egui::Response {
    let mut tmp_value = format!("{}", value);
    let textedit = egui::TextEdit::singleline(&mut tmp_value);
    let res = ui.add_enabled(enabled, textedit);
    if let Ok(result) = tmp_value.parse::<f64>() {
        *value = result;
    } else if tmp_value == "" {
        *value = 0.0;
    }
    res
}
