use crate::algebra::*;
use crate::parser::{parse_query, ParseError};
use crate::term::*;
use oxiri::Iri;
use std::fmt;
use std::str::FromStr;

/// A parsed [SPARQL query](https://www.w3.org/TR/sparql11-query/).
///
/// ```
/// use spargebra::Query;
///
/// let query_str = "SELECT ?s ?p ?o WHERE { ?s ?p ?o . }";
/// let query = Query::parse(query_str, None)?;
/// assert_eq!(query.to_string(), query_str);
/// assert_eq!(query.to_sse(), "(project (?s ?p ?o) (bgp (triple ?s ?p ?o)))");
/// # Ok::<_, spargebra::ParseError>(())
/// ```
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub enum Query {
    /// [SELECT](https://www.w3.org/TR/sparql11-query/#select).
    Select {
        /// The [query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset).
        dataset: Option<QueryDataset>,
        /// The query selection graph pattern.
        pattern: GraphPattern,
        /// The query base IRI.
        base_iri: Option<Iri<String>>,
    },
    /// [CONSTRUCT](https://www.w3.org/TR/sparql11-query/#construct).
    Construct {
        /// The query construction template.
        template: Vec<TriplePattern>,
        /// The [query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset).
        dataset: Option<QueryDataset>,
        /// The query selection graph pattern.
        pattern: GraphPattern,
        /// The query base IRI.
        base_iri: Option<Iri<String>>,
    },
    /// [DESCRIBE](https://www.w3.org/TR/sparql11-query/#describe).
    Describe {
        /// The [query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset).
        dataset: Option<QueryDataset>,
        /// The query selection graph pattern.
        pattern: GraphPattern,
        /// The query base IRI.
        base_iri: Option<Iri<String>>,
    },
    /// [ASK](https://www.w3.org/TR/sparql11-query/#ask).
    Ask {
        /// The [query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset).
        dataset: Option<QueryDataset>,
        /// The query selection graph pattern.
        pattern: GraphPattern,
        /// The query base IRI.
        base_iri: Option<Iri<String>>,
    },
}

impl Query {
    /// Parses a SPARQL query with an optional base IRI to resolve relative IRIs in the query.
    pub fn parse(query: &str, base_iri: Option<&str>) -> Result<Self, ParseError> {
        parse_query(query, base_iri)
    }

    /// Formats using the [SPARQL S-Expression syntax](https://jena.apache.org/documentation/notes/sse.html).
    pub fn to_sse(&self) -> String {
        let mut buffer = String::new();
        self.fmt_sse(&mut buffer)
            .expect("Unexpected error during SSE formatting");
        buffer
    }

    /// Formats using the [SPARQL S-Expression syntax](https://jena.apache.org/documentation/notes/sse.html).
    fn fmt_sse(&self, f: &mut impl fmt::Write) -> fmt::Result {
        match self {
            Query::Select {
                dataset,
                pattern,
                base_iri,
            } => {
                if let Some(base_iri) = base_iri {
                    write!(f, "(base <{base_iri}> ")?;
                }
                if let Some(dataset) = dataset {
                    write!(f, "(dataset ")?;
                    dataset.fmt_sse(f)?;
                    write!(f, " ")?;
                }
                pattern.fmt_sse(f)?;
                if dataset.is_some() {
                    write!(f, ")")?;
                }
                if base_iri.is_some() {
                    write!(f, ")")?;
                }
                Ok(())
            }
            Query::Construct {
                template,
                dataset,
                pattern,
                base_iri,
            } => {
                if let Some(base_iri) = base_iri {
                    write!(f, "(base <{base_iri}> ")?;
                }
                write!(f, "(construct (")?;
                for (i, t) in template.iter().enumerate() {
                    if i > 0 {
                        write!(f, " ")?;
                    }
                    t.fmt_sse(f)?;
                }
                write!(f, ") ")?;
                if let Some(dataset) = dataset {
                    write!(f, "(dataset ")?;
                    dataset.fmt_sse(f)?;
                    write!(f, " ")?;
                }
                pattern.fmt_sse(f)?;
                if dataset.is_some() {
                    write!(f, ")")?;
                }
                write!(f, ")")?;
                if base_iri.is_some() {
                    write!(f, ")")?;
                }
                Ok(())
            }
            Query::Describe {
                dataset,
                pattern,
                base_iri,
            } => {
                if let Some(base_iri) = base_iri {
                    write!(f, "(base <{base_iri}> ")?;
                }
                write!(f, "(describe ")?;
                if let Some(dataset) = dataset {
                    write!(f, "(dataset ")?;
                    dataset.fmt_sse(f)?;
                    write!(f, " ")?;
                }
                pattern.fmt_sse(f)?;
                if dataset.is_some() {
                    write!(f, ")")?;
                }
                write!(f, ")")?;
                if base_iri.is_some() {
                    write!(f, ")")?;
                }
                Ok(())
            }
            Query::Ask {
                dataset,
                pattern,
                base_iri,
            } => {
                if let Some(base_iri) = base_iri {
                    write!(f, "(base <{base_iri}> ")?;
                }
                write!(f, "(ask ")?;
                if let Some(dataset) = dataset {
                    write!(f, "(dataset ")?;
                    dataset.fmt_sse(f)?;
                    write!(f, " ")?;
                }
                pattern.fmt_sse(f)?;
                if dataset.is_some() {
                    write!(f, ")")?;
                }
                write!(f, ")")?;
                if base_iri.is_some() {
                    write!(f, ")")?;
                }
                Ok(())
            }
        }
    }
}

impl fmt::Display for Query {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Query::Select {
                dataset,
                pattern,
                base_iri,
            } => {
                if let Some(base_iri) = base_iri {
                    writeln!(f, "BASE <{base_iri}>")?;
                }
                write!(
                    f,
                    "{}",
                    SparqlGraphRootPattern {
                        pattern,
                        dataset: dataset.as_ref()
                    }
                )
            }
            Query::Construct {
                template,
                dataset,
                pattern,
                base_iri,
            } => {
                if let Some(base_iri) = base_iri {
                    writeln!(f, "BASE <{base_iri}>")?;
                }
                write!(f, "CONSTRUCT {{ ")?;
                for triple in template.iter() {
                    write!(f, "{triple} . ")?;
                }
                write!(f, "}}")?;
                if let Some(dataset) = dataset {
                    dataset.fmt(f)?;
                }
                write!(
                    f,
                    " WHERE {{ {} }}",
                    SparqlGraphRootPattern {
                        pattern,
                        dataset: None
                    }
                )
            }
            Query::Describe {
                dataset,
                pattern,
                base_iri,
            } => {
                if let Some(base_iri) = base_iri {
                    writeln!(f, "BASE <{}>", base_iri.as_str())?;
                }
                write!(f, "DESCRIBE *")?;
                if let Some(dataset) = dataset {
                    dataset.fmt(f)?;
                }
                write!(
                    f,
                    " WHERE {{ {} }}",
                    SparqlGraphRootPattern {
                        pattern,
                        dataset: None
                    }
                )
            }
            Query::Ask {
                dataset,
                pattern,
                base_iri,
            } => {
                if let Some(base_iri) = base_iri {
                    writeln!(f, "BASE <{base_iri}>")?;
                }
                write!(f, "ASK")?;
                if let Some(dataset) = dataset {
                    dataset.fmt(f)?;
                }
                write!(
                    f,
                    " WHERE {{ {} }}",
                    SparqlGraphRootPattern {
                        pattern,
                        dataset: None
                    }
                )
            }
        }
    }
}

impl FromStr for Query {
    type Err = ParseError;

    fn from_str(query: &str) -> Result<Self, ParseError> {
        Self::parse(query, None)
    }
}

impl<'a> TryFrom<&'a str> for Query {
    type Error = ParseError;

    fn try_from(query: &str) -> Result<Self, ParseError> {
        Self::from_str(query)
    }
}

impl<'a> TryFrom<&'a String> for Query {
    type Error = ParseError;

    fn try_from(query: &String) -> Result<Self, ParseError> {
        Self::from_str(query)
    }
}
