//! Thread-safe packet/message utilization counters.

use crate::Error;
use crate::stats::Type;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

#[derive(Clone, Debug, PartialEq)]
pub struct UtilizationStatisticsStat {
    type_: Type,
    tx_count: i64,
    rx_count: i64,
    tx_bytes: i64,
    rx_bytes: i64,
}

impl Default for UtilizationStatisticsStat {
    fn default() -> Self {
        Self {
            type_: Type::Packet,
            tx_count: 0,
            rx_count: 0,
            tx_bytes: 0,
            rx_bytes: 0,
        }
    }
}

impl UtilizationStatisticsStat {
    pub fn new() -> Result<Self, Error> {
        Ok(Self::default())
    }

    #[must_use]
    pub fn type_(&self) -> Type {
        self.type_
    }
    pub fn set_type_(&mut self, value: Type) {
        self.type_ = value;
    }
    #[must_use]
    pub fn tx_count(&self) -> i64 {
        self.tx_count
    }
    pub fn set_tx_count(&mut self, value: i64) {
        self.tx_count = value;
    }
    #[must_use]
    pub fn rx_count(&self) -> i64 {
        self.rx_count
    }
    pub fn set_rx_count(&mut self, value: i64) {
        self.rx_count = value;
    }
    #[must_use]
    pub fn tx_bytes(&self) -> i64 {
        self.tx_bytes
    }
    pub fn set_tx_bytes(&mut self, value: i64) {
        self.tx_bytes = value;
    }
    #[must_use]
    pub fn rx_bytes(&self) -> i64 {
        self.rx_bytes
    }
    pub fn set_rx_bytes(&mut self, value: i64) {
        self.rx_bytes = value;
    }
}

#[derive(Clone, Debug, Default)]
pub struct UtilizationStatistics {
    statistics: Arc<Mutex<HashMap<String, UtilizationStatisticsStat>>>,
}

impl UtilizationStatistics {
    pub fn new() -> Result<Self, Error> {
        Ok(Self::default())
    }

    pub fn get_statistics(&self) -> Result<HashMap<String, UtilizationStatisticsStat>, Error> {
        Ok(self
            .statistics
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .clone())
    }

    pub fn update(&self, key: String, type_: Type, tx_bytes: i64, rx_bytes: i64) {
        let mut statistics = self
            .statistics
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner);
        let statistic = statistics.entry(key).or_default();
        statistic.type_ = type_;
        if rx_bytes > 0 {
            statistic.rx_count = statistic.rx_count.wrapping_add(1);
            statistic.rx_bytes = statistic.rx_bytes.wrapping_add(rx_bytes);
        }
        if tx_bytes > 0 {
            statistic.tx_count = statistic.tx_count.wrapping_add(1);
            statistic.tx_bytes = statistic.tx_bytes.wrapping_add(tx_bytes);
        }
    }
}

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

    #[test]
    fn updates_count_only_positive_transfers_and_snapshots_are_independent() {
        let statistics = UtilizationStatistics::new().unwrap();
        statistics.update("PacketAck".to_owned(), Type::Packet, 10, 0);
        statistics.update("PacketAck".to_owned(), Type::Packet, 5, 20);
        statistics.update("PacketAck".to_owned(), Type::Packet, -1, -1);
        let snapshot = statistics.get_statistics().unwrap();
        let stat = &snapshot["PacketAck"];
        assert_eq!(stat.tx_count(), 2);
        assert_eq!(stat.tx_bytes(), 15);
        assert_eq!(stat.rx_count(), 1);
        assert_eq!(stat.rx_bytes(), 20);
        assert_eq!(stat.type_(), Type::Packet);
    }
}
