use super::*;
use kde;
use report::{BenchmarkId, ComparisonData, MeasurementData, ReportContext};
use std::process::Child;

pub(crate) fn pdf(
    id: &BenchmarkId,
    context: &ReportContext,
    measurements: &MeasurementData,
    size: Option<Size>,
) -> Child {
    let avg_times = &measurements.avg_times;

    let mean = avg_times.mean();

    let iter_counts = measurements.iter_counts();
    let &max_iters = iter_counts
        .iter()
        .max_by_key(|&&iters| iters as u64)
        .unwrap();
    let exponent = (max_iters.log10() / 3.).floor() as i32 * 3;
    let y_scale = 10f64.powi(-exponent);

    let y_label = if exponent == 0 {
        "Iterations".to_owned()
    } else {
        format!("Iterations (x 10^{})", exponent)
    };

    let (xs, ys) = kde::sweep(&avg_times, KDE_POINTS, None);
    let (lost, lomt, himt, hist) = avg_times.fences();

    let vertical = &[0., max_iters];
    let zeros = iter::repeat(0);

    let mut figure = Figure::new();
    figure
        .set(Font(DEFAULT_FONT))
        .set(size.unwrap_or(SIZE))
        .configure(Axis::BottomX, |a| {
            let (x_scale, prefix) = scale_time(avg_times.max());
            let xs_ = Sample::new(&xs);

            a.set(Label(format!("Average time ({}s)", prefix)))
                .set(Range::Limits(xs_.min() * x_scale, xs_.max() * x_scale))
                .set(ScaleFactor(x_scale))
        })
        .configure(Axis::LeftY, |a| {
            a.set(Label(y_label))
                .set(Range::Limits(0., max_iters * y_scale))
                .set(ScaleFactor(y_scale))
        })
        .configure(Axis::RightY, |a| a.set(Label("Density (a.u.)")))
        .configure(Key, |k| {
            k.set(Justification::Left)
                .set(Order::SampleText)
                .set(Position::Outside(Vertical::Top, Horizontal::Right))
        })
        .plot(
            FilledCurve {
                x: &*xs,
                y1: &*ys,
                y2: zeros,
            },
            |c| {
                c.set(Axes::BottomXRightY)
                    .set(DARK_BLUE)
                    .set(Label("PDF"))
                    .set(Opacity(0.25))
            },
        )
        .plot(
            Lines {
                x: &[mean, mean],
                y: vertical,
            },
            |c| {
                c.set(DARK_BLUE)
                    .set(LINEWIDTH)
                    .set(LineType::Dash)
                    .set(Label("Mean"))
            },
        )
        .plot(
            Points {
                x: avg_times.iter().filter_map(
                    |(t, label)| {
                        if label.is_outlier() {
                            None
                        } else {
                            Some(t)
                        }
                    },
                ),
                y: avg_times
                    .iter()
                    .zip(iter_counts.iter())
                    .filter_map(
                        |((_, label), i)| {
                            if label.is_outlier() {
                                None
                            } else {
                                Some(i)
                            }
                        },
                    ),
            },
            |c| {
                c.set(DARK_BLUE)
                    .set(Label("\"Clean\" sample"))
                    .set(PointType::FilledCircle)
                    .set(POINT_SIZE)
            },
        )
        .plot(
            Points {
                x: avg_times.iter().filter_map(
                    |(x, label)| {
                        if label.is_mild() {
                            Some(x)
                        } else {
                            None
                        }
                    },
                ),
                y: avg_times
                    .iter()
                    .zip(iter_counts.iter())
                    .filter_map(
                        |((_, label), i)| {
                            if label.is_mild() {
                                Some(i)
                            } else {
                                None
                            }
                        },
                    ),
            },
            |c| {
                c.set(DARK_ORANGE)
                    .set(Label("Mild outliers"))
                    .set(POINT_SIZE)
                    .set(PointType::FilledCircle)
            },
        )
        .plot(
            Points {
                x: avg_times.iter().filter_map(
                    |(x, label)| {
                        if label.is_severe() {
                            Some(x)
                        } else {
                            None
                        }
                    },
                ),
                y: avg_times
                    .iter()
                    .zip(iter_counts.iter())
                    .filter_map(
                        |((_, label), i)| {
                            if label.is_severe() {
                                Some(i)
                            } else {
                                None
                            }
                        },
                    ),
            },
            |c| {
                c.set(DARK_RED)
                    .set(Label("Severe outliers"))
                    .set(POINT_SIZE)
                    .set(PointType::FilledCircle)
            },
        )
        .plot(
            Lines {
                x: &[lomt, lomt],
                y: vertical,
            },
            |c| c.set(DARK_ORANGE).set(LINEWIDTH).set(LineType::Dash),
        )
        .plot(
            Lines {
                x: &[himt, himt],
                y: vertical,
            },
            |c| c.set(DARK_ORANGE).set(LINEWIDTH).set(LineType::Dash),
        )
        .plot(
            Lines {
                x: &[lost, lost],
                y: vertical,
            },
            |c| c.set(DARK_RED).set(LINEWIDTH).set(LineType::Dash),
        )
        .plot(
            Lines {
                x: &[hist, hist],
                y: vertical,
            },
            |c| c.set(DARK_RED).set(LINEWIDTH).set(LineType::Dash),
        );
    figure.set(Title(escape_underscores(id.as_title())));

    let path = context.report_path(id, "pdf.svg");
    debug_script(&path, &figure);
    figure.set(Output(path)).draw().unwrap()
}

pub(crate) fn pdf_small(
    id: &BenchmarkId,
    context: &ReportContext,
    measurements: &MeasurementData,
    size: Option<Size>,
) -> Child {
    let avg_times = &*measurements.avg_times;
    let mean = avg_times.mean();

    let (xs, ys, mean_y) = kde::sweep_and_estimate(avg_times, KDE_POINTS, None, mean);
    let xs_ = Sample::new(&xs);
    let ys_ = Sample::new(&ys);

    let y_limit = ys_.max() * 1.1;
    let zeros = iter::repeat(0);

    let mut figure = Figure::new();
    figure
        .set(Font(DEFAULT_FONT))
        .set(size.unwrap_or(SIZE))
        .configure(Axis::BottomX, |a| {
            let (x_scale, prefix) = scale_time(avg_times.max());
            a.set(Label(format!("Average time ({}s)", prefix)))
                .set(Range::Limits(xs_.min() * x_scale, xs_.max() * x_scale))
                .set(ScaleFactor(x_scale))
        })
        .configure(Axis::LeftY, |a| {
            a.set(Label("Density (a.u.)"))
                .set(Range::Limits(0., y_limit))
        })
        .configure(Axis::RightY, |a| a.hide())
        .configure(Key, |k| k.hide())
        .plot(
            FilledCurve {
                x: &*xs,
                y1: &*ys,
                y2: zeros,
            },
            |c| {
                c.set(Axes::BottomXRightY)
                    .set(DARK_BLUE)
                    .set(Label("PDF"))
                    .set(Opacity(0.25))
            },
        )
        .plot(
            Lines {
                x: &[mean, mean],
                y: &[0., mean_y],
            },
            |c| c.set(DARK_BLUE).set(LINEWIDTH).set(Label("Mean")),
        );

    let path = context.report_path(id, "pdf_small.svg");
    debug_script(&path, &figure);
    figure.set(Output(path)).draw().unwrap()
}

fn pdf_comparison_figure(
    measurements: &MeasurementData,
    comparison: &ComparisonData,
    size: Option<Size>,
) -> Figure {
    let base_avg_times = Sample::new(&comparison.base_avg_times);

    let base_mean = base_avg_times.mean();
    let new_mean = measurements.avg_times.mean();

    let (base_xs, base_ys, base_y_mean) =
        kde::sweep_and_estimate(base_avg_times, KDE_POINTS, None, base_mean);
    let (xs, ys, y_mean) =
        kde::sweep_and_estimate(&measurements.avg_times, KDE_POINTS, None, new_mean);
    let base_xs_ = Sample::new(&base_xs);
    let xs_ = Sample::new(&xs);

    let (x_scale, prefix) = scale_time(base_xs_.max().max(xs_.max()));
    let zeros = iter::repeat(0);

    let mut figure = Figure::new();
    figure
        .set(Font(DEFAULT_FONT))
        .set(size.unwrap_or(SIZE))
        .configure(Axis::BottomX, |a| {
            a.set(Label(format!("Average time ({}s)", prefix)))
                .set(ScaleFactor(x_scale))
        })
        .configure(Axis::LeftY, |a| a.set(Label("Density (a.u.)")))
        .configure(Axis::RightY, |a| a.hide())
        .configure(Key, |k| {
            k.set(Justification::Left)
                .set(Order::SampleText)
                .set(Position::Outside(Vertical::Top, Horizontal::Right))
        })
        .plot(
            FilledCurve {
                x: &*base_xs,
                y1: &*base_ys,
                y2: zeros.clone(),
            },
            |c| c.set(DARK_RED).set(Label("Base PDF")).set(Opacity(0.5)),
        )
        .plot(
            Lines {
                x: &[base_mean, base_mean],
                y: &[0., base_y_mean],
            },
            |c| c.set(DARK_RED).set(Label("Base Mean")).set(LINEWIDTH),
        )
        .plot(
            FilledCurve {
                x: &*xs,
                y1: &*ys,
                y2: zeros,
            },
            |c| c.set(DARK_BLUE).set(Label("New PDF")).set(Opacity(0.5)),
        )
        .plot(
            Lines {
                x: &[new_mean, new_mean],
                y: &[0., y_mean],
            },
            |c| c.set(DARK_BLUE).set(Label("New Mean")).set(LINEWIDTH),
        );
    figure
}

pub(crate) fn pdf_comparison(
    id: &BenchmarkId,
    context: &ReportContext,
    measurements: &MeasurementData,
    comparison: &ComparisonData,
    size: Option<Size>,
) -> Child {
    let mut figure = pdf_comparison_figure(measurements, comparison, size);
    figure.set(Title(escape_underscores(id.as_title())));
    let path = context.report_path(id, "both/pdf.svg");
    debug_script(&path, &figure);
    figure.set(Output(path)).draw().unwrap()
}

pub(crate) fn pdf_comparison_small(
    id: &BenchmarkId,
    context: &ReportContext,
    measurements: &MeasurementData,
    comparison: &ComparisonData,
    size: Option<Size>,
) -> Child {
    let mut figure = pdf_comparison_figure(measurements, comparison, size);
    figure.configure(Key, |k| k.hide());
    let path = context.report_path(id, "relative_pdf_small.svg");
    debug_script(&path, &figure);
    figure.set(Output(path)).draw().unwrap()
}
