use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::io::{Seek, SeekFrom};

mod byte_writer;
mod node;

use byte_writer::{binary_to_string, string_to_binary};
use node::{Node, create_tree, read_tree, tree_to_map, tree_to_string};

fn main() -> std::io::Result<()> {
    let args: Vec<String> = env::args().collect();

    let request = args.get(1).map(|s| s.to_string());

    match request {
        Some(r) => {
            if r == "--compress" || r == "-c" {
                let file_name = args.get(2).map(|s| s.to_string());
                let file_to_un = args.get(3).map(|s| s.to_string());
                match file_name {
                    Some(f) => {
                        let mut to_compress = File::open(&f)?;
                        let mut after_compression: File;
                        if let Some(str) = file_to_un {
                            after_compression = File::create(str)?;
                        } else {
                            after_compression = File::create(f + ".oeuf")?;
                        }
                        let map = get_map_file(&to_compress)?;
                        let nodes = nodes_at_vec(map);
                        let mut tree = create_tree(nodes);
                        let (str_bin, chars) = tree_to_string(&mut tree);
                        let binary = string_to_binary(&str_bin);
                        let len_tree: u32 = str_bin.len() as u32;
                        let string: String = chars.iter().collect();
                        after_compression
                            .write_all(&(len_tree + (string.len() as u32) * 8).to_be_bytes())?;
                        after_compression.write_all(&len_tree.to_be_bytes())?;
                        after_compression.write_all(&binary)?;
                        after_compression.write_all(string.as_bytes())?;
                        to_compress.seek(SeekFrom::Start(0))?;
                        let map = tree_to_map(&mut tree, true);
                        let trad = translate_char_bin(&mut to_compress, map);
                        let trad_bin = string_to_binary(&trad);
                        let len_body = trad.len() as u32;
                        after_compression.write_all(&len_body.to_be_bytes())?;
                        after_compression.write_all(&trad_bin)?;
                    }
                    None => {
                        println!("No file name!");
                        display_options();
                    }
                }
            } else if r == "--print" || r == "-p" {
                let file_name = args.get(2).map(|s| s.to_string());
                match file_name {
                    Some(f) => {
                        let mut to_print = File::open(f)?;
                        let untranslate = string_translate(&mut to_print);
                        eprintln!("untranslate = {}", untranslate);
                    }
                    None => {
                        println!("No file name!");
                        display_options();
                    }
                }
            } else if r == "--uncompress" || r == "-u" {
                let file_name = args.get(2).map(|s| s.to_string());
                let file_to_un = args.get(3).map(|s| s.to_string());
                match file_name {
                    Some(f) => {
                        let mut to_uncomp = File::open(&f)?;
                        let mut uncomp: File;
                        if let Some(str) = file_to_un {
                            uncomp = File::create(str)?;
                        } else {
                            uncomp = File::create(f + ".txt")?;
                        }
                        let txt = string_translate(&mut to_uncomp);
                        uncomp.write_all(txt.as_bytes())?;
                    }
                    None => {
                        println!("No File name !");
                        display_options();
                    }
                }
            }
        }
        None => {
            println!("No argument to deal with !");
            display_options();
        }
    }

    Ok(())
}

fn display_options() {
    println!("--compress  , -c <intput> [<output>]     To compress a file.");
    println!("--uncompress, -u <intput> [<output>]     To uncompress a file.");
    println!("--print     ,-p  <input>                 To print a file compressed.");
}

fn get_map_file(mut file: &File) -> Result<HashMap<char, i32>, std::io::Error> {
    let mut hashmap: HashMap<char, i32> = HashMap::new();
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    for c in contents.chars() {
        let counter = hashmap.entry(c).or_insert(0);
        *counter += 1;
    }
    Ok(hashmap)
}

fn string_translate(file: &mut File) -> String {
    let mut buffer = [0u8; 4];
    let _ = file.read_exact(&mut buffer);
    let len_head = u32::from_be_bytes(buffer);
    let _ = file.read_exact(&mut buffer);
    let len_tree = u32::from_be_bytes(buffer);
    //eprintln!("len_head = {:?}", len_head);
    //eprintln!("len_tree = {:?}", len_tree);

    let len_chars = len_head - len_tree;
    let tree_bin = read_n_bytes(file, len_tree);
    let tree_str = binary_to_string(tree_bin, len_tree);
    //eprintln!("tree_str = {:?}", tree_str);

    let chars_bin = read_n_bytes(file, len_chars);
    let chars_str = String::from_utf8(chars_bin).unwrap();
    let root = read_tree(&tree_str, &chars_str.chars().collect());
    let _ = file.read_exact(&mut buffer);
    let len_body = u32::from_be_bytes(buffer);
    translate_bin_char(file, root, len_body)
}

fn nodes_at_vec(map: HashMap<char, i32>) -> Vec<Node> {
    let mut nodes = Vec::new();

    for (key, value) in &map {
        let mut node = Node::new();
        node.set_char(*key);
        node.set_counter(*value);
        nodes.push(node);
    }
    nodes.sort_by(|a, b| a.val.cmp(&b.val));
    nodes
}

fn read_n_bytes(file: &mut File, len: u32) -> Vec<u8> {
    let mut n = 0;
    let mut out: Vec<u8> = Vec::new();
    let mut buffer = vec![0u8; 1];
    while n < len {
        let read = file.read_exact(&mut buffer);
        match read {
            Ok(_c) => {
                out.push(buffer[0]);
            }
            Err(e) => {
                println!("Problem {}", e);
            }
        }
        n += 8;
    }
    out
}

fn translate_char_bin(file: &mut File, map: HashMap<String, String>) -> String {
    let mut txt = String::new();
    let mut translate = String::new();

    let _ = file.read_to_string(&mut txt);
    for c in txt.chars() {
        if let Some(code) = map.get(&c.to_string()) {
            translate.push_str(code);
        }
    }
    translate
}

fn translate_bin_char(file: &mut File, root: Node, len: u32) -> String {
    let mut txt = Vec::new();
    let mut translate = String::new();
    let mut current = &root;

    let _ = file.read_to_end(&mut txt);
    let txt_str = binary_to_string(txt, len);
    for b in txt_str.chars() {
        match b {
            '1' => {
                if let Some(r) = &current.r {
                    current = r;
                }
            }
            '0' => {
                if let Some(l) = &current.l {
                    current = l;
                }
            }
            _ => {
                println!("I see you, you try to tinker with things but it breaks everything");
            }
        }
        if let Some(v) = current.val {
            translate.push(v);
            current = &root;
        }
    }
    translate
}
