use log::trace;
use rand::{rngs::StdRng, Rng};
use std::cmp::Ordering;
use weighted_selector::WeightedSelector;
#[derive(Debug, PartialEq, Eq)]
pub struct Item<T> {
    pub absolute_time: u64,
    pub data: T,
}
#[derive(Default)]
pub struct TimeOrderedQueue<T> {
    pub items: Vec<Item<T>>,
}
impl<T> TimeOrderedQueue<T> {
    pub fn new() -> Self {
        Self { items: Vec::new() }
    }
    pub fn push(&mut self, absolute_time: u64, data: T) {
        let index = self
            .items
            .binary_search_by(|item| {
                match item.absolute_time.cmp(&absolute_time) {
                    Ordering::Equal => Ordering::Less, other => other,
                }
            })
            .unwrap_or_else(|x| x);
        self.items.insert(
            index,
            Item {
                absolute_time,
                data,
            },
        );
    }
    pub fn len(&self) -> usize {
        self.items.len()
    }
    pub fn is_empty(&self) -> bool {
        self.items.is_empty()
    }
    pub fn pop_ready(&mut self, absolute_time: u64) -> Option<Item<T>> {
        let first = self.items.first()?;
        if first.absolute_time > absolute_time {
            None
        } else {
            Some(self.items.remove(0))
        }
    }
}
#[derive(Debug, PartialEq)]
pub enum Decision {
    Drop,
    Tamper,
    Duplicate,
    Reorder,
    Unaffected,
}
pub struct Decider {
    selector: WeightedSelector<Decision>,
}
pub struct DeciderConfig {
    pub unaffected: usize,
    pub drop: usize,
    pub tamper: usize,
    pub duplicate: usize,
    pub reorder: usize,
}
impl Decider {
    pub fn new(config: DeciderConfig) -> Self {
        Self {
            selector: WeightedSelector::<Decision>::new(
                [
                    (config.unaffected, Decision::Unaffected),
                    (config.drop, Decision::Drop),
                    (config.tamper, Decision::Tamper),
                    (config.duplicate, Decision::Duplicate),
                    (config.reorder, Decision::Reorder),
                ]
                .into(),
            ),
        }
    }
    pub fn decide(&self, value: usize) -> Option<&Decision> {
        self.selector.select(value)
    }
    pub fn total(&self) -> usize {
        self.selector.total()
    }
}
pub struct DirectionConfig {
    pub decider: DeciderConfig,
}
pub struct Direction {
    pub decider: Decider,
    pub latency_in_ms: u64,
    pub datagrams: TimeOrderedQueue<Vec<u8>>,
    pub pseudo_random: StdRng,
}
impl Direction {
    pub fn new(config: DirectionConfig, pseudo_random: StdRng) -> Self {
        Self {
            latency_in_ms: 150,
            datagrams: TimeOrderedQueue::<Vec<u8>>::new(),
            decider: Decider::new(config.decider),
            pseudo_random,
        }
    }
    pub fn push(&mut self, absolute_time_now_ms: u64, datagram: &[u8]) {
        let value = self.pseudo_random.gen_range(0..self.decider.total());
        let decision = self.decider.decide(value).expect("decider should not fail");
        trace!("push: decision was {:?}", decision);
        let mut absolute_time = self.latency_in_ms + absolute_time_now_ms;
        match decision {
            Decision::Drop => return,
            Decision::Tamper => todo!(),
            Decision::Duplicate => self.datagrams.push(absolute_time, datagram.to_vec()),
            Decision::Reorder => {
                absolute_time += self.pseudo_random.gen_range(0..32) as u64;
            }
            Decision::Unaffected => {}
        }
        self.datagrams.push(absolute_time, datagram.to_vec());
    }
    pub fn pop_ready(&mut self, absolute_time_now_ms: u64) -> Option<Item<Vec<u8>>> {
        self.datagrams.pop_ready(absolute_time_now_ms)
    }
}