extern crate countmap;

use countmap::CountMap;
use std::{
    collections::HashMap,
    fs::File,
    io::{self, BufRead, BufReader},
    path::Path,
};

fn analyze_text<P: AsRef<Path>>(p: P) -> io::Result<CountMap<char, u32>> {
    let mut map = CountMap::new();
    let read = BufReader::new(File::open(p)?);
    read.lines()
        .filter(Result::is_ok)
        .map(Result::unwrap)
        .for_each(|l| {
            l.chars().filter(|c| c.is_alphabetic()).for_each(|c| {
                map.insert_or_increment(dumb_to_lowercase(c));
            });
        });
    Ok(map)
}

fn create_mapping_table<I1, I2>(natural: I1, target: I2) -> HashMap<char, char>
where
    I1: IntoIterator<Item = (char, u32)>,
    I2: IntoIterator<Item = (char, u32)>,
{
    let mut natural: Vec<_> = natural.into_iter().collect();
    natural.sort_by(|(_, u1), (_, u2)| u2.cmp(u1));
    let mut target: Vec<_> = target.into_iter().collect();
    target.sort_by(|(_, u1), (_, u2)| u2.cmp(u1));
    natural
        .into_iter()
        .zip(target.into_iter())
        .map(|((c1, _), (c2, _))| (c1, c2))
        .collect()
}

fn auto_decrypt<P1, P2>(natural: P1, target: P2) -> io::Result<String>
where
    P1: AsRef<Path>,
    P2: AsRef<Path>,
{
    let natural_map = analyze_text(natural)?;
    let target_map = analyze_text(&target)?;
    let mapping = create_mapping_table(natural_map, target_map);
    let read = BufReader::new(File::open(target)?);
    let res: Vec<_> = read
        .lines()
        .filter(Result::is_ok)
        .map(Result::unwrap)
        .map(|l| {
            l.chars()
                .map(|c| mapping.get(&dumb_to_lowercase(c)).cloned().unwrap_or(c))
                .collect::<String>()
        }).collect();
    Ok(res.as_slice().join("\n").to_owned())
}

fn count() {
    let args: Vec<_> = std::env::args().collect();
    let file = &args[1];
    let mut map: CountMap<char, u32> = CountMap::new();
    let read = BufReader::new(File::open(file).unwrap());
    for line in read.lines() {
        if let Ok(line) = line {
            line.chars().filter(|c| c.is_alphabetic()).for_each(|c| {
                map.insert_or_increment(c);
            });
        }
    }
    let sum: u32 = map
        .iter()
        .filter(|(k, _)| k.is_alphabetic())
        .map(|(_, v)| v)
        .sum();
    let mut vec: Vec<_> = map.into_iter().collect();
    vec.sort_unstable();
    vec.into_iter().for_each(|(k, v)| {
        println!(
            "{} &\\text{{:}}& {:3} \\to{{}} {:.2} \\\\",
            k,
            v,
            f64::from(v) / f64::from(sum)
        )
    });
    println!("sum &\\text{{:}}& {}", sum);
}

fn dumb_to_lowercase(c: char) -> char {
    c.to_lowercase().to_string().chars().nth(0).unwrap_or(c)
}

fn create_map<P: AsRef<Path>>(path: P) -> std::io::Result<HashMap<char, char>> {
    let read = BufReader::new(File::open(path)?);
    let mut map = HashMap::new();
    for line in read.lines() {
        if let Ok(line) = line {
            let chars: Vec<char> = line.chars().collect();
            if chars.len() != 2 {
                eprintln!("Invalid line {}", line);
            } else {
                map.insert(chars[0], chars[1]);
            }
        }
    }
    Ok(map)
}

fn main() {
    let args: Vec<_> = std::env::args().collect();
    let res = auto_decrypt(&args[1], &args[2]).unwrap();
    println!("{}", res);
    // count();
    // let args: Vec<_> = std::env::args().collect();
    // let target = &args[1];
    // let map = create_map(&args[2]).unwrap();
    // let read = BufReader::new(File::open(target).unwrap());
    // for line in read.lines() {
    //     if let Ok(line) = line {
    //         let s: String = line
    //             .chars()
    //             .map(|c| map.get(&c).cloned().unwrap_or(c))
    //             .collect();
    //         println!("{}", s);
    //     }
    // }
}
