use super::StatusVisibility;
use crate::entities as MegalodonEntities;
use serde::ser::SerializeStruct;
use serde::{de, ser, Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq)]
pub struct Preferences {
    pub posting_default_visibility: StatusVisibility,
    pub posting_default_sensitive: bool,
    pub posting_default_language: Option<String>,
    pub reading_expand_media: ExpandMedia,
    pub reading_expand_spoilers: bool,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ExpandMedia {
    Default,
    ShowAll,
    HideAll,
}

const FIELDS: &'static [&'static str] = &[
    "posting:default:visibility",
    "posting:default:sensitive",
    "posting:default:language",
    "reading:expand:media",
    "reading:expand:spoilers",
];

impl ser::Serialize for Preferences {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut state = serializer.serialize_struct("Preferences", FIELDS.len())?;
        state.serialize_field(
            "posting:default:visibility",
            &self.posting_default_visibility,
        )?;
        state.serialize_field("posting:default:sensitive", &self.posting_default_sensitive)?;
        state.serialize_field("posting:default:language", &self.posting_default_language)?;
        state.serialize_field("reading:expand:media", &self.reading_expand_media)?;
        state.serialize_field("reading:expand:spoilers", &self.reading_expand_spoilers)?;
        state.end()
    }
}

impl<'de> de::Deserialize<'de> for Preferences {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        enum Field {
            PostingDefaultVisibility,
            PostingDefaultSensitive,
            PostingDefaultLanguage,
            ReadingExpandMedia,
            ReadingExpandSpoilers,
        }
        impl<'de> de::Deserialize<'de> for Field {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: serde::Deserializer<'de>,
            {
                struct FieldVisitor;

                impl<'de> de::Visitor<'de> for FieldVisitor {
                    type Value = Field;

                    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                        formatter.write_str("`posting:default:visibility` or `posting:default:sensitive` or `posting:default:language` or `reading:expand:media` or `reading:expand:spoilers`")
                    }

                    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
                    where
                        E: de::Error,
                    {
                        match v {
                            "posting:default:visibility" => Ok(Field::PostingDefaultVisibility),
                            "posting:default:sensitive" => Ok(Field::PostingDefaultSensitive),
                            "posting:default:language" => Ok(Field::PostingDefaultLanguage),
                            "reading:expand:media" => Ok(Field::ReadingExpandMedia),
                            "reading:expand:spoilers" => Ok(Field::ReadingExpandSpoilers),
                            _ => Err(de::Error::unknown_field(v, FIELDS)),
                        }
                    }
                }

                deserializer.deserialize_identifier(FieldVisitor)
            }
        }

        struct PreferencesVisitor;

        impl<'de> de::Visitor<'de> for PreferencesVisitor {
            type Value = Preferences;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter.write_str("struct Preferences")
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: de::SeqAccess<'de>,
            {
                let posting_default_visibility = seq
                    .next_element()?
                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
                let posting_default_sensitive = seq
                    .next_element()?
                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
                let posting_default_language = seq
                    .next_element()?
                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
                let reading_expand_media = seq
                    .next_element()?
                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
                let reading_expand_spoilers = seq
                    .next_element()?
                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
                Ok(Self::Value {
                    posting_default_visibility,
                    posting_default_sensitive,
                    posting_default_language,
                    reading_expand_media,
                    reading_expand_spoilers,
                })
            }

            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
            where
                A: de::MapAccess<'de>,
            {
                let mut posting_default_visibility = None;
                let mut posting_default_sensitive = None;
                let mut posting_default_language = None;
                let mut reading_expand_media = None;
                let mut reading_expand_spoilers = None;
                while let Some(key) = map.next_key()? {
                    match key {
                        Field::PostingDefaultVisibility => {
                            if posting_default_visibility.is_some() {
                                return Err(de::Error::duplicate_field(
                                    "posting_default_visibility",
                                ));
                            }
                            posting_default_visibility = Some(map.next_value()?);
                        }
                        Field::PostingDefaultSensitive => {
                            if posting_default_sensitive.is_some() {
                                return Err(de::Error::duplicate_field(
                                    "posting_default_sensitive",
                                ));
                            }
                            posting_default_sensitive = Some(map.next_value()?);
                        }
                        Field::PostingDefaultLanguage => {
                            if posting_default_language.is_some() {
                                return Err(de::Error::duplicate_field("posting_default_language"));
                            }
                            posting_default_language = Some(map.next_value()?);
                        }
                        Field::ReadingExpandMedia => {
                            if reading_expand_media.is_some() {
                                return Err(de::Error::duplicate_field("reading_expand_media"));
                            }
                            reading_expand_media = Some(map.next_value()?);
                        }
                        Field::ReadingExpandSpoilers => {
                            if reading_expand_spoilers.is_some() {
                                return Err(de::Error::duplicate_field("reading_expand_spoilers"));
                            }
                            reading_expand_spoilers = Some(map.next_value()?);
                        }
                    }
                }
                let posting_default_visibility = posting_default_visibility
                    .ok_or_else(|| de::Error::missing_field("posting_default_visibility"))?;
                let posting_default_sensitive = posting_default_sensitive
                    .ok_or_else(|| de::Error::missing_field("posting_default_sensitive"))?;
                let posting_default_language = posting_default_language
                    .ok_or_else(|| de::Error::missing_field("posting_default_language"))?;
                let reading_expand_media = reading_expand_media
                    .ok_or_else(|| de::Error::missing_field("reading_expand_media"))?;
                let reading_expand_spoilers = reading_expand_spoilers
                    .ok_or_else(|| de::Error::missing_field("reading_exapnd_spoilers"))?;
                Ok(Self::Value {
                    posting_default_visibility,
                    posting_default_sensitive,
                    posting_default_language,
                    reading_expand_media,
                    reading_expand_spoilers,
                })
            }
        }

        deserializer.deserialize_struct("Preferences", FIELDS, PreferencesVisitor)
    }
}

impl From<Preferences> for MegalodonEntities::Preferences {
    fn from(val: Preferences) -> Self {
        MegalodonEntities::Preferences {
            posting_default_visibility: val.posting_default_visibility.into(),
            posting_default_sensitive: val.posting_default_sensitive.into(),
            posting_default_language: val.posting_default_language.into(),
            reading_expand_media: val.reading_expand_media.into(),
            reading_expand_spoilers: val.reading_expand_spoilers.into(),
        }
    }
}

impl From<ExpandMedia> for MegalodonEntities::preferences::ExpandMedia {
    fn from(val: ExpandMedia) -> Self {
        match val {
            ExpandMedia::Default => MegalodonEntities::preferences::ExpandMedia::Default,
            ExpandMedia::ShowAll => MegalodonEntities::preferences::ExpandMedia::ShowAll,
            ExpandMedia::HideAll => MegalodonEntities::preferences::ExpandMedia::HideAll,
        }
    }
}

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

    #[test]
    fn test_preferences_serialize() {
        let preferences = Preferences {
            posting_default_visibility: StatusVisibility::Public,
            posting_default_sensitive: false,
            posting_default_language: Some("Japanese".to_string()),
            reading_expand_media: ExpandMedia::Default,
            reading_expand_spoilers: false,
        };

        let r = serde_json::to_string(&preferences);
        assert!(r.is_ok());
        assert_eq!(
        r.unwrap(),
            r#"{"posting:default:visibility":"public","posting:default:sensitive":false,"posting:default:language":"Japanese","reading:expand:media":"default","reading:expand:spoilers":false}"#.to_string(),
        );
    }

    #[test]
    fn test_preferences_deserialize() {
        let text = r#"{"posting:default:visibility":"public","posting:default:sensitive":false,"posting:default:language":"Japanese","reading:expand:media":"default","reading:expand:spoilers":false}"#;

        let r = serde_json::from_str::<Preferences>(text);
        assert!(r.is_ok());
        assert_eq!(
            r.unwrap(),
            Preferences {
                posting_default_visibility: StatusVisibility::Public,
                posting_default_sensitive: false,
                posting_default_language: Some("Japanese".to_string()),
                reading_expand_media: ExpandMedia::Default,
                reading_expand_spoilers: false,
            }
        );
    }
}
