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

/// A parsed [SPARQL update](https://www.w3.org/TR/sparql11-update/).
///
/// ```
/// use spargebra::Update;
///
/// let update_str = "CLEAR ALL ;";
/// let update = Update::parse(update_str, None)?;
/// assert_eq!(update.to_string().trim(), update_str);
/// assert_eq!(update.to_sse(), "(update (clear all))");
/// # Ok::<_, spargebra::ParseError>(())
/// ```
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct Update {
    /// The update base IRI.
    pub base_iri: Option<Iri<String>>,
    /// The [update operations](https://www.w3.org/TR/sparql11-update/#formalModelGraphUpdate).
    pub operations: Vec<GraphUpdateOperation>,
}

impl Update {
    /// Parses a SPARQL update with an optional base IRI to resolve relative IRIs in the query.
    pub fn parse(update: &str, base_iri: Option<&str>) -> Result<Self, ParseError> {
        parse_update(update, 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 {
        if let Some(base_iri) = &self.base_iri {
            write!(f, "(base <{base_iri}> ")?;
        }
        write!(f, "(update")?;
        for op in &self.operations {
            write!(f, " ")?;
            op.fmt_sse(f)?;
        }
        write!(f, ")")?;
        if self.base_iri.is_some() {
            write!(f, ")")?;
        }
        Ok(())
    }
}

impl fmt::Display for Update {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(base_iri) = &self.base_iri {
            writeln!(f, "BASE <{base_iri}>")?;
        }
        for update in &self.operations {
            writeln!(f, "{update} ;")?;
        }
        Ok(())
    }
}

impl FromStr for Update {
    type Err = ParseError;

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

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

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

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

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

/// The [graph update operations](https://www.w3.org/TR/sparql11-update/#formalModelGraphUpdate).
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub enum GraphUpdateOperation {
    /// [insert data](https://www.w3.org/TR/sparql11-update/#defn_insertDataOperation).
    InsertData { data: Vec<Quad> },
    /// [delete data](https://www.w3.org/TR/sparql11-update/#defn_deleteDataOperation).
    DeleteData { data: Vec<GroundQuad> },
    /// [delete insert](https://www.w3.org/TR/sparql11-update/#defn_deleteInsertOperation).
    DeleteInsert {
        delete: Vec<GroundQuadPattern>,
        insert: Vec<QuadPattern>,
        using: Option<QueryDataset>,
        pattern: Box<GraphPattern>,
    },
    /// [load](https://www.w3.org/TR/sparql11-update/#defn_loadOperation).
    Load {
        silent: bool,
        source: NamedNode,
        destination: GraphName,
    },
    /// [clear](https://www.w3.org/TR/sparql11-update/#defn_clearOperation).
    Clear { silent: bool, graph: GraphTarget },
    /// [create](https://www.w3.org/TR/sparql11-update/#defn_createOperation).
    Create { silent: bool, graph: NamedNode },
    /// [drop](https://www.w3.org/TR/sparql11-update/#defn_dropOperation).
    Drop { silent: bool, graph: GraphTarget },
}

impl GraphUpdateOperation {
    /// 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 {
            GraphUpdateOperation::InsertData { data } => {
                write!(f, "(insertData (")?;
                for (i, t) in data.iter().enumerate() {
                    if i > 0 {
                        write!(f, " ")?;
                    }
                    t.fmt_sse(f)?;
                }
                write!(f, "))")
            }
            GraphUpdateOperation::DeleteData { data } => {
                write!(f, "(deleteData (")?;
                for (i, t) in data.iter().enumerate() {
                    if i > 0 {
                        write!(f, " ")?;
                    }
                    t.fmt_sse(f)?;
                }
                write!(f, "))")
            }
            GraphUpdateOperation::DeleteInsert {
                delete,
                insert,
                using,
                pattern,
            } => {
                write!(f, "(modify ")?;
                if let Some(using) = using {
                    write!(f, " (using ")?;
                    using.fmt_sse(f)?;
                    write!(f, " ")?;
                    pattern.fmt_sse(f)?;
                    write!(f, ")")?;
                } else {
                    pattern.fmt_sse(f)?;
                }
                if !delete.is_empty() {
                    write!(f, " (delete (")?;
                    for (i, t) in delete.iter().enumerate() {
                        if i > 0 {
                            write!(f, " ")?;
                        }
                        t.fmt_sse(f)?;
                    }
                    write!(f, "))")?;
                }
                if !insert.is_empty() {
                    write!(f, " (insert (")?;
                    for (i, t) in insert.iter().enumerate() {
                        if i > 0 {
                            write!(f, " ")?;
                        }
                        t.fmt_sse(f)?;
                    }
                    write!(f, "))")?;
                }
                write!(f, ")")
            }
            GraphUpdateOperation::Load {
                silent,
                source,
                destination,
            } => {
                write!(f, "(load ")?;
                if *silent {
                    write!(f, "silent ")?;
                }
                write!(f, "{source} ")?;
                destination.fmt_sse(f)?;
                write!(f, ")")
            }
            GraphUpdateOperation::Clear { silent, graph } => {
                write!(f, "(clear ")?;
                if *silent {
                    write!(f, "silent ")?;
                }
                graph.fmt_sse(f)?;
                write!(f, ")")
            }
            GraphUpdateOperation::Create { silent, graph } => {
                write!(f, "(create ")?;
                if *silent {
                    write!(f, "silent ")?;
                }
                write!(f, "{graph})")
            }
            GraphUpdateOperation::Drop { silent, graph } => {
                write!(f, "(drop ")?;
                if *silent {
                    write!(f, "silent ")?;
                }
                graph.fmt_sse(f)?;
                write!(f, ")")
            }
        }
    }
}

impl fmt::Display for GraphUpdateOperation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            GraphUpdateOperation::InsertData { data } => {
                writeln!(f, "INSERT DATA {{")?;
                write_quads(data, f)?;
                write!(f, "}}")
            }
            GraphUpdateOperation::DeleteData { data } => {
                writeln!(f, "DELETE DATA {{")?;
                write_ground_quads(data, f)?;
                write!(f, "}}")
            }
            GraphUpdateOperation::DeleteInsert {
                delete,
                insert,
                using,
                pattern,
            } => {
                if !delete.is_empty() {
                    writeln!(f, "DELETE {{")?;
                    for quad in delete {
                        writeln!(f, "\t{quad} .")?;
                    }
                    writeln!(f, "}}")?;
                }
                if !insert.is_empty() {
                    writeln!(f, "INSERT {{")?;
                    for quad in insert {
                        writeln!(f, "\t{quad} .")?;
                    }
                    writeln!(f, "}}")?;
                }
                if let Some(using) = using {
                    for g in &using.default {
                        writeln!(f, "USING {g}")?;
                    }
                    if let Some(named) = &using.named {
                        for g in named {
                            writeln!(f, "USING NAMED {g}")?;
                        }
                    }
                }
                write!(
                    f,
                    "WHERE {{ {} }}",
                    SparqlGraphRootPattern {
                        pattern,
                        dataset: None
                    }
                )
            }
            GraphUpdateOperation::Load {
                silent,
                source,
                destination,
            } => {
                write!(f, "LOAD ")?;
                if *silent {
                    write!(f, "SILENT ")?;
                }
                write!(f, "{source}")?;
                if destination != &GraphName::DefaultGraph {
                    write!(f, " INTO GRAPH {destination}")?;
                }
                Ok(())
            }
            GraphUpdateOperation::Clear { silent, graph } => {
                write!(f, "CLEAR ")?;
                if *silent {
                    write!(f, "SILENT ")?;
                }
                write!(f, "{graph}")
            }
            GraphUpdateOperation::Create { silent, graph } => {
                write!(f, "CREATE ")?;
                if *silent {
                    write!(f, "SILENT ")?;
                }
                write!(f, "GRAPH {graph}")
            }
            GraphUpdateOperation::Drop { silent, graph } => {
                write!(f, "DROP ")?;
                if *silent {
                    write!(f, "SILENT ")?;
                }
                write!(f, "{graph}")
            }
        }
    }
}

fn write_quads(quads: &[Quad], f: &mut fmt::Formatter<'_>) -> fmt::Result {
    for quad in quads {
        if quad.graph_name == GraphName::DefaultGraph {
            writeln!(f, "\t{} {} {} .", quad.subject, quad.predicate, quad.object)?;
        } else {
            writeln!(
                f,
                "\tGRAPH {} {{ {} {} {} }}",
                quad.graph_name, quad.subject, quad.predicate, quad.object
            )?;
        }
    }
    Ok(())
}

fn write_ground_quads(quads: &[GroundQuad], f: &mut fmt::Formatter<'_>) -> fmt::Result {
    for quad in quads {
        if quad.graph_name == GraphName::DefaultGraph {
            writeln!(f, "\t{} {} {} .", quad.subject, quad.predicate, quad.object)?;
        } else {
            writeln!(
                f,
                "\tGRAPH {} {{ {} {} {} }}",
                quad.graph_name, quad.subject, quad.predicate, quad.object
            )?;
        }
    }
    Ok(())
}
