mod blank_node;
mod iri;
mod literal;

use crate::prelude::*;

#[cfg(feature = "source_n_triples")]
use crate::sources::parsers::n_triples;

pub use self::blank_node::*;
pub use self::iri::*;
pub use self::literal::*;

use thiserror::Error;

use std::fmt;

#[derive(Error, Debug, PartialEq)]
pub enum TermError {
    #[error("Unexpected literal datatype")]
    UnexpectedType,
    #[error("Unexpected literal value: {0}")]
    UnexpectedValue(String),
    #[error("Literal has language tag but is not a langString")]
    LiteralInvalidNoLangString,
    #[error("Unknown Skolem IRI for the BlankNode type in use")]
    UnknownSkolemIri,
    #[error("Tried to retrieve inner value for wrong Term variant")]
    WrongVariant,
}

/// https://www.w3.org/TR/rdf11-concepts/#section-triples
#[derive(PartialEq, Eq, Hash, Clone, Debug, PartialOrd, Ord)]
pub enum Term {
    NamedNode(Iri),
    Literal(Literal),
    BlankNode(BlankNode),
    TripleTerm(Arc<Triple>),
}

impl Term {
    pub fn into_iri(self) -> Result<Iri, TermError> {
        match self {
            Self::NamedNode(i) => Ok(i),
            _ => Err(TermError::WrongVariant),
        }
    }

    pub fn into_literal(self) -> Result<Literal, TermError> {
        match self {
            Self::Literal(l) => Ok(l),
            _ => Err(TermError::WrongVariant),
        }
    }

    pub fn into_blank_node(self) -> Result<BlankNode, TermError> {
        match self {
            Self::BlankNode(b) => Ok(b),
            _ => Err(TermError::WrongVariant),
        }
    }

    pub fn into_triple_term(self) -> Result<Arc<Triple>, TermError> {
        match self {
            Self::TripleTerm(t) => Ok(t),
            _ => Err(TermError::WrongVariant),
        }
    }

    pub fn to_iri(&self) -> Result<&Iri, TermError> {
        match self {
            Self::NamedNode(i) => Ok(i),
            _ => Err(TermError::WrongVariant),
        }
    }

    pub fn to_literal(&self) -> Result<&Literal, TermError> {
        match self {
            Self::Literal(l) => Ok(l),
            _ => Err(TermError::WrongVariant),
        }
    }

    pub fn to_blank_node(&self) -> Result<&BlankNode, TermError> {
        match self {
            Self::BlankNode(b) => Ok(b),
            _ => Err(TermError::WrongVariant),
        }
    }

    pub fn to_triple_term(&self) -> Result<&Arc<Triple>, TermError> {
        match self {
            Self::TripleTerm(t) => Ok(t),
            _ => Err(TermError::WrongVariant),
        }
    }

    pub fn is_named_node(&self) -> bool {
        matches!(self, Self::NamedNode(_))
    }

    pub fn is_blank_node(&self) -> bool {
        matches!(self, Self::BlankNode(_))
    }
    pub fn is_literal(&self) -> bool {
        matches!(self, Self::Literal(_))
    }
    pub fn is_triple_term(&self) -> bool {
        matches!(self, Self::TripleTerm(_))
    }

    /// Checks whether term is *ground* as defined by
    /// [RDF Concepts and Abstract Syntax](https://www.w3.org/TR/rdf12-concepts/#section-terms)
    ///
    /// A term is *ground* if it is not a blank node or a triple term containing a
    /// blank node.
    ///
    /// # Example
    ///
    /// ```
    /// use taganak_core::terms::Term;
    /// use std::sync::Arc;
    ///
    /// let nn: Arc<Term> = Arc::new("<https://example.com/foo>".try_into().unwrap());
    /// let bn: Arc<Term> = Arc::new("_:b1".try_into().unwrap());
    /// let li: Arc<Term> = Arc::new(r#""Something""#.try_into().unwrap());
    ///
    /// assert_eq!(nn.ground(), true);
    /// assert_eq!(li.ground(), true);
    /// assert_eq!(bn.ground(), false);
    /// ```
    pub fn ground(&self) -> bool {
        match self {
            Self::NamedNode(_) | Self::Literal(_) => true,
            // FIXME we probably need to bust potential endless lopos here
            Self::TripleTerm(triple) => triple.ground(),
            Self::BlankNode(_) => false,
        }
    }
}

#[cfg(feature = "source_n_triples")]
impl TryFrom<String> for Term {
    type Error = TermError;

    fn try_from(mut value: String) -> Result<Self, Self::Error> {
        let mut input = n_triples::TokenStream {
            input: value.as_mut_str(),
            state: &mut n_triples::State::new(),
        };
        Ok(n_triples::object_parser(&mut input)
            .map_err(|e| TermError::UnexpectedValue(value))?
            .as_ref()
            .clone())
    }
}

#[cfg(feature = "source_n_triples")]
impl TryFrom<&str> for Term {
    type Error = TermError;

    /// # Examples
    ///
    /// ```
    /// use language_tags::LanguageTag;
    /// use taganak_core::terms::{Iri, Literal, Term};
    ///
    /// let t1: Term = "<https://example.com/foo#bar>".try_into().unwrap();
    /// let t2: Term = r#""Schildkröte"@de"#.try_into().unwrap();
    ///
    /// assert_eq!(t1, Term::NamedNode(Iri::new("https://example.com/foo#bar".to_string()).unwrap()));
    /// assert_eq!(t2, Term::Literal(Literal::new("Schildkröte".to_string(), None, Some(LanguageTag::parse("de").unwrap()), None).unwrap()));
    /// ```
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        value.to_string().try_into()
    }
}

impl fmt::Display for Term {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::NamedNode(iri) => write!(f, "<{}>", iri.as_str()),
            Self::Literal(literal) => literal.fmt(f),
            Self::BlankNode(blank_node) => blank_node.fmt(f),
            Self::TripleTerm(term) => write!(f, "<<( {} )>>", *term),
        }
    }
}

impl AsRef<Term> for Term {
    fn as_ref(&self) -> &Term {
        self
    }
}
