use std::borrow::Cow;
use std::io::{BufRead, Write};

use quick_xml::events::attributes::Attributes;
use quick_xml::events::{BytesEnd, BytesStart, Event};
use quick_xml::Reader;
use quick_xml::Writer;

use crate::category::Category;
use crate::error::{Error, XmlError};
use crate::fromxml::FromXml;
use crate::generator::Generator;
use crate::link::Link;
use crate::person::Person;
use crate::text::Text;
use crate::toxml::{ToXml, WriterExt};
use crate::util::{atom_datetime, atom_text, decode, default_fixed_datetime, skip, FixedDateTime};

/// Represents the source of an Atom entry
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "builders", derive(Builder))]
#[cfg_attr(
    feature = "builders",
    builder(
        setter(into),
        default,
        build_fn(name = "build_impl", private, error = "never::Never")
    )
)]
pub struct Source {
    /// A human-readable title for the feed.
    pub title: Text,
    /// A universally unique and permanent URI.
    pub id: String,
    /// The last time the feed was modified in a significant way.
    pub updated: FixedDateTime,
    /// The authors of the feed.
    #[cfg_attr(feature = "builders", builder(setter(each = "author")))]
    pub authors: Vec<Person>,
    /// The categories that the feed belongs to.
    #[cfg_attr(feature = "builders", builder(setter(each = "category")))]
    pub categories: Vec<Category>,
    /// The contributors to the feed.
    #[cfg_attr(feature = "builders", builder(setter(each = "contributor")))]
    pub contributors: Vec<Person>,
    /// The software used to generate the feed.
    pub generator: Option<Generator>,
    /// A small image which provides visual identification for the feed.
    pub icon: Option<String>,
    /// The Web pages related to the feed.
    #[cfg_attr(feature = "builders", builder(setter(each = "link")))]
    pub links: Vec<Link>,
    /// A larger image which provides visual identification for the feed.
    pub logo: Option<String>,
    /// Information about rights held in and over the feed.
    pub rights: Option<Text>,
    /// A human-readable description or subtitle for the feed.
    pub subtitle: Option<Text>,
}

impl Source {
    /// Return the title of the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::Source;
    ///
    /// let mut source = Source::default();
    /// source.set_title("Feed Title");
    /// assert_eq!(source.title(), "Feed Title");
    /// ```
    pub fn title(&self) -> &Text {
        &self.title
    }

    /// Set the title of the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::Source;
    ///
    /// let mut source = Source::default();
    /// source.set_title("Feed Title");
    /// ```
    pub fn set_title<V>(&mut self, title: V)
    where
        V: Into<Text>,
    {
        self.title = title.into();
    }

    /// Return the unique URI of the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::Source;
    ///
    /// let mut source = Source::default();
    /// source.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6");
    /// assert_eq!(source.id(), "urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6");
    /// ```
    pub fn id(&self) -> &str {
        self.id.as_str()
    }

    /// Set the unique URI of the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::Source;
    ///
    /// let mut source = Source::default();
    /// source.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6");
    /// ```
    pub fn set_id<V>(&mut self, id: V)
    where
        V: Into<String>,
    {
        self.id = id.into();
    }

    /// Return the last time that the source feed was modified.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::Source;
    /// use atom_syndication::FixedDateTime;
    /// use std::str::FromStr;
    ///
    /// let mut source = Source::default();
    /// source.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap());
    /// assert_eq!(source.updated().to_rfc3339(), "2017-06-03T15:15:44-05:00");
    /// ```
    pub fn updated(&self) -> &FixedDateTime {
        &self.updated
    }

    /// Set the last time that the source feed was modified.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::Source;
    /// use atom_syndication::FixedDateTime;
    /// use std::str::FromStr;
    ///
    /// let mut source = Source::default();
    /// source.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap());
    /// ```
    pub fn set_updated<V>(&mut self, updated: V)
    where
        V: Into<FixedDateTime>,
    {
        self.updated = updated.into();
    }

    /// Return the authors of the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Person};
    ///
    /// let mut source = Source::default();
    /// source.set_authors(vec![Person::default()]);
    /// assert_eq!(source.authors().len(), 1);
    /// ```
    pub fn authors(&self) -> &[Person] {
        self.authors.as_slice()
    }

    /// Set the authors of the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Person};
    ///
    /// let mut source = Source::default();
    /// source.set_authors(vec![Person::default()]);
    /// ```
    pub fn set_authors<V>(&mut self, authors: V)
    where
        V: Into<Vec<Person>>,
    {
        self.authors = authors.into();
    }

    /// Return the categories the source feed belongs to.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Category};
    ///
    /// let mut source = Source::default();
    /// source.set_categories(vec![Category::default()]);
    /// assert_eq!(source.categories().len(), 1);
    /// ```
    pub fn categories(&self) -> &[Category] {
        self.categories.as_slice()
    }

    /// Set the categories the source feed belongs to.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Category};
    ///
    /// let mut source = Source::default();
    /// source.set_categories(vec![Category::default()]);
    /// ```
    pub fn set_categories<V>(&mut self, categories: V)
    where
        V: Into<Vec<Category>>,
    {
        self.categories = categories.into();
    }

    /// Return the contributors to the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Person};
    ///
    /// let mut source = Source::default();
    /// source.set_contributors(vec![Person::default()]);
    /// assert_eq!(source.contributors().len(), 1);
    /// ```
    pub fn contributors(&self) -> &[Person] {
        self.contributors.as_slice()
    }

    /// Set the contributors to the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Person};
    ///
    /// let mut source = Source::default();
    /// source.set_contributors(vec![Person::default()]);
    /// ```
    pub fn set_contributors<V>(&mut self, contributors: V)
    where
        V: Into<Vec<Person>>,
    {
        self.contributors = contributors.into();
    }

    /// Return the name of the software used to generate the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Generator};
    ///
    /// let mut source = Source::default();
    /// source.set_generator(Generator::default());
    /// assert!(source.generator().is_some());
    /// ```
    pub fn generator(&self) -> Option<&Generator> {
        self.generator.as_ref()
    }

    /// Set the name of the software used to generate the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Generator};
    ///
    /// let mut source = Source::default();
    /// source.set_generator(Generator::default());
    /// ```
    pub fn set_generator<V>(&mut self, generator: V)
    where
        V: Into<Option<Generator>>,
    {
        self.generator = generator.into()
    }

    /// Return the icon for the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::Source;
    ///
    /// let mut source = Source::default();
    /// source.set_icon("http://example.com/icon.png".to_string());
    /// assert_eq!(source.icon(), Some("http://example.com/icon.png"));
    /// ```
    pub fn icon(&self) -> Option<&str> {
        self.icon.as_deref()
    }

    /// Set the icon for the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::Source;
    ///
    /// let mut source = Source::default();
    /// source.set_icon("http://example.com/icon.png".to_string());
    /// ```
    pub fn set_icon<V>(&mut self, icon: V)
    where
        V: Into<Option<String>>,
    {
        self.icon = icon.into()
    }

    /// Return the Web pages related to the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Link};
    ///
    /// let mut source = Source::default();
    /// source.set_links(vec![Link::default()]);
    /// assert_eq!(source.links().len(), 1);
    /// ```
    pub fn links(&self) -> &[Link] {
        self.links.as_slice()
    }

    /// Set the Web pages related to the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Link};
    ///
    /// let mut source = Source::default();
    /// source.set_links(vec![Link::default()]);
    /// ```
    pub fn set_links<V>(&mut self, links: V)
    where
        V: Into<Vec<Link>>,
    {
        self.links = links.into();
    }

    /// Return the logo for the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::Source;
    ///
    /// let mut source = Source::default();
    /// source.set_logo("http://example.com/logo.png".to_string());
    /// assert_eq!(source.logo(), Some("http://example.com/logo.png"));
    /// ```
    pub fn logo(&self) -> Option<&str> {
        self.logo.as_deref()
    }

    /// Set the logo for the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::Source;
    ///
    /// let mut source = Source::default();
    /// source.set_logo("http://example.com/logo.png".to_string());
    /// ```
    pub fn set_logo<V>(&mut self, logo: V)
    where
        V: Into<Option<String>>,
    {
        self.logo = logo.into()
    }

    /// Return the information about the rights held in and over the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Text};
    ///
    /// let mut source = Source::default();
    /// source.set_rights(Text::from("© 2017 John Doe"));
    /// assert_eq!(source.rights().map(Text::as_str), Some("© 2017 John Doe"));
    /// ```
    pub fn rights(&self) -> Option<&Text> {
        self.rights.as_ref()
    }

    /// Set the information about the rights held in and over the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Text};
    ///
    /// let mut source = Source::default();
    /// source.set_rights(Text::from("© 2017 John Doe"));
    /// ```
    pub fn set_rights<V>(&mut self, rights: V)
    where
        V: Into<Option<Text>>,
    {
        self.rights = rights.into()
    }

    /// Return the description or subtitle of the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Text};
    ///
    /// let mut source = Source::default();
    /// source.set_subtitle(Text::from("Feed subtitle"));
    /// assert_eq!(source.subtitle().map(Text::as_str), Some("Feed subtitle"));
    /// ```
    pub fn subtitle(&self) -> Option<&Text> {
        self.subtitle.as_ref()
    }

    /// Set the description or subtitle of the source feed.
    ///
    /// # Examples
    ///
    /// ```
    /// use atom_syndication::{Source, Text};
    ///
    /// let mut source = Source::default();
    /// source.set_subtitle(Text::from("Feed subtitle"));
    /// ```
    pub fn set_subtitle<V>(&mut self, subtitle: V)
    where
        V: Into<Option<Text>>,
    {
        self.subtitle = subtitle.into()
    }
}

impl FromXml for Source {
    fn from_xml<B: BufRead>(reader: &mut Reader<B>, _: Attributes<'_>) -> Result<Self, Error> {
        let mut source = Source::default();
        let mut buf = Vec::new();

        loop {
            match reader.read_event_into(&mut buf).map_err(XmlError::new)? {
                Event::Start(element) => match decode(element.name().as_ref(), reader)? {
                    Cow::Borrowed("id") => source.id = atom_text(reader)?.unwrap_or_default(),
                    Cow::Borrowed("title") => {
                        source.title = Text::from_xml(reader, element.attributes())?
                    }
                    Cow::Borrowed("updated") => {
                        source.updated =
                            atom_datetime(reader)?.unwrap_or_else(default_fixed_datetime)
                    }
                    Cow::Borrowed("author") => source
                        .authors
                        .push(Person::from_xml(reader, element.attributes())?),
                    Cow::Borrowed("category") => {
                        source
                            .categories
                            .push(Category::from_xml(reader, &element)?);
                        skip(element.name(), reader)?;
                    }
                    Cow::Borrowed("contributor") => source
                        .contributors
                        .push(Person::from_xml(reader, element.attributes())?),
                    Cow::Borrowed("generator") => {
                        source.generator = Some(Generator::from_xml(reader, element.attributes())?)
                    }
                    Cow::Borrowed("icon") => source.icon = atom_text(reader)?,
                    Cow::Borrowed("link") => {
                        source.links.push(Link::from_xml(reader, &element)?);
                        skip(element.name(), reader)?;
                    }
                    Cow::Borrowed("logo") => source.logo = atom_text(reader)?,
                    Cow::Borrowed("rights") => {
                        source.rights = Some(Text::from_xml(reader, element.attributes())?)
                    }
                    Cow::Borrowed("subtitle") => {
                        source.subtitle = Some(Text::from_xml(reader, element.attributes())?)
                    }
                    _ => skip(element.name(), reader)?,
                },
                Event::End(_) => break,
                Event::Eof => return Err(Error::Eof),
                _ => {}
            }

            buf.clear();
        }

        Ok(source)
    }
}

impl ToXml for Source {
    fn to_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), XmlError> {
        let name = "source";
        writer
            .write_event(Event::Start(BytesStart::new(name)))
            .map_err(XmlError::new)?;
        writer.write_object_named(&self.title, "title")?;
        writer.write_text_element("id", &self.id)?;
        writer.write_text_element("updated", &self.updated.to_rfc3339())?;
        writer.write_objects_named(&self.authors, "author")?;
        writer.write_objects(&self.categories)?;
        writer.write_objects_named(&self.contributors, "contributor")?;

        if let Some(ref generator) = self.generator {
            writer.write_object(generator)?;
        }

        if let Some(ref icon) = self.icon {
            writer.write_text_element("icon", icon)?;
        }

        writer.write_objects(&self.links)?;

        if let Some(ref logo) = self.logo {
            writer.write_text_element("logo", logo)?;
        }

        if let Some(ref rights) = self.rights {
            writer.write_object_named(rights, "rights")?;
        }

        if let Some(ref subtitle) = self.subtitle {
            writer.write_object_named(subtitle, "subtitle")?;
        }

        writer
            .write_event(Event::End(BytesEnd::new(name)))
            .map_err(XmlError::new)?;

        Ok(())
    }
}

impl Default for Source {
    fn default() -> Self {
        Source {
            title: Text::default(),
            id: String::new(),
            updated: default_fixed_datetime(),
            authors: Vec::new(),
            categories: Vec::new(),
            contributors: Vec::new(),
            generator: None,
            icon: None,
            links: Vec::new(),
            logo: None,
            rights: None,
            subtitle: None,
        }
    }
}

#[cfg(feature = "builders")]
impl SourceBuilder {
    /// Builds a new `Source`.
    pub fn build(&self) -> Source {
        self.build_impl().unwrap()
    }
}
