
pub mod timetracking {
    use std::io::BufRead;
    use std::collections::HashMap;
    use regex::Regex;
    use chrono::NaiveDateTime;
    use itertools::Itertools;

    pub fn nearest(hr: f64) -> f64 {
       let t = hr * 4.0;
       t.round() / 4.0
    }
    
    pub fn process_input_file(reader: &mut Box<dyn BufRead>) -> (
        HashMap<String, Vec<i64>>, 
        HashMap<String, Vec<i64>>,
        Vec<String>) {
        let mut start: HashMap<String, Vec<i64>>  = HashMap::new();
        let mut finish: HashMap<String, Vec<i64>> = HashMap::new();
        let mut categories: Vec<String> = Vec::new();
        
        // regex
        let empty       = Regex::new(r"^\s*$").unwrap();  // regex for finding empty lines (and skipping them)
        let comment     = Regex::new(r"^\s*#").unwrap();  // regex for finding empty lines (and skipping them)
        let delim       = Regex::new(r":\s{2}").unwrap(); // regex for splitting timestamp entry
        let begin       = Regex::new(r"Begin\s+(?P<action>.*)\s*$").unwrap();
        let end         = Regex::new(r"End\s+(?P<action>.*)\s*$").unwrap();
        let category    = Regex::new(r"\[(?P<category>.*)\]\s+(?P<activity>.*)\s*$").unwrap();
    
        for line in reader.lines() {
            // need to get the string out of line (which is an option enum)
            let l: String = line.unwrap();
    
            // if the line is empty or is a comment
            if empty.is_match(&l) || comment.is_match(&l) {
                continue
            };
            
            // Parse the timestamp and entry
            let splits: Vec<_> = delim.split(&l.trim_end()).into_iter().collect();
            // splits should only ever have two elements (only one delimiter per log line)
            let datetime = splits[0];
            let entry = splits[1];
            let date_time = NaiveDateTime::parse_from_str(datetime,"%Y-%m-%d %H:%M:%S").unwrap();
            let timestamp = date_time.timestamp();
    
            // Process a Begin
            if begin.is_match(&entry) {
                let caps = begin.captures(&entry).unwrap();
                let action = caps.name("action").unwrap().as_str();
                start.entry(action.to_string()).or_insert(Vec::new()).push(timestamp);
                let cats = category.captures(&entry).unwrap();
                let cat = cats.name("category").unwrap().as_str();
                categories.push(cat.to_string());
    
            };
    
            // Process an End
            if end.is_match(&entry) {
                let caps = end.captures(&entry).unwrap();
                let action = caps.name("action").unwrap().as_str();
                finish.entry(action.to_string()).or_insert(Vec::new()).push(timestamp);
                let cats = category.captures(&entry).unwrap();
                let cat = cats.name("category").unwrap().as_str();
                categories.push(cat.to_string());
            };
    
        }; // end for
        // sort and dedup categories
        categories.sort_unstable();
        categories.dedup();
    
        (start, finish, categories)
    }
    
    pub fn generate_individual_timecards (
        start: &mut HashMap<String, Vec<i64>>,
        finish: &mut HashMap<String, Vec<i64>>
        ) -> (
            HashMap<String, i64>,
            f64
        ) 
        {
            let mut ind: HashMap<String, i64> = HashMap::new();
            let mut gtoth: f64 = 0.0;
            for (act, tstamps) in start.iter_mut().sorted_by_key(|x| x.0) {
                let bc: i64 = tstamps.len().try_into().unwrap();
                let mut ec: i64 = 0;
                if finish.contains_key(&act.to_string()) {
                    ec = finish.get(&act.to_string()).expect("Somehow, not a vector!").len().try_into().unwrap();
                };
    
                // Debugging
                // println!("bc: {}, ec: {}", bc, ec);
                // This block ensures the lengths of start and finish for this action are equal
                if bc - ec > 1 {
                    panic!("ERROR:  Missing more than one End");  // need to fix log file
                } else if bc > ec {  // bc should be exactly one greater
                    tstamps.pop();
                } else if ec > bc {
                    panic!("ERROR:  Missing a Begin");  // need to fix log file
                };
    
                // Debugging
                //for t in tstamps.into_iter() {
                //    print!("{} ", t);
                //};
                //println!();
                //for t in finish.get_mut(&act.to_string()).expect("Somehow, not a vector!").into_iter() {
                //    print!("{} ", t);
                //};
                //println!();
    
    
                let mut delta: i64 = 0;
                for i in 0..tstamps.len() {
                    let beg = tstamps[i];
                    let en  = finish.get_mut(&act.to_string()).unwrap()[i];
                    delta += en - beg;
                };
    
                ind.insert(act.to_string(), delta);
                let d: f64 = nearest(delta as f64 / 3600.00);
                if d == 0.0 {
                    gtoth += 0.08;
                } else {
                    gtoth += d;
                };
    
            }
    
            (ind, gtoth)
        
    }
    
}
