use std::io;
use std::io::BufRead;
use std::collections::HashMap;
use std::cmp::max;
use regex::Regex;
use lazy_static::lazy_static;

#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum Action {
    Begin(i32),
    Sleep,
    Wake
}

#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Record {
    year: i32,
    month: i32,
    day: i32,
    hour: i32,
    minute: i32,
    action: Action
}

struct Guard {
    id: i32,
    sleep_times: Vec<(i32, i32)>,  // inclusive-exclusive
    asleep: [i32; 60]
}

fn parse_action(line: &str) -> Action {
    if line.chars().next() == Some('G') {
        let idx = 7 + line[7..].find(' ').unwrap();
        Action::Begin(line[7 .. idx].parse().unwrap())
    } else if line.chars().next() == Some('f') {
        Action::Sleep
    } else {
        Action::Wake
    }
}

fn parse_line(line: &str) -> Record {
    lazy_static! {
        static ref RE: Regex = Regex::new(r"^\[(\d+)-(\d+)-(\d+) (\d+):(\d+)\] (.*)$").unwrap();
    }

    let caps = RE.captures(line).unwrap();
    Record {
        year: caps.get(1).unwrap().as_str().parse().unwrap(),
        month: caps.get(2).unwrap().as_str().parse().unwrap(),
        day: caps.get(3).unwrap().as_str().parse().unwrap(),
        hour: caps.get(4).unwrap().as_str().parse().unwrap(),
        minute: caps.get(5).unwrap().as_str().parse().unwrap(),
        action: parse_action(caps.get(6).unwrap().as_str())
    }
}

fn argmax<T: Ord>(v: &[T]) -> Option<usize> {
    if v.len() == 0 {
        None
    } else {
        let mut maxat = 0;
        for i in 1..v.len() {
            if v[i] > v[maxat] {
                maxat = i;
            }
        }
        Some(maxat)
    }
}

pub fn main<T: BufRead>(reader: T) -> io::Result<(String, String)> {
    let mut records: Vec<Record> = reader.lines().map(|l| parse_line(&l.unwrap())).collect();

    records.sort_unstable();
    let mut guards = HashMap::new();

    {
        let mut current_id = 0;
        let mut sleep_start = 0;

        for record in records.iter() {
            match record.action {
                Action::Begin(id) => {
                    if let None = guards.get(&id) {
                        guards.insert(id, Guard {id: id, sleep_times: Vec::new(), asleep: [0; 60]});
                    }
                    current_id = id;
                },
                Action::Sleep => sleep_start = record.minute,
                Action::Wake =>
                    guards.get_mut(&current_id).unwrap()
                        .sleep_times.push((sleep_start, record.minute))
            }
        }
    }

    let mut max_sleep = -1;
    let mut max_sleep_at = -1;
    let mut max_times = -1;
    let mut max_times_at = -1;

    for mut guard in guards.values_mut() {
        let mut sleep = 0;
        let mut times = 0;

        for (from, to) in guard.sleep_times.iter() {
            for i in *from..*to {
                guard.asleep[i as usize] += 1;
                times = max(times, guard.asleep[i as usize]);
            }
            sleep += to - from;
        }

        if sleep > max_sleep {
            max_sleep = sleep;
            max_sleep_at = guard.id;
        }

        if times > max_times {
            max_times = times;
            max_times_at = guard.id;
        }
    }

    let sleepy_guard = guards.get(&max_sleep_at).unwrap();
    let max_minute = argmax(&sleepy_guard.asleep).unwrap() as i32;
    let part1 = max_minute * sleepy_guard.id;

    let consistent_guard = guards.get(&max_times_at).unwrap();
    let max_minute = argmax(&consistent_guard.asleep).unwrap() as i32;
    let part2 = max_minute * consistent_guard.id;

    Ok((part1.to_string(), part2.to_string()))
}