#[macro_use] extern crate lazy_static;
extern crate rayon;
extern crate regex;

use regex::Regex;

struct Star {
    x: i32,
    y: i32,
    x_vel: i32,
    y_vel: i32,
}

impl Star {
    pub fn new(x: i32, y: i32, x_vel: i32, y_vel: i32) -> Self {
        Star {
            x, y, x_vel, y_vel
        }
    }
}

struct Bounds {
    min_x: i32,
    max_x: i32,
    min_y: i32,
    max_y: i32,
}

impl Bounds {
    pub fn from_stars(stars: &Vec<Star>) -> Self {
        Bounds {
            min_x: stars.iter().min_by_key(|a| a.x).unwrap().x,
            max_x: stars.iter().max_by_key(|a| a.x).unwrap().x,
            min_y: stars.iter().min_by_key(|a| a.y).unwrap().y, 
            max_y: stars.iter().max_by_key(|a| a.y).unwrap().y 
        }
    }
}

lazy_static! {
    static ref STAR_REGEX: Regex = Regex::new(r"position=<\s?(-?\d+),\s{1,2}(-?\d+)>\svelocity=<\s?(-?\d+),\s{1,2}(-?\d+)>").unwrap();
}

fn parse_star(input: &str) -> Star {
    let captures = STAR_REGEX.captures(input).expect("unexpected input line");
    let (x, y, x_vel, y_vel): (i32, i32, i32, i32);
    x = captures.get(1).unwrap().as_str().parse::<i32>().unwrap();
    y = captures.get(2).unwrap().as_str().parse::<i32>().unwrap();
    x_vel = captures.get(3).unwrap().as_str().parse::<i32>().unwrap();
    y_vel = captures.get(4).unwrap().as_str().parse::<i32>().unwrap();
    return Star::new(x, y, x_vel, y_vel);
}

fn print_stars(stars: &Vec<Star>) {
    let bounds = Bounds::from_stars(&stars);
    let width: i32 = bounds.max_x - bounds.min_x;
    let height: i32 = bounds.max_y - bounds.min_y;
    let line = vec!('.'; (width + 1) as usize);
    let mut field = vec!(line.clone(); (height + 1) as usize);
    for star in stars {
        field[(star.y - bounds.min_y) as usize][(star.x - bounds.min_x) as usize] = '#';
    }
    for l in field {
        let s: String = l.into_iter().collect();
        println!("{}", s);
    }
}

fn main() {
    let lines: Vec<&str> = include_str!("../input").lines().collect();

    let mut stars: Vec<Star> = lines.iter().map(|line| parse_star(line)).collect();

    let mut bounds = Bounds::from_stars(&stars);
    let mut step = 0;
    while (bounds.max_y - bounds.min_y).abs() > 15 {
        stars.iter_mut().for_each(|star| {
            star.x += star.x_vel;
            star.y += star.y_vel;
        });
        bounds = Bounds::from_stars(&stars);
        step+=1;
    }
    // Part 1
    print_stars(&stars);
    
    // Part 2
    println!("{}", step);
}
