use crate::ParseError;
use crate::Span;
use crate::compact::shapemap_grammar::ShapeMapStatement;
use crate::compact::shapemap_grammar::shapemap_statement;
use crate::compact::shapemap_grammar::{node_selector, shape_spec};
use crate::compact::shex_grammar::iri;
use crate::shapemap::NodeSelector;
use crate::shapemap::ShapeSelector;
use crate::shapemap::query_shape_map::QueryShapeMap;
use crate::tws0;
use iri_s::IriS;
use nom::Err;
use prefixmap::IriRef;
use prefixmap::PrefixMap;
use std::fs;
use std::path::Path;
use tracing::debug;
use tracing::trace;

type Result<A> = std::result::Result<A, ParseError>;

pub struct ShapeMapParser<'a> {
    shapemap_statement_iterator: ShapeMapStatementIterator<'a>,
}

impl ShapeMapParser<'_> {
    /// Parse a ShapeMap that uses [ShapeMap compact syntax](https://shexspec.github.io/shape-map/#grammar)
    ///
    pub fn parse(
        src: &str,
        nodes_prefixmap: &Option<PrefixMap>,
        base_nodes: &Option<IriS>,
        shapes_prefixmap: &Option<PrefixMap>,
        base_shapes: &Option<IriS>,
    ) -> Result<QueryShapeMap> {
        trace!("Parsing shapemap with src: {src} and base_nodes: {base_nodes:?} and base_shapes: {base_shapes:?}");
        let mut query_shapemap = QueryShapeMap::new();
        if let Some(pm) = nodes_prefixmap {
            query_shapemap = query_shapemap.with_nodes_prefixmap(pm)
        };
        if let Some(pm) = shapes_prefixmap {
            query_shapemap = query_shapemap.with_shapes_prefixmap(pm)
        };
        let mut parser = ShapeMapParser {
            shapemap_statement_iterator: ShapeMapStatementIterator::new(Span::new(src))?,
        };
        for ss in parser.shapemap_statement_iterator.by_ref() {
            let statements = ss?;
            for s in statements {
                match s {
                    ShapeMapStatement::Association {
                        node_selector,
                        shape_selector,
                    } => {
                        tracing::debug!(
                            "Association {node_selector:?}@{shape_selector:?} with base_nodes: {base_nodes:?} and base_shapes: {base_shapes:?}"
                        );
                        query_shapemap.add_association(node_selector, base_nodes, shape_selector, base_shapes)?;
                    },
                }
            }
        }
        Ok(query_shapemap)
    }

    pub fn parse_buf(
        path: &Path,
        nodes_prefixmap: &Option<PrefixMap>,
        base_nodes: &Option<IriS>,
        shapes_prefixmap: &Option<PrefixMap>,
        base_shapes: &Option<IriS>,
    ) -> Result<QueryShapeMap> {
        let data = fs::read_to_string(path)?;
        let query_shapemap = ShapeMapParser::parse(&data, nodes_prefixmap, base_nodes, shapes_prefixmap, base_shapes)?;
        Ok(query_shapemap)
    }

    pub fn parse_shape_selector(str: &str) -> Result<ShapeSelector> {
        let span = Span::new(str);
        let (_, ss) = shape_spec()(span).map_err(|e| match e {
            Err::Incomplete(s) => ParseError::Custom {
                msg: format!("Incomplete input: needed {s:?}"),
            },
            Err::Error(e) => ParseError::NomError { err: Box::new(e) },
            Err::Failure(f) => ParseError::NomError { err: Box::new(f) },
        })?;
        Ok(ss)
    }

    pub fn parse_node_selector(str: &str) -> Result<NodeSelector> {
        let span = Span::new(str);
        let (_, ns) = node_selector()(span).map_err(|e| match e {
            Err::Incomplete(s) => ParseError::Custom {
                msg: format!("Incomplete input parsing node selector {str}: needed {s:?}"),
            },
            Err::Error(e) => ParseError::NodeSelectorNomError {
                str: str.to_string(),
                err: Box::new(e),
            },
            Err::Failure(f) => ParseError::NodeSelectorNomError {
                str: str.to_string(),
                err: Box::new(f),
            },
        })?;
        Ok(ns)
    }

    pub fn parse_iri_ref(str: &str) -> Result<IriRef> {
        let span = Span::new(str);
        let (_, iri_ref) = iri(span).map_err(|e| match e {
            Err::Incomplete(s) => ParseError::Custom {
                msg: format!("Incomplete input: needed {s:?}"),
            },
            Err::Error(e) => ParseError::NomError { err: Box::new(e) },
            Err::Failure(f) => ParseError::NomError { err: Box::new(f) },
        })?;
        Ok(iri_ref)
    }
}

struct ShapeMapStatementIterator<'a> {
    src: Span<'a>,
    done: bool,
}

impl ShapeMapStatementIterator<'_> {
    pub fn new(src: Span) -> Result<ShapeMapStatementIterator> {
        match tws0(src) {
            Ok((left, _)) => Ok(ShapeMapStatementIterator { src: left, done: false }),
            Err(Err::Incomplete(_)) => Ok(ShapeMapStatementIterator { src, done: false }),
            Err(e) => Err(ParseError::Custom {
                msg: format!("cannot start parsing. Error: {e}"),
            }),
        }
    }
}

impl Iterator for ShapeMapStatementIterator<'_> {
    type Item = Result<Vec<ShapeMapStatement>>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.done {
            return None;
        }
        let mut r;
        match shapemap_statement()(self.src) {
            Ok((left, s)) => {
                if s.is_empty() {
                    r = None;
                } else {
                    r = Some(Ok(s));
                }
                self.src = left;
            },
            Err(Err::Incomplete(_)) => {
                debug!("Incomplete! shapemap_statement");
                self.done = true;
                r = None;
            },
            Err(Err::Error(e)) | Err(Err::Failure(e)) => {
                r = Some(Err(ParseError::NomError { err: Box::new(e) }));
                self.done = true;
            },
        }
        if self.src.is_empty() {
            self.done = true;
        }
        if r.is_none() && !self.src.is_empty() {
            r = Some(Err(ParseError::Custom {
                msg: format!("trailing bytes {}", self.src),
            }));
        }
        r
    }
}

#[cfg(test)]
mod tests {

    use crate::shapemap::{NodeSelector, ShapeSelector};
    use iri_s::IriS;

    use super::*;

    #[test]
    fn test_prefix() {
        let str = r#":a@:S"#;
        let mut nodes_prefixmap = PrefixMap::new();
        nodes_prefixmap
            .add_prefix("", IriS::new_unchecked("http://example.org/"))
            .unwrap();

        let mut shapes_prefixmap = PrefixMap::new();
        shapes_prefixmap
            .add_prefix("", IriS::new_unchecked("http://example.org/shapes/"))
            .unwrap();

        let parsed_shapemap = ShapeMapParser::parse(
            str,
            &Some(nodes_prefixmap.clone()),
            &None,
            &Some(shapes_prefixmap.clone()),
            &None,
        )
        .unwrap();

        let mut expected = QueryShapeMap::new()
            .with_nodes_prefixmap(&nodes_prefixmap)
            .with_shapes_prefixmap(&shapes_prefixmap);

        expected
            .add_association(
                NodeSelector::prefixed("", "a"),
                &None,
                ShapeSelector::prefixed("", "S"),
                &None,
            )
            .unwrap();
        assert_eq!(parsed_shapemap, expected)
    }
}
