//! Filters
//!
//! This is used for clients to requests for events and subscribe to new events.
//!
//! Each property is treated as an AND, but a vector of filters can be provided,
//! and they will be treated as OR.
use super::Kind;
use chrono::{DateTime, Utc};
use serde::{
    de::{MapAccess, Visitor},
    ser::SerializeMap,
    Deserialize, Deserializer, Serialize, Serializer,
};
use std::{
    collections::{HashMap, HashSet},
    fmt,
};

#[derive(Serialize, Deserialize, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Clone)]
#[serde(untagged)]
/// Tag filter value
pub enum TagValue {
    /// Tag another event
    Id(super::Id),
    /// Any non standard tag
    String(String),
}

const SEPARATOR: &[u8] = b"\x1f\xcc";

impl TagValue {
    /// Convert the value into bytes
    pub fn into_bytes(self, id: &str) -> Vec<u8> {
        let value = match self {
            TagValue::Id(addr) => addr.into_bytes(),
            TagValue::String(s) => s.into_bytes(),
        };

        let mut bytes = Vec::with_capacity(id.len() + SEPARATOR.len() + value.len());
        bytes.extend_from_slice(id.as_bytes());
        bytes.extend_from_slice(SEPARATOR);
        bytes.extend_from_slice(&value);
        bytes
    }
}

fn deserialize_tags<'de, D>(deserializer: D) -> Result<HashMap<String, HashSet<TagValue>>, D::Error>
where
    D: Deserializer<'de>,
{
    struct TagVisitor;

    impl<'de> Visitor<'de> for TagVisitor {
        type Value = HashMap<String, HashSet<TagValue>>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("a map with special #<one-letter> fields")
        }

        fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
        where
            A: MapAccess<'de>,
        {
            let mut tags = HashMap::new();
            while let Some((key, value)) = map.next_entry::<String, _>()? {
                if key.starts_with('#') {
                    tags.insert(key[1..].to_string(), value);
                }
            }
            Ok(tags)
        }
    }

    deserializer.deserialize_map(TagVisitor)
}

fn serialize_tags<S>(
    tags: &HashMap<String, HashSet<TagValue>>,
    serializer: S,
) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    let mut map = serializer.serialize_map(Some(tags.len()))?;
    for (key, value) in tags {
        map.serialize_entry(&format!("#{}", key), value)?;
    }
    map.end()
}

/// Filter
///
/// As defined in Nostr, all this filters options are AND, meaning that an object must match all the
/// requirements in order to be included in the resultset
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct Filter {
    /// Fetch events by their Id, or subscribe
    ///
    /// A full Id can be provided, or a prefix.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub ids: Vec<super::Id>,
    /// Fetch events by a public key, or a prefix
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub authors: Vec<super::Id>,
    /// Fetch events by their kinds
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub kinds: Vec<Kind>,
    /// Fetch events by their tags
    /// Although only single letter tags should be indexed per NIP-01, using
    /// a string will allow to tweak the filter in the future
    #[serde(
        flatten,
        serialize_with = "serialize_tags",
        deserialize_with = "deserialize_tags"
    )]
    pub tags: HashMap<String, HashSet<TagValue>>,
    #[serde(
        default,
        with = "super::option_ts_seconds",
        skip_serializing_if = "Option::is_none"
    )]
    /// Fetch events newer to this given timestamp
    pub since: Option<DateTime<Utc>>,
    /// Fetch events older to this timestamp
    #[serde(
        default,
        with = "super::option_ts_seconds",
        skip_serializing_if = "Option::is_none"
    )]
    pub until: Option<DateTime<Utc>>,
    /// Has a limit to fetch stored events
    #[serde(default, skip_serializing_if = "is_zero")]
    pub limit: u64,
}

#[inline]
fn is_zero(number: &u64) -> bool {
    *number == 0
}

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

    #[test]
    fn deserialize() {
        let json = "{\"authors\":[\"a42007e33cfa25673b26f46f39df039aa6003258a68dc88f1f1e0447607aedb3\", \"9984188a6578eb513fddcf658f389dbd532e54b82b628ad36666f7aa8f731b79\"]}";
        let obj: Result<Filter, _> = serde_json::from_str(json);
        assert!(obj.is_ok());
    }

    #[test]
    fn serialize() {
        let now = Utc::now();
        let x = Filter {
            since: Some(now),
            ..Default::default()
        };

        assert_eq!(
            format!("{}\"since\":{:?}{}", "{", now.timestamp(), "}"),
            serde_json::to_string(&x).expect("valid json")
        );
    }
}
