// Copyright 2019 Stichting Organism
// Copyright (c) 2018-2019 Prime Type Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! A Known Peer in the p3p system

use async_std::net::SocketAddr;
use std::{collections::BTreeSet, time::SystemTime};
use serde::{Deserialize, Serialize};
use crate::topic::{InterestLevel, Proximity, Subscription, Subscriptions, Topic};
use crate::id::NetworkID;


/// The data associated to a Node.
///
/// This can be gossiped through the topology in order to update
/// the topology of new nodes or _better_ neighbors.
///
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct KnownPeer {
    /// a unique identifier associated to the node
    pub(crate) id: NetworkID,

    /// the address to contact the node
    pub(crate) address: Option<SocketAddr>,

    /// all the subscription this node is interested about
    /// (with associated priority of interest)
    pub(crate) subscriptions: Subscriptions,

    /// the `Id` of the other `Node` this `Node` is aware of
    pub(crate) subscribers: BTreeSet<NetworkID>,

    /// this value denotes when this node exchange gossips
    /// with us for the last time.
    pub(crate) last_gossip: SystemTime,
}

impl KnownPeer {

    /// create a new unreachable Node with the given [`Id`].
    fn new(id: NetworkID) -> Self {
        KnownPeer {
            id,
            address: None,
            subscriptions: Subscriptions::default(),
            subscribers: BTreeSet::new(),
            last_gossip: SystemTime::now(),
        }
    }
    
    /// create a new Node with the given [`SocketAddr`].
    pub fn new_with(id: NetworkID, address: SocketAddr) -> Self {
        KnownPeer {
            id,
            address: Some(address),
            subscriptions: Subscriptions::default(),
            subscribers: BTreeSet::new(),
            last_gossip: SystemTime::now(),
        }
    }

    /// access the unique identifier of the `Node`.
    pub fn id(&self) -> &NetworkID {
        &self.id
    }

    /// get the Node's address (mean to contact it)
    pub fn address(&self) -> &Option<SocketAddr> {
        &self.address
    }

    /// these are the [`Topic`] and the [`InterestLevel`] associated.
    ///
    pub fn subscriptions(&self) -> impl Iterator<Item = (&Topic, &InterestLevel)> {
        self.subscriptions.iter()
    }

    /// the nodes that are related to this Node
    pub fn subscribers(&self) -> impl Iterator<Item = &NetworkID> {
        self.subscribers.iter()
    }

    /// add a subscription
    pub fn add_subscription(&mut self, subscription: Subscription) -> Option<InterestLevel> {
        self.subscriptions.add(subscription)
    }

    /// remove a subscriptions
    pub fn remove_subscription(&mut self, topic: Topic) -> Option<InterestLevel> {
        self.subscriptions.remove(topic)
    }

    /// list all common subscriptions between the two nodes
    pub fn common_subscriptions<'a>(&'a self, other: &'a Self) -> impl Iterator<Item = &'a Topic> {
        self.subscriptions
            .common_subscriptions(&other.subscriptions)
    }

    /// list all common subscribers between the two nodes
    pub fn common_subscribers<'a>(&'a self, other: &'a Self) -> impl Iterator<Item = &'a NetworkID> {
        self.subscribers.intersection(&other.subscribers)
    }

    /// compute the relative proximity between these 2 nodes.
    ///
    /// This is based on the subscription. The more 2 nodes have subscription
    /// in common the _closer_ they are.
    pub fn proximity(&self, other: &Self) -> Proximity {
        self.subscriptions.proximity_to(&other.subscriptions)
    }
}


#[cfg(test)]
mod test {
    use super::*;
    use quickcheck::{Arbitrary, Gen};

    impl Arbitrary for KnownPeer {
        fn arbitrary<G: Gen>(g: &mut G) -> Self {
            use std::ops::Sub;
            let address: Option<SocketAddr> = Arbitrary::arbitrary(g);
            let id = NetworkID::arbitrary(g);

            KnownPeer {
                id,
                address,
                subscriptions: Subscriptions::arbitrary(g),
                subscribers: Arbitrary::arbitrary(g),
                last_gossip: SystemTime::now()
                    .sub(std::time::Duration::new(u32::arbitrary(g) as u64, 0)),
            }
        }
    }
}