use std::i32;
use std::io::Read;

use common::read_single_input;
use common::Solution;

fn power_at(serial: i32, (x, y): (i32, i32)) -> i32 {
    let rack_id = x + 10;
    let mut power_level = rack_id * y;
    power_level += serial;
    power_level *= rack_id;
    power_level = (power_level % 1000) / 100;
    power_level - 5
}

pub struct Day11 {
    power_grid: [[i32; 300]; 300],
}

impl Day11 {
    pub fn new() -> Self {
        Day11 {
            power_grid: [[0i32; 300]; 300],
        }
    }

    fn compute_summed_area(&mut self, serial: i32) {
        self.power_grid[0][0] = power_at(serial, (1, 1));
        for x in 1..300 {
            self.power_grid[0][x] = self.power_grid[0][x - 1] + power_at(serial, (x as i32 + 1, 1));
        }

        for y in 1..300 {
            self.power_grid[y][0] = self.power_grid[y - 1][0] + power_at(serial, (y as i32 + 1, 1));

            for x in 1..300 {
                let mut power = power_at(serial, (x as i32 + 1, y as i32 + 1));
                power += self.power_grid[y - 1][x];
                power += self.power_grid[y][x - 1];
                power -= self.power_grid[y - 1][x - 1];
                self.power_grid[y][x] = power;
            }
        }
    }

    fn best(&self, size: usize) -> (usize, usize, i32) {
        let mut best_coordinates = (1, 1);
        let mut best_result = self.power_grid[size - 1][size - 1];

        // First row
        for x in 0..(300 - size) {
            let score = self.power_grid[size - 1][x + size] - self.power_grid[size - 1][x];
            if score > best_result {
                best_result = score;
                best_coordinates = (x + 1, 1);
            }
        }

        // First column
        for y in 0..(300 - size) {
            let score = self.power_grid[y + size][size - 1] - self.power_grid[y][size - 1];
            if score > best_result {
                best_result = score;
                best_coordinates = (1, y + 1);
            }
        }

        // Remaining tiles
        for y in 0..(300 - size) {
            for x in 0..(300 - size) {
                let a = self.power_grid[y][x];
                let b = self.power_grid[y][x + size];
                let c = self.power_grid[y + size][x];
                let d = self.power_grid[y + size][x + size];
                let score = d + a - b - c;
                if score > best_result {
                    best_result = score;
                    best_coordinates = (x + 2, y + 2);
                }
            }
        }

        let (x, y) = best_coordinates;

        (x, y, best_result)
    }
}

impl Default for Day11 {
    fn default() -> Self {
        Self::new()
    }
}

impl Solution for Day11 {
    fn part1(&mut self, input: &mut dyn Read) -> String {
        let serial = read_single_input(input);
        self.compute_summed_area(serial);
        let (x, y, _) = self.best(3);

        format!("{},{}", x, y)
    }

    fn part2(&mut self, input: &mut dyn Read) -> String {
        let serial = read_single_input(input);
        self.compute_summed_area(serial);
        let mut best_result = 0;
        let mut best_option = None;
        for size in 1..=300 {
            let (x, y, result) = self.best(size);
            if result > best_result {
                best_result = result;
                best_option = Some((x, y, size));
            }
        }
        let (x, y, size) = best_option.unwrap();
        format!("{},{},{}", x, y, size)
    }
}

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

    #[test]
    fn test_power_at() {
        assert_eq!(4, power_at(8, (3, 5)));
        assert_eq!(-5, power_at(57, (122, 79)));
        assert_eq!(0, power_at(39, (217, 196)));
        assert_eq!(4, power_at(71, (101, 153)));
    }

    #[test]
    fn sample_part1() {
        let mut instance = Day11::new();
        assert_eq!("33,45", instance.part1(&mut b"18".as_ref()));
        assert_eq!("21,61", instance.part1(&mut b"42".as_ref()));
    }

    #[test]
    fn sample_part2() {
        let mut instance = Day11::new();
        assert_eq!("90,269,16", instance.part2(&mut b"18".as_ref()));
        assert_eq!("232,251,12", instance.part2(&mut b"42".as_ref()));
    }
}
