use rand::prelude::{StdRng, SliceRandom};
use rand::{SeedableRng, Rng, thread_rng};

pub const SIZE: usize = 16384;

pub struct City {
    prices: Vec<u16>,
    buyable_house_count: usize
}

impl City {
    pub fn read_from_file(filename: &str) -> Self {
        let values = std::fs::read(filename).unwrap();
        let mut prices: Vec<u16> = Vec::new();

        for y in 0..SIZE {
            for x in 0..SIZE {
                let price = (values[(y * SIZE + x) * 2] as u16) | ((values[(y * SIZE + x) * 2 + 1] as u16) << 8);
                prices.push(price);
            }
        }

        City::new(prices)
    }

    pub fn new(prices: Vec<u16>) -> Self {
        let mut buyable_house_count = 0;
        for &price in &prices {
            if price > 0 {
                buyable_house_count += 1;
            }
        }
        City { prices, buyable_house_count }
    }

    pub fn get_price(&self, house: &House) -> u16 {
        self.prices[house.y * SIZE + house.x]
    }

    pub fn get_price_xy(&self, x: usize, y: usize) -> u16 {
        self.prices[y * SIZE + x]
    }

    pub fn is_house(&self, house: &House) -> bool {
        self.get_price(&house) > 0
    }

    pub fn is_house_xy(&self, x: usize, y: usize) -> bool {
        self.get_price_xy(x, y) > 0
    }

    pub fn get_house_count(&self) -> usize {
        self.buyable_house_count
    }
}

#[derive(Eq, PartialEq, Hash, Copy, Clone)]
pub struct House {
    x: usize,
    y: usize,
}

impl House {
    pub fn new(x: usize, y: usize) -> Self {
        House { x, y }
    }
}

struct HouseLayout<'a> {
    city: &'a City,
    reachable: Vec<u16>,
    houses: Vec<House>,
    reachable_houses: usize
}

impl<'a> HouseLayout<'a> {
    pub fn new(city: &'a City) -> Self {
        HouseLayout { city, reachable: vec![0; SIZE * SIZE], houses: Vec::new(), reachable_houses: 0 }
    }

    pub fn cover_count(&self, house: House) -> u16 {
        self.reachable[house.y * SIZE + house.x]
    }

    pub fn is_covered(&self, house: House) -> bool {
        self.cover_count(house) > 0
    }

    pub fn add_house(&mut self, house: House) -> usize {
        for y in (house.y as i32 - 500).max(0)..=(house.y as i32 + 500).min((SIZE - 1) as i32) {
            for x in (house.x as i32 - 500).max(0)..=(house.x as i32 + 500).min((SIZE - 1) as i32) {
                let index = y as usize * SIZE + x as usize;

                if self.reachable[index] == 0 && self.city.is_house_xy(x as usize, y as usize) {
                    self.reachable_houses += 1;
                }

                self.reachable[index] += 1;
            }
        }
        self.houses.push(house);
        self.houses.len() - 1
    }

    pub fn remove_house(&mut self, index: usize) {
        let house = self.houses.swap_remove(index);

        for y in (house.y as i32 - 500).max(0)..=(house.y as i32 + 500).min((SIZE - 1) as i32) {
            for x in (house.x as i32 - 500).max(0)..=(house.x as i32 + 500).min((SIZE - 1) as i32) {
                let index = y as usize * SIZE + x as usize;

                self.reachable[index] -= 1;

                if self.reachable[index] == 0 && self.city.is_house_xy(x as usize, y as usize) {
                    self.reachable_houses -= 1;
                }
            }
        }
    }

    pub fn is_valid(&self) -> bool {
        self.reachable_houses == self.city.buyable_house_count
    }

    pub fn price(&self) -> u32 {
        get_price(self.city, &self.houses)
    }

    pub fn houses(&self) -> &Vec<House> {
        &self.houses
    }
}

fn main() {
    let city = City::read_from_file("01.in");

    const AROUND_RANGE: i32 = 200;
    const MAX_CANDIDATES: usize = 200;
    //const MAX_FAILED_ITERATIONS: usize = 50;
    println!("Params: AROUND_RANGE {}, MAX_CANDIDATES {}", AROUND_RANGE, MAX_CANDIDATES);
    eprintln!("Params: AROUND_RANGE {}, MAX_CANDIDATES {}", AROUND_RANGE, MAX_CANDIDATES);

    let mut best_price: Option<u32> = None;
    loop {
        let seed: u64 = thread_rng().gen();
        eprintln!("Starting seed {}", seed);

        let mut rng = StdRng::seed_from_u64(seed);
        let mut layout = HouseLayout::new(&city);
        loop {
            loop {
                let x = rng.gen_range(0..SIZE);
                let y = rng.gen_range(0..SIZE);
                let house = House::new(x, y);
                if city.is_house_xy(x, y) && !layout.is_covered(house) {
                    layout.add_house(house);
                    break;
                }
            }

            if layout.is_valid() {
                break;
            }
        }

        eprintln!("Finished random init, price: {}", layout.price());

        let mut untried_houses = layout.houses().clone();
        untried_houses.shuffle(&mut rng);

        while untried_houses.len() > 0 {
            let house = untried_houses.pop().unwrap();
            let mut house_index = layout.houses().iter().position(|x| *x == house).unwrap();

            let mut new_candidates = Vec::new();
            for delta_y in -AROUND_RANGE..=AROUND_RANGE {
                for delta_x in -AROUND_RANGE..=AROUND_RANGE {
                    let new_x = (house.x as i32 + delta_x).max(0).min(SIZE as i32 - 1) as usize;
                    let new_y = (house.y as i32 + delta_y).max(0).min(SIZE as i32 - 1) as usize;
                    if city.is_house_xy(new_x, new_y) && city.get_price_xy(new_x, new_y) < city.get_price(&house) {
                        new_candidates.push(House::new(new_x, new_y));
                    }
                }
            }

            new_candidates.sort_by(|a, b| city.get_price(&a).cmp(&city.get_price(&b)));
            if new_candidates.len() == 0 {
                //eprintln!("Did not find candidate");
            } else {
                for (i, &candidate) in new_candidates.iter().enumerate() {
                    if i > MAX_CANDIDATES {
                        //eprintln!("No valid candidate");
                        break;
                    }

                    //eprint!("Found candidate {}...", i);

                    let old_price = layout.price();
                    layout.remove_house(house_index);

                    if layout.is_valid() {
                        // The candidate is not needed, the house was unnecessary
                        let new_price = layout.price();
                        let price_diff = new_price as i64 - old_price as i64;
                        //eprintln!(" candidate is valid, price diff: {}.", price_diff);
                        eprintln!("Removed a house, diff {}", price_diff);
                        eprintln!("Improved price: {}", new_price);
                        untried_houses = layout.houses().clone();
                        untried_houses.shuffle(&mut rng);
                        break;
                    }

                    let candidate_index = layout.add_house(candidate);

                    if layout.is_valid() {
                        let new_price = layout.price();
                        let price_diff = new_price as i64 - old_price as i64;
                        //eprintln!(" candidate is valid, price diff: {}.", price_diff);
                        eprintln!("Improved price: {}", new_price);
                        untried_houses = layout.houses().clone();
                        untried_houses.shuffle(&mut rng);
                        break;
                    } else {
                        //eprintln!(" candidate is invalid.");
                        layout.remove_house(candidate_index);
                        house_index = layout.add_house(house);
                    }
                }
            }
        }

        let price = layout.price();
        if best_price.is_none() || price < best_price.unwrap() {
            best_price = Some(price);
            eprintln!("Finished randomization, price: {}, new best, printing", price);
            println!("Price {}, seed {}", price, seed);
            print_houses(&layout.houses());
            println!();
        } else {
            eprintln!("Finished randomization, price: {}, printing", price);
            println!("Price {}, seed {}", price, seed);
            print_houses(&layout.houses());
            println!();
        }
    }
}

pub fn get_neighbors(city: &City, house: &House) -> Vec<House> {
    let mut neighbors = Vec::new();
    for y in (house.y as i32 - 500).max(0)..=(house.y as i32 + 500).min((SIZE - 1) as i32) {
        for x in (house.x as i32 - 500).max(0)..=(house.x as i32 + 500).min((SIZE - 1) as i32) {
            let house = House::new(x as usize, y as usize);
            if city.get_price(&house) > 0 {
                neighbors.push(house);
            }
        }
    }

    neighbors
}

fn print_houses(houses: &Vec<House>) {
    println!("{}", houses.len());
    for house in houses {
        println!("{} {}", house.y, house.x);
    }
}

fn get_price(city: &City, houses: &Vec<House>) -> u32 {
    let mut price = 0u32;
    for house in houses {
        price += city.get_price(&house) as u32;
    }

    price
}

fn is_valid(city: &City, houses: &Vec<House>) -> Option<u32> {
    let mut reachable = vec![false; SIZE * SIZE];
    let mut price = 0u32;

    for house in houses {
        assert!(city.prices[house.y * SIZE + house.x] > 0);

        for y in (house.y as i32 - 500).max(0)..=(house.y as i32 + 500).min((SIZE - 1) as i32) {
            for x in (house.x as i32 - 500).max(0)..=(house.x as i32 + 500).min((SIZE - 1) as i32) {
                reachable[y as usize * SIZE + x as usize] = true;
            }
        }
        price += city.get_price(&house) as u32;
    }

    for y in 0..SIZE {
        for x in 0..SIZE {
            if !reachable[y * SIZE + x] && city.prices[y * SIZE + x] > 0 {
                return None;
            }
        }
    }

    Some(price)
}