use rand::Rng;
use crate::city::{House, HouseLayout, City};
use rand::prelude::*;
use crate::db::{SqliteLayoutDB, SavedLayout, LayoutDB};
use crate::city;
use std::collections::HashMap;
use std::ops::Add;
use itertools::Itertools;
use rand::distributions::WeightedIndex;

pub fn populate_random(layout: &mut HouseLayout, rng: &mut StdRng) {
    loop {
        loop {
            let x = rng.gen_range(0..layout.city.width());
            let y = rng.gen_range(0..layout.city.height());
            let house = House::new(x, y);
            if layout.city.is_house_xy(x, y) && !layout.is_covered(house) {
                layout.add_house(house);
                break;
            }
        }

        if layout.is_valid() {
            break;
        }
    }
}

pub fn build_house_probabilities<TDB : LayoutDB>(city: &City, db: &TDB, min_score: f64, max_score: f64) -> (WeightedIndex<f64>, Vec<House>){
    let mut counts: HashMap<House, f64> = HashMap::new();
    for layout in db.layouts() {
        let price = city::get_price(&city, layout.houses()) as f64;
        let value_range = max_score - min_score;
        let value = 1. - ((price - min_score) / value_range).max(0.).min(1.);

        for house in layout.houses() {
            *counts.entry(*house).or_default() += value;
        }
    }

    let houses: Vec<_> = counts.into_iter().filter(|(house, price)| *price != 0.).collect();
    let index = WeightedIndex::new(houses.iter().map(|(house, price)| price)).unwrap();

    (index, houses.iter().map(|(house, _)| *house).collect())
}

pub fn populate_using_db<TDB: LayoutDB>(layout: &mut HouseLayout, mut rng: &mut StdRng, db: &TDB, min_score: f64, max_score: f64, db_probability: f64) {
    let (mut index, houses) = build_house_probabilities(&layout.city, db, min_score, max_score);

    loop {
        if rng.gen::<f64>() < db_probability {
            // TODO: Test without allowing buying covered tiles
            let house = houses[index.sample(&mut rng)];

            layout.add_house(house);

            if layout.is_valid() {
                break;
            }
        } else {
            loop {
                let x = rng.gen_range(0..layout.city.width());
                let y = rng.gen_range(0..layout.city.height());
                let house = House::new(x, y);
                if layout.city.is_house_xy(x, y) && !layout.is_covered(house) {
                    layout.add_house(house);
                    break;
                }
            }

            if layout.is_valid() {
                break;
            }
        }
    }
}

pub fn populate_from_saved_layout(layout: &mut HouseLayout, saved_layout: &SavedLayout) {
    for house in saved_layout.houses() {
        layout.add_house(*house);
    }
}