use super::Assessment;

use super::assessment;

use std::cell::{RefCell};
use std::fmt::{Display, Formatter};
use std::fs::{File};
use std::io::{BufWriter, Error as IoError, Write};
use std::ops::{Deref, DerefMut};
use std::path::{Path};

fn write_possible_assessment<W: Write>(mut writer: W, assessement: Option<&RefCell<Assessment>>) -> Result<(), IoError>
{
	match assessement
	{
		None => Ok(()),
		Some(a) => a.borrow().write_to(&mut writer).and_then(|_| write!(writer, "\n"))
	}
}

fn fidge_resolution_results_based_on_worst_score<T: Deref<Target=Assessment> + DerefMut<Target=Assessment>>(assessments: &mut [T]) -> ()
{
	let all_passed = assessments.iter().all(|a| a.deref().passed());
	if !all_passed
	{
		for assessment in assessments
		{
			assessment.set_failed();
			assessment.append_message(" Failed due to one or more other resolution assessments failing.");
		}
		return
	}

	let get_score = |a: &Assessment| a.score().unwrap_or(std::f64::MIN);
	assessments.sort_by(|a, b| get_score(a).total_cmp(&get_score(b)));
	let n = assessments.len();
	assessments[n - 1].set_passed();
	for assessment in assessments.iter_mut().take(n - 1)
	{
		assessment.set_failed();
		assessment.append_message(" Failed due to other resolution having a worse score.");
	}
}

#[derive(Debug, Default, PartialEq)]
pub struct Report 
{
	localisation_precision: Option<RefCell<Assessment>>,
	blinking: Option<RefCell<Assessment>>,
	bias: Option<RefCell<Assessment>>,
	squirrel: Option<RefCell<Assessment>>,
	frc_sampling_assessment: Option<RefCell<Assessment>>,
	frc_resolution_assessment: Option<RefCell<Assessment>>,
	drift: Option<RefCell<Assessment>>,
	magnification: Option<RefCell<Assessment>>,
}

impl Report
{
	pub fn new() -> Self
	{
		Self::default()
	}
	
	pub fn add_localisation_precision_assessment(&mut self, assessement: Assessment) -> ()
	{
		self.localisation_precision = Some(RefCell::new(assessement))
	}

	pub fn add_blinking_assessment(&mut self, assessement: Assessment) -> ()
	{
		self.blinking = Some(RefCell::new(assessement));
	}

	pub fn add_drift_assessment(&mut self, assessement: Assessment) -> ()
	{
		self.drift = Some(RefCell::new(assessement));
	}

	pub fn add_bias_assessment(&mut self, assessement: Assessment) -> ()
	{
		self.bias = Some(RefCell::new(assessement));
	}

	pub fn fidge_precision_assessments(&mut self) -> ()
	{
		let mut precision_assessments = Vec::new();
		match self.bias.as_ref()
		{
			None => {},
			Some(a) => precision_assessments.push(a.borrow_mut()),
		}
		match self.localisation_precision.as_ref()
		{
			None => {},
			Some(a) => precision_assessments.push(a.borrow_mut())
		}
		match self.frc_resolution_assessment.as_ref()
		{
			None => {},
			Some(a) =>  precision_assessments.push(a.borrow_mut())
		}
		if precision_assessments.is_empty()
		{
			return
		}
		fidge_resolution_results_based_on_worst_score(precision_assessments.as_mut_slice())
	}

	pub fn add_squirrel_assessment(&mut self, assessement: Assessment) -> ()
	{
		self.squirrel = Some(RefCell::new(assessement));
	}

	pub fn add_frc_sampling_assessment(&mut self, assessement: Assessment) -> ()
	{
		self.frc_sampling_assessment = Some(RefCell::new(assessement));
	}

	pub fn add_frc_resolution_assessment(&mut self, assessement: Assessment) -> ()
	{
		self.frc_resolution_assessment = Some(RefCell::new(assessement));
	}

	pub fn add_magnification_assessment(&mut self, assessement: Assessment) -> ()
	{
		self.magnification = Some(RefCell::new(assessement))
	}

	fn assessments(&self) -> Vec<(&str, Option<&RefCell<Assessment>>, &[&str; 3])>
	{
		vec![
				("localisation_precision", self.localisation_precision.as_ref(), &assessment::STANDARD_DICTIONARY),
				("blinking", self.blinking.as_ref(), &assessment::BLINK_DICTIONARY),
				("frc_resolution", self.frc_resolution_assessment.as_ref(), &assessment::FRC_RESOLUTION_DICTIONARY),
				("drift", self.drift.as_ref(), &assessment::DRIFT_DICTIONARY),
				("sampling", self.frc_sampling_assessment.as_ref(), &assessment::SAMPLING_DICTIONARY),
				("bias", self.bias.as_ref(), &assessment::HAWKMAN_DICTIONARY),
				("squirrel", self.squirrel.as_ref(), &assessment::SQUIRREL_DICTIONARY),
				("magnification", self.magnification.as_ref(), &assessment::MAGNIFICATION_DICTIONARY)
			]
	}

	fn write_to<W: Write>(&self, mut writer: W) -> Result<(), IoError>
	{
		let mut wa = |a| write_possible_assessment(&mut writer, a);

		let mut ok = Ok(());
		for a in self.assessments()
		{
			ok = ok.and(wa(a.1));
		}
		ok
	}

	pub fn write_to_directory<P: AsRef<Path>>(&self, directory: P) -> Result<(), IoError>
	{
		let dir = directory.as_ref();
		if !dir.exists()
		{
			let _ = std::fs::create_dir(&directory)?;
		}
		let mut ok = Ok(());
		for a in self.assessments()
		{
			ok = ok.and(self.write_assessment(&dir.join(a.0), a.1.map(|a| a.borrow()).as_deref(), a.2));
		}
		ok
	}

	fn write_assessment<P: AsRef<Path>>(&self, filename: P, assessment: Option<&Assessment>, dictionary: &[&str; 3]) -> Result<(), IoError>
	{		
		assessment.map_or(Ok(()), |a| File::create(filename).map(BufWriter::new).and_then(|w| a.write_as_sections(w, dictionary)))
	}
}

impl Display for Report
{
	fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error>
	{
		let mut buffer = Vec::new();
		let _ = self.write_to(&mut buffer);
		let message = match std::str::from_utf8(&buffer)
		{
			Err(e) => e.to_string(),
			Ok(s) => s.to_string()
		};
		write!(f, "{}", message)
	}
}

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

	#[test]
	fn if_not_all_resolutions_pass_there_is_no_correct_resolution()
	{
		let mut a = Assessment::pass_with("a", "");
		a.set_score(1.0);
		let mut b = Assessment::fail_with("b", "");
		b.set_score(2.0);
		
		assert_eq!(a.passed(), true);
		assert_eq!(b.passed(), false);
		{
			let mut data = [&mut a, &mut b];
			fidge_resolution_results_based_on_worst_score(&mut data);
		}
		
		assert_eq!(a.passed(), false);
		assert_eq!(a.message(), " Failed due to one or more other resolution assessments failing.");
		assert_eq!(b.passed(), false);
		assert_eq!(b.message(), " Failed due to one or more other resolution assessments failing.");
	}

	#[test]
	fn if_all_resolutions_pass_only_have_worst_score_as_passed()
	{
		let mut a = Assessment::pass_with("a", "");
		a.set_score(1.0);
		let mut b = Assessment::pass_with("b", "");
		b.set_score(2.0);
		
		{
			let mut data = [&mut a, &mut b];
			fidge_resolution_results_based_on_worst_score(&mut data);
		}
		assert_eq!(a.passed(), false);
		assert_eq!(a.message(), " Failed due to other resolution having a worse score.");
		assert_eq!(b.passed(), true);
	}

	#[test]
	fn if_all_resolutions_pass_but_score_not_set_ignore_it()
	{
		let mut a = Assessment::pass_with("a", "");
		a.set_score(1.0);
		let mut b = Assessment::pass_with("b", "");
		
		{
			let mut data = [&mut a, &mut b];
			fidge_resolution_results_based_on_worst_score(&mut data);
		}

		assert_eq!(a.passed(), true);
		assert_eq!(a.message(), "");
		assert_eq!(b.passed(), false);
		assert_eq!(b.message(), " Failed due to other resolution having a worse score.");
	}


	#[test]
	fn can_fidge_an_empty_report()
	{
		let mut report = Report::new();
		report.fidge_precision_assessments();
		assert_eq!(true, true)
	}
}