#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use fxhash::FxHashMap;
use regex_lite::Regex;
use std::env;
use std::fmt::Display;
use std::fs::File;
use std::io::{self, BufRead, Write};
use std::ops::AddAssign;
use std::str::FromStr;
use Weight::*;
enum Weight {
    Unit,
    Integral,
    Fractional,
}
fn main() -> io::Result<()> {
    match do_main() {
        Ok(_) => Ok(()),
        Err(ref err) if err.kind() == io::ErrorKind::BrokenPipe => Ok(()),
        Err(err) => {
            {
                ::std::io::_eprint(format_args!("counts: {0}\n", err));
            };
            std::process::exit(1);
        }
    }
}
const USAGE: &str = "\
counts: a tool for ad hoc profiling

USAGE:
    counts [OPTIONS] [FILES]

OPTIONS:
    -h, --help     Print help information
    --version      Print version information
    -i, -w         Integral weighting of lines
    -f             Fractional weighting of lines
    -e             Erase weights after applying, replacing them with `NNN`
";
const VERSION: &str = "1.0.4";
fn do_main() -> io::Result<()> {
    let mut weights = Unit;
    let mut erase = false;
    let mut readers: Vec<Box<dyn io::BufRead>> = ::alloc::vec::Vec::new();
    for arg in env::args().skip(1) {
        if arg == "-h" || arg == "--help" {
            {
                ::std::io::_print(format_args!("{0}\n", USAGE));
            };
            return Ok(());
        } else if arg == "--version" {
            {
                ::std::io::_print(format_args!("counts-{0}\n", VERSION));
            };
            std::process::exit(1);
        } else if arg == "-i" || arg == "-w" {
            weights = Integral;
        } else if arg == "-f" {
            weights = Fractional;
        } else if arg == "-e" {
            erase = true;
        } else if arg.starts_with('-') {
            {
                ::std::io::_eprint(format_args!("counts: unknown option `{0}`\n", arg));
            };
            std::process::exit(1);
        } else {
            let file = File::open(arg)?;
            let reader = Box::new(io::BufReader::new(file));
            readers.push(reader);
        }
    }
    if readers.is_empty() {
        readers.push(Box::new(io::BufReader::new(io::stdin())))
    }
    let erased_label = if erase { ", erased" } else { "" };
    match weights {
        Unit => process(readers, "", |_line| (None, 1i64)),
        Integral => {
            let re = Regex::new(r"(([+-]?)\d+)(\D*)$").unwrap();
            process(
                readers,
                &{
                    let res =
                        ::alloc::fmt::format(format_args!(" (weighted integral{0})", erased_label));
                    res
                },
                |line| {
                    if let Some(captures) = re.captures(line) {
                        let weight = i64::from_str(&captures[1]).unwrap();
                        if erase {
                            let line = re.replace(line, "NNN${3}").to_string();
                            (Some(line), weight)
                        } else {
                            (None, weight)
                        }
                    } else {
                        (None, 1i64)
                    }
                },
            )
        }
        Fractional => {
            let re = Regex::new(r"(([+-]?)\d+(\.\d+)?)(\D*)$").unwrap();
            process(
                readers,
                &{
                    let res = ::alloc::fmt::format(format_args!(
                        " (weighted fractional{0})",
                        erased_label
                    ));
                    res
                },
                |line| {
                    if let Some(captures) = re.captures(line) {
                        let weight = f64::from_str(&captures[1]).unwrap();
                        if erase {
                            let line = re.replace(line, "NNN${4}").to_string();
                            (Some(line), weight)
                        } else {
                            (None, weight)
                        }
                    } else {
                        (None, 1f64)
                    }
                },
            )
        }
    }
}
fn process<F, N>(
    readers: Vec<Box<dyn BufRead>>,
    label: &str,
    get_line_and_weight: F,
) -> io::Result<()>
where
    F: Fn(&str) -> (Option<String>, N),
    N: Total,
{
    let mut total = N::from(0u32);
    let mut counts: FxHashMap<String, N> = FxHashMap::default();
    let mut line_with_nl = String::new();
    for mut reader in readers {
        loop {
            match reader.read_line(&mut line_with_nl) {
                Ok(0) => break,
                Ok(_) => {}
                Err(err) => {
                    {
                        ::std::io::_eprint(format_args!("counts: {0}\n", err));
                    };
                    std::process::exit(1);
                }
            }
            let line = &line_with_nl[..line_with_nl.len() - 1];
            let (modified_line, weight) = get_line_and_weight(line);
            match modified_line {
                None => {
                    if let Some(entry) = counts.get_mut(line) {
                        *entry += weight;
                    } else {
                        counts.insert(line.to_string(), weight);
                    }
                }
                Some(modified_line) => {
                    let entry = counts.entry(modified_line).or_insert_with(|| N::from(0u32));
                    *entry += weight;
                }
            }
            total += weight;
            line_with_nl.clear();
        }
    }
    let mut counts: Vec<_> = counts.into_iter().collect();
    counts.sort_unstable_by(|(line1, n1), (line2, n2)| {
        (n2.abs(), line1).partial_cmp(&(n1.abs(), line2)).unwrap()
    });
    io::stdout().write_fmt(format_args!("{0:.1} counts{1}\n", total, label))?;
    let mut cum_perc: f64 = 0f64;
    let total_f64 = total.into_f64();
    for (i, (line, weight)) in counts.iter().enumerate() {
        let perc: f64 = weight.into_f64() * 100f64 / total_f64;
        cum_perc += perc;
        io::stdout().write_fmt(format_args!(
            "({0:3}) {1:8.1} ({2:4.1}%,{3:5.1}%): {4}\n",
            i + 1,
            weight,
            perc,
            cum_perc,
            line
        ))?;
    }
    Ok(())
}
trait Total: AddAssign + Copy + Display + From<u32> + PartialOrd {
    /// `f64` doesn't impl `From<i64>` or `TryFrom<i64>`, so we do it
    /// ourselves. We are unlikely to see `i64` values that are so big that
    /// they cannot be represented as `f64`s, so we make this infallible.
    fn into_f64(self) -> f64;
    fn abs(self) -> Self;
}
impl Total for f64 {
    fn into_f64(self) -> f64 {
        self
    }
    fn abs(self) -> f64 {
        self.abs()
    }
}
impl Total for i64 {
    fn into_f64(self) -> f64 {
        let f = self as f64;
        if f as i64 != self {
            {
                ::core::panicking::panic_fmt(format_args!("i64 too big to convert to f64"));
            }
        }
        f
    }
    fn abs(self) -> i64 {
        self.abs()
    }
}
