use super::{Assessment};

use crate::{tools::hawkman::HawkmanResult, settings::{Settings, report::BiasSettings}};

use std::path::{Path};


type Level = u32;

type Score = (Level, f64);


const ASSESSMENT_NAME: &str = "Bias";

fn first_level_over_threshold(threshold: f64, level_ordered_scores: &[Score]) -> bool
{
	level_ordered_scores.get(0).map(|t| t.1 > threshold).unwrap_or(false)
}

fn get_level(threshold: f64, level_ordered_scores: &[Score]) -> Result<Option<Level>, String>
{
	if first_level_over_threshold(threshold, level_ordered_scores)
	{
		return Ok(None)
	}

	for (idx, (_level, score)) in level_ordered_scores.iter().enumerate()
	{
		if *score > threshold
		{
			return Ok(idx.checked_sub(1)
					  .and_then(|i| level_ordered_scores.get(i))
					  .map(|(level, _)| *level))
		}
	}
	Err(format!("Unable to calculate threshold ({threshold}) crossing."))
}

fn level_to_nm(level: Option<Level>, sr_pixel_size: f64) -> f64
{
	level.map(|l| l as f64 * sr_pixel_size).unwrap_or(0.0)
}

fn test(precision_scalar: u8, max_localisation_precision: f64, calculated_precision: f64) -> bool
{
	// println!("{calculated_precision}");
	let threshold = (precision_scalar as f64) * max_localisation_precision;
	calculated_precision <= threshold
}

pub fn ok(threshold: f64, max_localisation_precision: f64, sr_pixel_size: f64, level_ordered_scores: &[Score]) -> Result<bool, String>
{
	get_level(threshold, level_ordered_scores).map(|l| level_to_nm(l, sr_pixel_size)).map(|p| test(2, max_localisation_precision, p))
}

fn get_max_precision(mean_precision: Option<f64>, settings: &BiasSettings) -> f64
{
	mean_precision.unwrap_or(settings.default_max_precision_nm())
}


fn get_details(max_precision_result: bool, max_precision: f64, precision_scalar: u8,) -> String
{
	if max_precision_result
	{
		"No bias detected.".to_string()
	}
	else
	{
		format!("precision is greater than {precision_scalar}x max precision {max_precision}.")
	}
}

pub fn assess<T: AsRef<Path>>(report_settings: &BiasSettings, settings: &Settings, hawkman_results: &HawkmanResult<T>, mean_precision: Option<f64>) -> Result<Assessment, String>
{
	let scores = hawkman_results.global_scores()?;
	let threshold = report_settings.threshold();
	match get_level(threshold, &scores)
	{
		Err(e) => 
		{
			Ok(Assessment::fail_with(ASSESSMENT_NAME, &e))
		}
		Ok(level) =>
		{
			match level
			{
				None => 
				{
					let details = "First level was over the threshold. This means no sharpening has been detected and resolution will be limited by localisation precision or sampling.";
					let mut assessment = Assessment::pass_with(ASSESSMENT_NAME, details);
					assessment.set_score(0.0);
					Ok(assessment)
				}
				Some(level) => 
				{
					let sr_pixel_size = settings.super_res_pixel_size_nm();
					let resolution_nm = level_to_nm(Some(level), sr_pixel_size);
					let max_precision = get_max_precision(mean_precision, report_settings);
					let scalar = report_settings.precision_scalar();
					let pass = test(scalar, max_precision, resolution_nm);
					let details = get_details(pass, max_precision, scalar);
					let mut assessment = Assessment::pass_fail(ASSESSMENT_NAME, pass, &details);
					assessment.set_score(resolution_nm);
					Ok(assessment)
				}
			}
		}
		
	}
}


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

	#[test]
	fn pass_with_less_than_2x_max_precision() 
	{
		assert_eq!(test(2, 2.0, 3.9999), true)
	}

	#[test]
	fn pass_with_exactly_2x_max_precision() 
	{
		assert_eq!(test(2, 2.0, 4.0), true)
	}

	#[test]
	fn fail_with_over_2x_max_precision()
	{
		assert_eq!(test(2, 2.0, 4.00001), false)
	}

	#[test]
	fn can_find_threshold_crossing_point()
	{
		let scores = [(1, 0.1), (2, 0.2)];
		assert_eq!(get_level(0.1, &scores).unwrap(), Some(1))
	}

	#[test]
	fn if_crossing_point_is_first_no_level()
	{
		let scores = [(1, 0.2)];
		assert_eq!(get_level(0.1, &scores).unwrap(), None)
	}

	#[test]
	fn levels_can_have_gaps()
	{
		let scores = [(1, 0.09), (2, 0.099), (3, 0.2)];
		assert_eq!(get_level(0.1, &scores).unwrap(), Some(2));

		let scores = [(1, 0.09), (3, 0.2)];
		assert_eq!(get_level(0.1, &scores).unwrap(), Some(1))
	}

	#[test]
	fn can_never_cross()
	{
		let scores = [(1, 0.2)];
		assert_eq!(get_level(0.3, &scores).unwrap_err(), "Unable to calculate threshold (0.3) crossing.")
	}

	#[test]
	fn bias_ok_basic()
	{
		let threshold = 0.2;
		let max_precision = 1.0;
		let sr_pixel_size = 1.0;
		let scores = [(1, 0.1), (2, 0.2), (3, 0.3)];
		assert_eq!(ok(threshold, max_precision, sr_pixel_size, &scores).unwrap(), true)
	}

	#[test]
	fn bias_fails_basic()
	{
		let threshold = 0.2;
		let max_precision = 0.8; // precision is 2.0 so > (2 * 0.8)
		let sr_pixel_size = 1.0;
		let scores = [(1, 0.1), (2, 0.2), (3, 0.3)];
		assert_eq!(ok(threshold, max_precision, sr_pixel_size, &scores).unwrap(), false)
	}

	#[test]
	fn bias_test_error_no_crossing()
	{
		let threshold = 0.3;
		let max_precision = 1.0;
		let sr_pixel_size = 1.0;
		let scores = [(1, 0.1), (2, 0.2), (3, 0.3)];
		assert_eq!(ok(threshold, max_precision, sr_pixel_size, &scores).unwrap_err(), "Unable to calculate threshold (0.3) crossing.")
	}

	#[test]
	fn get_details_passed()
	{
		assert_eq!(get_details(true, 0.0, 0), "No bias detected.");
	}

	#[test]
	fn get_details_failed()
	{
		assert_eq!(get_details(false, 10.1, 100), "precision is greater than 100x max precision 10.1.");
	}
}