use super::node::{Node, NodeId};
use parking_lot::RwLock;
use std::collections::VecDeque;
use std::net::SocketAddr;

const K: usize = 8;
const NUM_BUCKETS: usize = 160;

#[derive(Debug)]
struct Bucket {
    nodes: VecDeque<Node>,
    replacement_cache: VecDeque<Node>,
}

impl Bucket {
    fn new() -> Self {
        Self {
            nodes: VecDeque::with_capacity(K),
            replacement_cache: VecDeque::with_capacity(K),
        }
    }

    fn add(&mut self, node: Node) -> bool {
        if let Some(pos) = self.nodes.iter().position(|n| n.id == node.id) {
            let mut existing = self.nodes.remove(pos).unwrap();
            existing.touch();
            self.nodes.push_back(existing);
            return true;
        }

        if self.nodes.len() < K {
            self.nodes.push_back(node);
            return true;
        }

        if self.replacement_cache.len() < K {
            self.replacement_cache.push_back(node);
        }

        false
    }

    fn remove(&mut self, id: &NodeId) -> Option<Node> {
        if let Some(pos) = self.nodes.iter().position(|n| &n.id == id) {
            let removed = self.nodes.remove(pos);

            if let Some(replacement) = self.replacement_cache.pop_front() {
                self.nodes.push_back(replacement);
            }

            return removed;
        }

        None
    }

    fn get(&self, id: &NodeId) -> Option<&Node> {
        self.nodes.iter().find(|n| &n.id == id)
    }

    fn get_mut(&mut self, id: &NodeId) -> Option<&mut Node> {
        self.nodes.iter_mut().find(|n| &n.id == id)
    }

    fn oldest(&self) -> Option<&Node> {
        self.nodes.front()
    }

    fn nodes(&self) -> impl Iterator<Item = &Node> {
        self.nodes.iter()
    }

    fn good_nodes(&self) -> impl Iterator<Item = &Node> {
        self.nodes.iter().filter(|n| n.is_good())
    }
}

pub struct RoutingTable {
    our_id: NodeId,
    buckets: Vec<RwLock<Bucket>>,
}

impl RoutingTable {
    pub fn new(our_id: NodeId) -> Self {
        let buckets = (0..NUM_BUCKETS)
            .map(|_| RwLock::new(Bucket::new()))
            .collect();

        Self { our_id, buckets }
    }

    pub fn our_id(&self) -> &NodeId {
        &self.our_id
    }

    pub fn add_node(&self, node: Node) {
        if node.id == self.our_id {
            return;
        }

        let bucket_idx = self.our_id.bucket_index(&node.id);
        let mut bucket = self.buckets[bucket_idx].write();
        bucket.add(node);
    }

    pub fn remove_node(&self, id: &NodeId) {
        let bucket_idx = self.our_id.bucket_index(id);
        let mut bucket = self.buckets[bucket_idx].write();
        bucket.remove(id);
    }

    pub fn mark_failed(&self, id: &NodeId) {
        let bucket_idx = self.our_id.bucket_index(id);
        let mut bucket = self.buckets[bucket_idx].write();

        if let Some(node) = bucket.get_mut(id) {
            node.fail();

            if node.is_bad() {
                bucket.remove(id);
            }
        }
    }

    pub fn mark_seen(&self, id: &NodeId) {
        let bucket_idx = self.our_id.bucket_index(id);
        let mut bucket = self.buckets[bucket_idx].write();

        if let Some(node) = bucket.get_mut(id) {
            node.touch();
        }
    }

    pub fn find_closest(&self, target: &NodeId, count: usize) -> Vec<Node> {
        let mut nodes: Vec<(Node, [u8; 20])> = Vec::new();

        for bucket in &self.buckets {
            let bucket = bucket.read();
            for node in bucket.good_nodes() {
                let dist = node.id.distance(target);
                nodes.push((node.clone(), dist));
            }
        }

        nodes.sort_by(|a, b| a.1.cmp(&b.1));
        nodes.truncate(count);
        nodes.into_iter().map(|(n, _)| n).collect()
    }

    pub fn node_count(&self) -> usize {
        self.buckets.iter().map(|b| b.read().nodes.len()).sum()
    }

    pub fn all_nodes(&self) -> Vec<Node> {
        let mut nodes = Vec::new();
        for bucket in &self.buckets {
            let bucket = bucket.read();
            nodes.extend(bucket.nodes().cloned());
        }
        nodes
    }

    pub fn find_node(&self, id: &NodeId) -> Option<Node> {
        let bucket_idx = self.our_id.bucket_index(id);
        let bucket = self.buckets[bucket_idx].read();
        bucket.get(id).cloned()
    }

    pub fn find_by_addr(&self, addr: &SocketAddr) -> Option<Node> {
        for bucket in &self.buckets {
            let bucket = bucket.read();
            for node in bucket.nodes() {
                if &node.addr == addr {
                    return Some(node.clone());
                }
            }
        }
        None
    }

    pub fn stale_buckets(&self) -> Vec<usize> {
        let mut stale = Vec::new();

        for (i, bucket) in self.buckets.iter().enumerate() {
            let bucket = bucket.read();
            if let Some(oldest) = bucket.oldest() {
                if oldest.last_seen.elapsed().as_secs() > 15 * 60 {
                    stale.push(i);
                }
            }
        }

        stale
    }
}
