use crate::util::create_progress_bar;
use crate::{DataSet, Index, IndexValueVec, Model};
use hashbrown::HashSet;
use itertools::izip;
use log::info;
use rayon::prelude::*;
use std::sync::Mutex;
use std::time;

fn precision_at_k(
    max_k: usize,
    true_labels: &[HashSet<Index>],
    predicted_labels: &[IndexValueVec],
) -> Vec<f32> {
    assert_eq!(true_labels.len(), predicted_labels.len());
    let mut ps = vec![0.; max_k];
    for (truth, predictions) in izip!(true_labels, predicted_labels) {
        let mut n_correct = 0;
        for k in 0..max_k.min(predictions.len()) {
            if truth.contains(&predictions[k].0) {
                n_correct += 1;
            }
            ps[k] += n_correct as f32 / (k + 1) as f32;
        }
    }
    for p in &mut ps {
        *p /= predicted_labels.len() as f32;
    }
    ps
}

pub fn test_all(
    model: &Model,
    test_dataset: &DataSet,
    beam_size: usize,
) -> (Vec<IndexValueVec>, Vec<f32>) {
    let n_examples = test_dataset.feature_lists.len();
    let pb = Mutex::new(create_progress_bar(n_examples as u64));
    let start_t = time::Instant::now();
    let predicted_labels = test_dataset
        .feature_lists
        .par_iter()
        .map(|feature_vec| {
            let predictions = model.predict(feature_vec, beam_size);
            pb.lock().expect("Failed to lock progress bar").add(1);
            predictions
        })
        .collect::<Vec<_>>();
    info!(
        "Done testing on {} examples; it took {:.2}s",
        n_examples,
        start_t.elapsed().as_secs_f32()
    );

    let precisions = precision_at_k(5, &test_dataset.label_sets, &predicted_labels);
    info!(
        "Precision@[1, 3, 5] = [{:.2}, {:.2}, {:.2}]",
        precisions[0] * 100.,
        precisions[2] * 100.,
        precisions[4] * 100.,
    );

    (predicted_labels, precisions)
}
