use std::{fmt, fmt::Write};
use necsim_core::{
    event::{DispersalEvent, SpeciationEvent},
    impl_finalise, impl_report,
    lineage::LineageInteraction,
    reporter::Reporter,
};
use necsim_core_bond::NonNegativeF64;
#[allow(clippy::module_name_repetitions)]
#[derive(Default)]
pub struct EventCounterReporter {
    last_parent_prior_time: Option<NonNegativeF64>,
    last_speciation_event: Option<SpeciationEvent>,
    last_dispersal_event: Option<DispersalEvent>,
    raw_total: usize,
    speciation: usize,
    out_dispersal: usize,
    self_dispersal: usize,
    out_coalescence: usize,
    self_coalescence: usize,
    late_dispersal_coalescence: usize,
    late_coalescence: usize,
}
impl fmt::Debug for EventCounterReporter {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct(stringify!(EventCounterReporter))
            .field("speciation", &self.speciation)
            .field("out_dispersal", &self.out_dispersal)
            .field("self_dispersal", &self.self_dispersal)
            .field("out_coalescence", &self.out_coalescence)
            .field("self_coalescence", &self.self_coalescence)
            .field(
                "late_dispersal",
                &(self.late_dispersal_coalescence - self.late_coalescence),
            )
            .field("late_coalescence", &self.late_coalescence)
            .finish_non_exhaustive()
    }
}
impl serde::Serialize for EventCounterReporter {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_unit()
    }
}
impl<'de> serde::Deserialize<'de> for EventCounterReporter {
    fn deserialize<D: serde::Deserializer<'de>>(_deserializer: D) -> Result<Self, D::Error> {
        Ok(Self::default())
    }
}
impl Reporter for EventCounterReporter {
    impl_report!(speciation(&mut self, speciation: Used) {
        self.raw_total += 1;
        if Some(speciation) == self.last_speciation_event.as_ref() {
            if Some(speciation.prior_time) != self.last_parent_prior_time {
                self.late_coalescence += 1;
            }
            self.last_parent_prior_time = Some(speciation.prior_time);
            return;
        }
        self.last_speciation_event = Some(speciation.clone());
        self.last_parent_prior_time = Some(speciation.prior_time);
        self.speciation += 1;
    });
    impl_report!(dispersal(&mut self, dispersal: Used) {
        self.raw_total += 1;
        if Some(dispersal) == self.last_dispersal_event.as_ref() {
            if Some(dispersal.prior_time) != self.last_parent_prior_time {
                self.late_coalescence += 1;
            }
            self.last_parent_prior_time = Some(dispersal.prior_time);
            return;
        }
        self.last_dispersal_event = Some(dispersal.clone());
        self.last_parent_prior_time = Some(dispersal.prior_time);
        let self_dispersal = dispersal.origin == dispersal.target;
        let coalescence = if dispersal.interaction == LineageInteraction::Maybe {
            self.late_dispersal_coalescence += 1;
            return
        } else { dispersal.interaction.is_coalescence() };
        match (self_dispersal, coalescence) {
            (true, true) => {
                self.self_coalescence += 1;
            },
            (true, false) => {
                self.self_dispersal += 1;
            },
            (false, true) => {
                self.out_coalescence += 1;
            },
            (false, false) => {
                self.out_dispersal += 1;
            },
        }
    });
    impl_report!(progress(&mut self, _progress: Ignored) {});
    impl_finalise!((self) {
        if self.last_speciation_event.is_none() && self.last_dispersal_event.is_none() {
            return;
        }
        let mut event_summary = String::new();
        let _ = writeln!(&mut event_summary, "Event Summary:");
        let _ = writeln!(
            &mut event_summary,
            " - Total #individuals:\n   {}",
            self.speciation + self.self_coalescence + self.out_coalescence + self.late_coalescence
        );
        let _ = writeln!(
            &mut event_summary,
            " - Total #events:\
            \n   - raw:\n     {}\
            \n   - deduplicated:\n     {}",
            self.raw_total,
            self.speciation
                + self.self_coalescence
                + self.out_coalescence
                + self.self_dispersal
                + self.out_dispersal
                + self.late_dispersal_coalescence
        );
        let _ = writeln!(
            &mut event_summary,
            " - Speciation:\
            \n    {}",
            self.speciation
        );
        let _ = writeln!(
            &mut event_summary,
            " - Dispersal:\
            \n   - same location, no coalescence:\n     {}\
            \n   - same location, with coalescence:\n     {}\
            \n   - new location, no coalescence:\n     {}\
            \n   - new location, with coalescence:\n     {}\
            \n   - detected late, no coalescence:\n     {}\
            \n   - detected late, with coalescence:\n     {}",
            self.self_dispersal,
            self.self_coalescence,
            self.out_dispersal,
            self.out_coalescence,
            self.late_dispersal_coalescence - self.late_coalescence,
            self.late_coalescence
        );
        log::info!("{}", event_summary);
    });
}