// 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.

use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use crate::topic::{
    Topic,
    InterestLevel,
    PriorityScore,
    Proximity,
    ProximityScore
};

/// This described a subscription to a topic.
#[derive(Clone, Debug)]
pub struct Subscription {
    pub topic: Topic,
    pub interest_level: InterestLevel,
}

impl Subscription {
    pub fn new(topic: Topic, interest_level: InterestLevel) -> Self {
        Subscription {
            topic,
            interest_level,
        }
    }
}


impl PartialEq<Topic> for Subscription {
    fn eq(&self, topic: &Topic) -> bool {
        &self.topic == topic
    }
}
impl PartialEq<Self> for Subscription {
    fn eq(&self, other: &Self) -> bool {
        self.topic == other.topic
    }
}
impl Eq for Subscription {}
impl PartialOrd<Topic> for Subscription {
    fn partial_cmp(&self, topic: &Topic) -> Option<std::cmp::Ordering> {
        self.topic.partial_cmp(topic)
    }
}
impl PartialOrd<Self> for Subscription {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.topic.partial_cmp(&other.topic)
    }
}
impl Ord for Subscription {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.topic.cmp(&other.topic)
    }
}
impl std::hash::Hash for Subscription {
    fn hash<H>(&self, state: &mut H)
    where
        H: std::hash::Hasher,
    {
        std::hash::Hash::hash(&self.topic, state)
    }
}

#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct Subscriptions(HashMap<Topic, InterestLevel>);


impl Default for Subscriptions {
    fn default() -> Self {
        Subscriptions(HashMap::default())
    }
}

impl Subscriptions {
    /// add a new subscription, return the replaced/updated subscription if
    /// topic already present.
    pub fn add(&mut self, subscription: Subscription) -> Option<InterestLevel> {
        self.0
            .insert(subscription.topic, subscription.interest_level)
    }

    pub fn contains(&self, topic: Topic) -> bool {
        self.0.contains_key(&topic)
    }

    pub fn remove(&mut self, subscription: Topic) -> Option<InterestLevel> {
        self.0.remove(&subscription)
    }

    pub fn topics(&self) -> impl Iterator<Item = &Topic> {
        self.0.keys()
    }

    pub fn iter(&self) -> impl Iterator<Item = (&Topic, &InterestLevel)> {
        self.0.iter()
    }

    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Topic, &mut InterestLevel)> {
        self.0.iter_mut()
    }

    /// retrieve the iterator over the topics common between both subscriptions
    pub fn common_subscriptions<'a>(&'a self, other: &'a Self) -> impl Iterator<Item = &'a Topic> {
        self.0
            .keys()
            .filter(move |topic| other.0.contains_key(topic))
    }

    pub fn proximity_to(&self, other: &Self) -> Proximity {
        let mut priority_score = 0;
        let mut proximity_score = 0;
        for (subscription, interest_level) in self.iter() {
            if let Some(other_interest_level) = other.0.get(subscription) {
                proximity_score += 1;
                priority_score += interest_level.priority_score(*other_interest_level);
            }
        }
        Proximity {
            proximity: ProximityScore(proximity_score),
            priority: PriorityScore(priority_score),
        }
    }
}