use std::cmp::min;
use std::io::prelude::*;
use std::io;
use common;


#[derive(Default)]
pub struct Day2 {
}

impl Day2 {

    pub fn new() -> Day2 {
        Default::default()
    }
}

impl common::Solution for Day2 {

    fn part1(&mut self, input: &mut io::Read) -> String {
        let reader = io::BufReader::new(input);
        let mut pos = 5;
        let mut code = String::new();

        for line in reader.lines() {
            for instruction in line.unwrap().trim().chars() {
                pos = move_pos1(pos, instruction);
            }
            code += &pos.to_string();
        }

        code
    }

    fn part2(&mut self, input: &mut io::Read) -> String {
        let reader = io::BufReader::new(input);
        let mut pos = (0, 2);
        let mut code = String::new();

        for line in reader.lines() {
            for instruction in line.unwrap().trim().chars() {
                pos = move_pos2(pos, instruction);
            }
            code += &pos2char(pos);
        }

        code
    }
}


fn move_pos1(pos: i32, instruction: char) -> i32
{
    match instruction {
        'U' => if pos > 3 { pos - 3 } else { pos },
        'D' => if pos < 7 { pos + 3 } else { pos },
        'L' => if pos % 3 != 1 { pos - 1 } else { pos },
        'R' => if pos % 3 != 0 { pos + 1 } else { pos },
        _   => panic!("Unsupported direction {}", instruction),
    }
}

fn row_width(y: i32) -> i32
{
    min(2 * y + 1, 9 - 2 * y)
}

fn row_offset(y: i32) -> i32
{
    (5 - row_width(y)) / 2
}

fn is_legal(pos: (i32, i32)) -> bool {
    let (x, y) = pos;
    let width = row_width(y);
    let offset = row_offset(y);

    y >= 0 && y < 5 && x >= offset && x < offset + width
}

fn move_pos2(pos: (i32, i32), instruction: char) -> (i32, i32)
{
    let (x, y) = pos;
    let new_pos = match instruction {
        'U' => (x, y - 1),
        'D' => (x, y + 1),
        'L' => (x - 1, y),
        'R' => (x + 1, y),
        _   => panic!("Unsupported direction {}", instruction),
    };

    if is_legal(new_pos) { new_pos } else { pos }
}

fn pos2char(pos: (i32, i32)) -> String
{
    let (x, y) = pos;
    let mut num = x + 1 - row_offset(y);
    for i in 0..y {
        num += row_width(i);
    }

    format!("{:X}", num)
}

#[cfg(test)]
mod tests {
    use super::*;
    use common::Solution;

    const SAMPLE: &[u8] = b"ULL
RRDDD
LURDL
UUUUD";

    #[test]
    fn test_part1() {
        let mut instance = Day2::new();
        assert_eq!("1985", instance.part1(&mut SAMPLE));
    }

    #[test]
    fn test_part2() {
        let mut instance = Day2::new();
        assert_eq!("5DB3", instance.part2(&mut SAMPLE));
    }

}
