use super::*;

use iref::IriRefBuf;
pub use language_tags::LanguageTag;
use unicode_normalization::UnicodeNormalization;

use std::fmt::{self, Display};

#[cfg(feature = "base64")]
use base64::{self, Engine};
#[cfg(feature = "chrono")]
use chrono;
#[cfg(feature = "decimal")]
use rust_decimal;

#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
pub enum BaseDirection {
    LeftToRight,
    RightToLeft,
}

impl TryFrom<&str> for BaseDirection {
    type Error = std::io::Error;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value {
            "ltr" => Ok(Self::LeftToRight),
            "rtl" => Ok(Self::RightToLeft),
            _ => Err(std::io::Error::other("invalid base direction")),
        }
    }
}

impl Display for BaseDirection {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Self::LeftToRight => "ltr",
                Self::RightToLeft => "rtl",
            }
        )
    }
}

/// https://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct Literal {
    lexical: String,
    datatype: Iri,
    language: Option<LanguageTag>,
    base_direction: Option<BaseDirection>,
}

impl Ord for Literal {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        let language_ordering = match (self.language.as_ref(), other.language.as_ref()) {
            (None, None) => core::cmp::Ordering::Equal,
            (None, Some(_)) => core::cmp::Ordering::Less,
            (Some(_), None) => core::cmp::Ordering::Greater,
            (Some(langtag1), Some(langtag2)) => {
                let str1: &str = langtag1.as_str();
                let str2: &str = langtag2.as_str();
                str1.cmp(str2)
            }
        };
        self.lexical
            .cmp(&other.lexical)
            .then(self.datatype.cmp(&other.datatype))
            .then(language_ordering)
            .then(self.base_direction.cmp(&other.base_direction))
    }
}

impl PartialOrd for Literal {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Literal {
    pub fn new(
        lexical: String,
        datatype: Option<Iri>,
        language: Option<LanguageTag>,
        base_direction: Option<BaseDirection>,
    ) -> Result<Self, TermError> {
        let datatype = match datatype {
            Some(d) => d,
            None => Iri::new(match language {
                Some(_) => match base_direction {
                    Some(_) => {
                        "http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString".to_string()
                    }
                    None => "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString".to_string(),
                },
                None => "http://www.w3.org/2001/XMLSchema#string".to_string(),
            })
            .unwrap(),
        };

        if language.is_some()
            && base_direction.is_none()
            && datatype != "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString"
        {
            return Err(TermError::LiteralInvalidNoLangString);
        } else if language.is_some()
            && base_direction.is_some()
            && datatype != "http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString"
        {
            return Err(TermError::LiteralInvalidNoLangString);
        } else if language.is_none() && base_direction.is_some() {
            return Err(TermError::LiteralInvalidNoLangString);
        }

        Ok(Self {
            lexical: lexical.nfc().collect::<String>(),
            datatype,
            language,
            base_direction,
        })
    }

    pub fn lexical(&self) -> &str {
        &self.lexical
    }

    pub fn datatype(&self) -> &str {
        &self.datatype
    }

    pub fn language(&self) -> Option<&LanguageTag> {
        self.language.as_ref()
    }

    pub fn base_direction(&self) -> Option<&BaseDirection> {
        self.base_direction.as_ref()
    }

    pub fn valid(&self) -> bool {
        // FIXME implement
        true
    }
}

impl TryFrom<Literal> for String {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#string"
            | "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString"
            | "http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString"
            | "http://www.w3.org/1999/02/22-rdf-syntax-ns#normalizedString" => Ok(value.lexical),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

#[cfg(feature = "base64")]
impl From<Vec<u8>> for Literal {
    fn from(value: Vec<u8>) -> Self {
        let engine = base64::engine::general_purpose::STANDARD;
        Self::new(
            engine.encode(value),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#base64Binary".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

#[cfg(feature = "base64")]
impl TryFrom<Literal> for Vec<u8> {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        let engine = base64::engine::general_purpose::STANDARD;
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema@base64Binary" => Ok(engine
                .decode(value.lexical)
                .map_err(|e| TermError::UnexpectedValue(e.to_string()))?),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<String> for Literal {
    fn from(value: String) -> Self {
        Self::new(value, None, None, None).unwrap()
    }
}

impl TryFrom<Literal> for IriRefBuf {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#anyURI" => {
                IriRefBuf::new(value.lexical).map_err(|e| TermError::UnexpectedValue(e.to_string()))
            }
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<IriRefBuf> for Literal {
    fn from(value: IriRefBuf) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#anyURI".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl From<&str> for Literal {
    fn from(value: &str) -> Self {
        Self::new(value.to_string(), None, None, None).unwrap()
    }
}

impl TryFrom<Literal> for bool {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#boolean" => match value.lexical.as_str() {
                "true" | "1" => Ok(true),
                "false" | "0" => Ok(false),
                s => Err(TermError::UnexpectedValue(s.to_string())),
            },
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<bool> for Literal {
    fn from(value: bool) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#boolean".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl TryFrom<Literal> for i8 {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#integer"
            | "http://www.w3.org/2001/XMLSchema#byte"
            | "http://www.w3.org/2001/XMLSchema#negativeInteger"
            | "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<i8> for Literal {
    fn from(value: i8) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#byte".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl TryFrom<Literal> for i16 {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#integer"
            | "http://www.w3.org/2001/XMLSchema#short"
            | "http://www.w3.org/2001/XMLSchema#negativeInteger"
            | "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<i16> for Literal {
    fn from(value: i16) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#short".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl TryFrom<Literal> for i32 {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#integer"
            | "http://www.w3.org/2001/XMLSchema#int"
            | "http://www.w3.org/2001/XMLSchema#negativeInteger"
            | "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<i32> for Literal {
    fn from(value: i32) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#int".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl TryFrom<Literal> for i64 {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#integer"
            | "http://www.w3.org/2001/XMLSchema#long"
            | "http://www.w3.org/2001/XMLSchema#negativeInteger"
            | "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<i64> for Literal {
    fn from(value: i64) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#long".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl TryFrom<Literal> for i128 {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#integer" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<i128> for Literal {
    fn from(value: i128) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#integer".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl TryFrom<Literal> for u8 {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#integer"
            | "http://www.w3.org/2001/XMLSchema#unsignedByte"
            | "http://www.w3.org/2001/XMLSchema#positiveInteger"
            | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<u8> for Literal {
    fn from(value: u8) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#unsignedByte".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl TryFrom<Literal> for u16 {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#integer"
            | "http://www.w3.org/2001/XMLSchema#unsignedShort"
            | "http://www.w3.org/2001/XMLSchema#positiveInteger"
            | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<u16> for Literal {
    fn from(value: u16) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#unsignedShort".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl TryFrom<Literal> for u32 {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#integer"
            | "http://www.w3.org/2001/XMLSchema#unsignedInt"
            | "http://www.w3.org/2001/XMLSchema#positiveInteger"
            | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<u32> for Literal {
    fn from(value: u32) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#unsignedInt".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl TryFrom<Literal> for u64 {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#integer"
            | "http://www.w3.org/2001/XMLSchema#unsignedLong"
            | "http://www.w3.org/2001/XMLSchema#positiveInteger"
            | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<u64> for Literal {
    fn from(value: u64) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#unsignedLong".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl TryFrom<Literal> for u128 {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#integer"
            | "http://www.w3.org/2001/XMLSchema#positiveInteger"
            | "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<u128> for Literal {
    fn from(value: u128) -> Self {
        Self::new(
            value.to_string(),
            Some(
                Iri::new("http://www.w3.org/2001/XMLSchema#nonNegativeInteger".to_string())
                    .unwrap(),
            ),
            None,
            None,
        )
        .unwrap()
    }
}

impl TryFrom<Literal> for f32 {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#float" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<f32> for Literal {
    fn from(value: f32) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#float".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl TryFrom<Literal> for f64 {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#double" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

impl From<f64> for Literal {
    fn from(value: f64) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#double".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

#[cfg(feature = "decimal")]
impl TryFrom<Literal> for rust_decimal::Decimal {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#decimal" => value
                .lexical
                .parse()
                .map_err(|_| TermError::UnexpectedValue(value.lexical.to_string())),
            _ => Err(TermError::UnexpectedType),
        }
    }
}

#[cfg(feature = "decimal")]
impl From<rust_decimal::Decimal> for Literal {
    fn from(value: rust_decimal::Decimal) -> Self {
        Self::new(
            value.to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#decimal".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

#[cfg(feature = "chrono")]
impl TryFrom<Literal> for chrono::NaiveDate {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#date" => {
                for f in ["%F", "%Fz%:z"] {
                    match chrono::NaiveDate::parse_from_str(&value.lexical, f) {
                        Ok(v) => return Ok(v),
                        _ => continue,
                    }
                }
                return Err(TermError::UnexpectedValue(value.lexical.to_string()));
            }
            _ => return Err(TermError::UnexpectedType),
        }
    }
}

#[cfg(feature = "chrono")]
impl From<chrono::NaiveDate> for Literal {
    fn from(value: chrono::NaiveDate) -> Self {
        Self::new(
            value.format("%Y-%m-%d").to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#date".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

#[cfg(feature = "chrono")]
impl TryFrom<Literal> for chrono::NaiveTime {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#time" => {
                for f in ["%T", "%T%.f", "%TZ", "%T%.fZ", "%T%:z", "%T%.f%:z"] {
                    match chrono::NaiveTime::parse_from_str(&value.lexical, f) {
                        Ok(v) => return Ok(v),
                        _ => continue,
                    }
                }
                return Err(TermError::UnexpectedValue(value.lexical.to_string()));
            }
            _ => return Err(TermError::UnexpectedType),
        }
    }
}

#[cfg(feature = "chrono")]
impl From<chrono::NaiveTime> for Literal {
    fn from(value: chrono::NaiveTime) -> Self {
        Self::new(
            value.format("%T%.f").to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#time".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

#[cfg(feature = "chrono")]
impl TryFrom<Literal> for chrono::NaiveDateTime {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#dateTime" => {
                for f in ["%FT%T", "%FT%T%.f", "%FT%TZ", "%FT%T%.fZ", "%+"] {
                    match chrono::NaiveDateTime::parse_from_str(&value.lexical, f) {
                        Ok(v) => return Ok(v),
                        _ => continue,
                    }
                }
                return Err(TermError::UnexpectedValue(value.lexical.to_string()));
            }
            _ => return Err(TermError::UnexpectedType),
        }
    }
}

#[cfg(feature = "chrono")]
impl From<chrono::NaiveDateTime> for Literal {
    fn from(value: chrono::NaiveDateTime) -> Self {
        Self::new(
            value.format("%FT%T%.f").to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#dateTime".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

#[cfg(feature = "chrono")]
impl TryFrom<Literal> for chrono::DateTime<chrono::FixedOffset> {
    type Error = TermError;

    fn try_from(value: Literal) -> Result<Self, Self::Error> {
        match value.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#dateTime"
            | "http://www.w3.org/2001/XMLSchema#dateTimeStamp" => {
                for f in ["%+"] {
                    match chrono::DateTime::<chrono::FixedOffset>::parse_from_str(&value.lexical, f)
                    {
                        Ok(v) => return Ok(v),
                        _ => continue,
                    }
                }
                return Err(TermError::UnexpectedValue(value.lexical.to_string()));
            }
            _ => return Err(TermError::UnexpectedType),
        }
    }
}

#[cfg(feature = "chrono")]
impl<Tz: chrono::TimeZone> From<chrono::DateTime<Tz>> for Literal
where
    Tz::Offset: Display,
{
    fn from(value: chrono::DateTime<Tz>) -> Self {
        Self::new(
            value.format("%+").to_string(),
            Some(Iri::new("http://www.w3.org/2001/XMLSchema#dateTimeStamp".to_string()).unwrap()),
            None,
            None,
        )
        .unwrap()
    }
}

impl fmt::Display for Literal {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "\"")?;
        for c in self.lexical.chars() {
            match c {
                '"' => write!(f, "\\\""),
                '\r' => write!(f, "\\r"),
                '\n' => write!(f, "\\n"),
                '\\' => write!(f, "\\\\"),
                c => write!(f, "{}", c),
            }?
        }
        write!(f, "\"")?;

        match self.datatype.as_str() {
            "http://www.w3.org/2001/XMLSchema#string"
            | "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString"
            | "http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString" => Ok(()),
            s => write!(f, "^^<{}>", s),
        }?;

        match &self.language {
            Some(lang) => {
                write!(f, "@{}", lang.as_str());
                match &self.base_direction {
                    Some(dir) => write!(f, "--{}", dir.to_string()),
                    None => write!(f, ""),
                }
            }
            None => Ok(()),
        }
    }
}
