use core::str;
use plotters::{coord::types::RangedCoordf64, prelude::*};
use std::{fs, io::BufReader};
use wtx::{codec::Csv, collections::Vector};

const BACKENDS: [&str; 4] = ["aws-lc-rs", "graviola", "openssl", "ring"];
const CERTIFICATES: [&str; 5] = [
  "ecdsasecp256r1sha256",
  "ecdsasecp384r1sha384",
  "ed25519",
  "rsapssrsaesha256",
  "rsapssrsaesha384",
];
const CIPHERS: [&str; 3] = ["aes128gcmsha256", "aes256gcmsha384", "chacha20poly1305sha256"];
const FILE: &str = "/tmp/wtx-bench-results/all_results.csv";
const NAMED_GROUPS: [&str; 3] = ["secp256r1", "secp384r1", "x25519"];

fn main() {
  let records = get_records();
  for cipher in CIPHERS {
    let filename = format!("assets/{}.svg", cipher);
    let root = SVGBackend::new(&filename, (1920, 1080)).into_drawing_area();
    root.fill(&WHITE).unwrap();
    let (legend_area, chart_area) = root.split_vertically(60);
    draw_legend(cipher, legend_area);
    let areas = chart_area.split_evenly((1, 5));
    for idx in 0..5 {
      let area = &areas[idx];
      let certificate = CERTIFICATES[idx];
      let mut chart = create_chart(area, certificate, cipher, &records);
      draw_labels(&mut chart);
      draw_bars(certificate, &mut chart, cipher, &records);
    }
    root.present().unwrap();
  }
}

fn create_chart<'any>(
  area: &DrawingArea<SVGBackend<'any>, plotters::coord::Shift>,
  certificate: &str,
  cipher: &str,
  records: &Vector<Record>,
) -> ChartContext<'any, SVGBackend<'any>, Cartesian2d<RangedCoordf64, RangedCoordf64>> {
  let mut max_y = records
    .iter()
    .filter(|rec| rec.cipher_suite == cipher)
    .map(|rec| rec.mean)
    .fold(0.0f64, f64::max);
  max_y = max_y * 1.02;
  ChartBuilder::on(area)
    .caption(format!("{certificate}"), ("sans-serif", 18).into_font())
    .margin(10)
    .x_label_area_size(40)
    .y_label_area_size(40)
    .build_cartesian_2d(-0.5..3.5, 0.0..max_y)
    .unwrap()
}

fn draw_bars(
  certificate: &str,
  chart: &mut ChartContext<'_, SVGBackend<'_>, Cartesian2d<RangedCoordf64, RangedCoordf64>>,
  cipher: &str,
  records: &[Record],
) {
  let num_groups = NAMED_GROUPS.len() as f64;
  let total_bar_width = 0.55;
  let single_bar_width = total_bar_width / num_groups;
  for (ng_idx, ng) in NAMED_GROUPS.iter().enumerate() {
    let color = match *ng {
      "secp256r1" => RGBColor(231, 76, 60),
      "secp384r1" => RGBColor(52, 152, 219),
      "x25519" => RGBColor(39, 174, 96),
      _ => RGBColor(0, 0, 0),
    };
    let x_offset = (ng_idx as f64 - (num_groups - 1.0) / 2.0) * single_bar_width;
    for (backend_idx, backend) in BACKENDS.iter().enumerate() {
      if let Some(rec) = records.iter().find(|rec| {
        rec.backend == *backend
          && rec.cipher_suite == cipher
          && rec.named_group == *ng
          && rec.signature == certificate
      }) {
        let center_x = backend_idx as f64 + x_offset;
        let x0 = center_x - (single_bar_width / 2.0) + 0.015;
        let x1 = center_x + (single_bar_width / 2.0) - 0.015;
        chart.draw_series([Rectangle::new([(x0, 0.0), (x1, rec.mean)], color.filled())]).unwrap();
      }
    }
  }
}

fn draw_labels(
  chart: &mut ChartContext<'_, SVGBackend<'_>, Cartesian2d<RangedCoordf64, RangedCoordf64>>,
) {
  chart
    .configure_mesh()
    .x_labels(4)
    .x_label_formatter(&|el: &f64| {
      let local_idx = el.round() as i32;
      if local_idx >= 0 && local_idx < 4 { BACKENDS[local_idx as usize].into() } else { "".into() }
    })
    .y_labels(15)
    .y_label_formatter(&|el| format!("{:.4}", el))
    .draw()
    .unwrap();
}

fn draw_legend(cipher: &str, legend_area: DrawingArea<SVGBackend<'_>, plotters::coord::Shift>) {
  legend_area
    .draw(&Text::new(
      format!("{cipher} | secp256r1 (Red), secp384r1 (Blue), x25519 (Green)"),
      (20, 20),
      ("sans-serif", 24, &BLACK).into_text_style(&legend_area),
    ))
    .unwrap();
}

fn get_records() -> Vector<Record> {
  let raw_data = fs::read_to_string(FILE).unwrap();
  let mut line_buffer = Vector::new();
  let mut csv = Csv::from_buf_read(BufReader::new(raw_data.as_bytes()));
  let mut records = Vector::new();
  while let Some(mut line) = csv.next_elements(&mut line_buffer).unwrap() {
    let command = line.next().unwrap();
    let mut command_iter = str::from_utf8(command).unwrap().split(',');
    let mean: f64 = str::from_utf8(line.next().unwrap()).unwrap().parse().unwrap();
    records
      .push(Record {
        backend: command_iter.next().unwrap().into(),
        cipher_suite: command_iter.next().unwrap().into(),
        mean,
        named_group: command_iter.next().unwrap().into(),
        signature: command_iter.next().unwrap().into(),
      })
      .unwrap();
  }
  records
}

#[derive(Debug)]
struct Record {
  backend: String,
  cipher_suite: String,
  mean: f64,
  named_group: String,
  signature: String,
}
