mod alignment;
mod calibration;

use super::Assessment;

use crate::{Error};
use crate::settings::{report::RatioSettings};

use frc::{Resolution as FrcResolution};

use std::path::{Path};

const SAMPLING_ASSESSMENT_NAME : &str = "Sampling";
const DRIFT_ASSESSMENT_NAME : &str = "Drift";
const FRC_RESOLUTION_NAME : &str = "FRC Resolution";
const FRC_MAGNIFICATION_NAME : &str = "FRC Magnification";


fn get_index_closest_to<I: Iterator<Item=f64>>(target: f64, values: I) -> Option<usize>
{
	values.enumerate()
		  .filter(|(_, x)| x.is_finite())
		  .map(|(idx, x)| (idx, (x - target).powi(2).sqrt()))
		  .min_by(|(_, x), (_, y)| x.total_cmp(y))
		  .map(|(idx, _)| idx)
}

fn spatial_freq_for_correlation(target: f64, qs: &[f64], correlations: &[f64]) -> Option<f64>
{
	get_index_closest_to(target, correlations.iter().copied()).and_then(|idx| qs.get(idx)).copied()
}

fn spatial_freq_for_correlation_from(target: f64, resolution: &FrcResolution) -> Option<f64>
{
	spatial_freq_for_correlation(target, resolution.qs(), resolution.correlations())
}

fn relative_difference(x: f64, y: f64) -> f64
{
	let diff = x - y;
	let abs_diff = (diff * diff).sqrt();
	let sum = x + y;
	abs_diff / (sum / 2.0)
}

// fn percent_difference(x: f64, y: f64) -> f64
// {
// 	relative_difference(x, y) * 100.0
// }

fn error_message(target: f64, name: &str) -> String
{
	format!("Could not calculate spatial frequency at {target} correlation for {name}")
}

fn get_sampling_ratio(lower: f64, upper: f64) -> f64
{
	upper / lower
}

fn sampling_ratio(settings: &RatioSettings, curve: &FrcResolution) -> Result<(f64, f64), String>
{
	// lower is 1/7 and upper is 6/7
	// we want 6/7 / 1/7 for sampling report
	let lower = spatial_freq_for_correlation_from(settings.lower(), curve).ok_or_else(|| error_message(settings.lower(), "lower ratio"))?;
	let upper = spatial_freq_for_correlation_from(settings.upper(), curve).ok_or_else(|| error_message(settings.upper(), "upper ratio"))?;
	Ok((lower, upper))
}

pub fn assess_drift(drift_split: &FrcResolution, half_split: &FrcResolution, threshold: f64) -> Result<Assessment, String>
{
	let drift_res = drift_split.as_value();
	let half_res = half_split.as_value();
	let score = drift_res / half_res;
	let passed = relative_difference(drift_res, half_res) <= threshold;
	let core = format!("drift split: {drift_res} half split: {half_res}");
	let message = if passed {core} else{ format!("{core}. Your data still contains drift or photobleaching")};
	let mut assessment = Assessment::pass_fail(DRIFT_ASSESSMENT_NAME, passed, &message);
	assessment.set_score(score);
	Ok(assessment)
}

pub fn assess_sampling(threshold: f64, settings: &RatioSettings, drift_split: &FrcResolution) -> Result<Assessment, String>
{
	let (lower, upper) = sampling_ratio(settings, drift_split)?;
	let ratio = get_sampling_ratio(lower, upper);
	let passed = ratio >= threshold;
	let core = format!("lower bound spatial frequency: {lower} upper bound spatial frequency: {upper}");
	// println!("ratio: {:?}\n{ratio} >= {threshold} = {passed}", settings);
	let message = if passed {core} else {format!("{core}. Acquire for longer or increase blinking rate")};
	let mut assessment = Assessment::pass_fail(SAMPLING_ASSESSMENT_NAME, passed, &message);
	assessment.set_score(ratio);
	Ok(assessment)
}

pub fn assess_resolution(drift_split: &FrcResolution, pixel_size: f64) -> Assessment
{
	let res = drift_split.to_nm(pixel_size);
	let mut assessment = Assessment::pass_with(FRC_RESOLUTION_NAME, "Score is in nm.");
	assessment.set_score(res);
	assessment
}

fn get_base_magnification_assessment(spatial_frequency: f64, lower: f64, upper: f64) -> Assessment
{
	// magnification too high
	if spatial_frequency < lower
	{
		let message = "Magnification too high. No extra information is gained from this level of magnification and downstream processing may be affected by the sparsity of the images.";
		Assessment::inderterminate_with(FRC_MAGNIFICATION_NAME, message)
	}
	// magnification too low
	else if upper < spatial_frequency
	{
		let message = "Magnification too low, you may be missing information.";
		Assessment::fail_with(FRC_MAGNIFICATION_NAME, message)
	}
	else 
	{
		Assessment::pass_with(FRC_MAGNIFICATION_NAME, "")
	}
}

fn assess_magnification_with_spatial_freq(spatial_frequency: f64, lower: f64, upper: f64) -> Assessment
{	
	let mut assessment = get_base_magnification_assessment(spatial_frequency, lower, upper);
	assessment.set_score(spatial_frequency);
	assessment
}

pub fn assess_magnification(drift_split: &FrcResolution, lower: f64, upper: f64) -> Assessment
{
	let value = drift_split.to_spatial_frequency();
	assess_magnification_with_spatial_freq(value, lower, upper)
}

pub fn align_to_calibration_space(frc: &FrcResolution, magnification: f64, pixel_size: f64) -> FrcResolution
{
	let new_qs = alignment::align_to_calibration(frc.qs(), magnification, pixel_size);
	frc.realign_with(new_qs)
}

pub fn write_calibration_curves_to<P: AsRef<Path>>(output_directory: P) -> Result<(), Error>
{
	calibration::write_calibration_data(output_directory)
}

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

	#[test]
	fn sampling_ratio_is_upper_over_lower() // i.e f(6/7) / f(1/7)
	{
		assert_eq!(get_sampling_ratio(1.0, 2.0), 2.0)
	}

	#[test]
	fn magnification_ok()
	{
		let assessment = assess_magnification_with_spatial_freq(0.5, 0.0, 1.0);
		assert_eq!(assessment.score(), Some(0.5));
		assert_eq!(assessment.passed(), true);
		assert_eq!(assessment.message(), "");
	}

	#[test]
	fn magnification_too_low()
	{
		let assessment = assess_magnification_with_spatial_freq(0.5, 0.2, 0.4);
		assert_eq!(assessment.score(), Some(0.5));
		assert_eq!(assessment.passed(), false);
		assert_eq!(assessment.failed(), true);
		assert_eq!(assessment.message(), "Magnification too low, you may be missing information.");
	}

	#[test]
	fn magnification_too_high()
	{
		let assessment = assess_magnification_with_spatial_freq(0.1, 0.2, 0.4);
		assert_eq!(assessment.score(), Some(0.1));
		assert_eq!(assessment.passed(), false);
		assert_eq!(assessment.failed(), false);
		assert_eq!(assessment.indeterminate(), true);
		assert_eq!(assessment.message(), "Magnification too high. No extra information is gained from this level of magnification and downstream processing may be affected by the sparsity of the images.");
	}

	// #[test]
	// fn assessment_test_ok() 
	// {
	// 	assert_eq!(assess_sampling_with(1.0, 0.0, 0.999999), true)
	// }

	// #[test]
	// fn assessment_description_ok() 
	// {
	// 	let message = describe_sampling_assessment(1.0, 0.0, 0.999);
	// 	let expected = "Sampling is assessed with the FRC by taking the two extreme splits of the data and measuring their similarity. For well sampled data these values should be very similar. Your data resulted in a distance of 0.999, this is within the tolerance specified 1. The first split of the data cuts it in half (by index rather than frame number), and achieved a resolution of 0, whilst the second split takes alternate frames (like a zip) and achieved a resolution of 0.999.";
	// 	assert_eq!(message, expected)
	// }

	// #[test]
	// fn assessment_test_ok_equal() 
	// {
	// 	assert_eq!(assess_sampling_with(1.0, 0.0, 1.0), true)
	// }

	// #[test]
	// fn assessment_test_fail() 
	// {
	// 	assert_eq!(assess_sampling_with(1.0, 0.0, 1.0000001), false)
	// }

	// #[test]
	// fn assessment_description_fail() 
	// {
	// 	let message = describe_sampling_assessment(1.0, 0.0, 1.1);
	// 	let expected = "Sampling is assessed with the FRC by taking the two extreme splits of the data and measuring their similarity. For well sampled data these values should be very similar. Your data resulted in a distance of 1.1, this is outside the tolerance specified 1. The first split of the data cuts it in half (by index rather than frame number), and achieved a resolution of 0, whilst the second split takes alternate frames (like a zip) and achieved a resolution of 1.1.";
	// 	assert_eq!(message, expected)
	// }

	// #[test]
	// fn assess_resolutions_test() 
	// {
	// 	let settings = ReportSettings::default().with_target_resolution(1.51)
	// 									  .with_fail_on_any_over(false)
	// 									  .with_fail_on_invalid_resolution(false);
	// 	let resolutions = [1.0, 2.0];
	// 	let assessment = assess_resolutions(&settings, resolutions.iter().copied());
	// 	let expected = "Mean resolution was under target resolution. Mean: 1.5 target: 1.51";
	// 	assert_eq!(assessment.passed(), true);
	// 	assert_eq!(assessment.message(), expected)
	// }

	// #[test]
	// fn assess_resolutions_mean_value_over() 
	// {
	// 	let settings = ReportSettings::default().with_target_resolution(1.4999)
	// 									  .with_fail_on_any_over(false)
	// 									  .with_fail_on_invalid_resolution(false);
	// 	let resolutions = [1.0, 2.0];
	// 	let assessment = assess_resolutions(&settings, resolutions.iter().copied());
	// 	let expected = "Mean resolution was over target resolution. Mean: 1.5 target: 1.4999";
	// 	assert_eq!(assessment.passed(), false);
	// 	assert_eq!(assessment.message(), expected)
	// }

	// #[test]
	// fn assess_resolutions_every_value_under() 
	// {
	// 	let settings = ReportSettings::default().with_target_resolution(2.0)
	// 									  .with_fail_on_any_over(true)
	// 									  .with_fail_on_invalid_resolution(false);
	// 	let resolutions = [1.0, 2.0];
	// 	let assessment = assess_resolutions(&settings, resolutions.iter().copied());
	// 	let expected = "All resolutions were under the target resolution of 2";
	// 	assert_eq!(assessment.passed(), true);
	// 	assert_eq!(assessment.message(), expected)
	// }

	// #[test]
	// fn assess_resolutions_not_every_value_under() 
	// {
	// 	let settings = ReportSettings::default().with_target_resolution(1.999).with_fail_on_any_over(true).with_fail_on_invalid_resolution(false);
	// 	let resolutions = [1.0, 2.0];
	// 	let assessment = assess_resolutions(&settings, resolutions.iter().copied());
	// 	let expected = "Not all resolutions were under the target resolution of 1.999";
	// 	assert_eq!(assessment.passed(), false);
	// 	assert_eq!(assessment.message(), expected)
	// }

	// #[test]
	// fn assess_resolutions_filter_invalid_resolutions_out() 
	// {
	// 	let settings = ReportSettings::default().with_target_resolution(1.51)
	// 									  .with_fail_on_any_over(false)
	// 									  .with_fail_on_invalid_resolution(false);
	// 	let resolutions = [1.0, std::f64::NAN, 2.0];
	// 	let assessment = assess_resolutions(&settings, resolutions.iter().copied());
	// 	let expected = "Mean resolution was under target resolution. Mean: 1.5 target: 1.51";
	// 	assert_eq!(assessment.passed(), true);
	// 	assert_eq!(assessment.message(), expected)
	// }

	// #[test]
	// fn assess_resolutions_fail_on_invalid_resolution() 
	// {
	// 	let settings = ReportSettings::default().with_target_resolution(1.999)
	// 									  .with_fail_on_any_over(true)
	// 									  .with_fail_on_invalid_resolution(true);
	// 	let resolutions = [1.0, std::f64::NAN, 2.0];
	// 	let assessment = assess_resolutions(&settings, resolutions.iter().copied());
	// 	let expected = "1/3 resolutions did not provide a finite value";
	// 	assert_eq!(assessment.passed(), false);
	// 	assert_eq!(assessment.message(), expected)
	// }
}