use crate::prelude::*;

use std::io::BufRead;
use std::sync::Arc;
use std::sync::Mutex;

use tokio::io::{AsyncBufRead, AsyncBufReadExt};
use winnow::error::ErrMode;
use winnow::stream::Offset as _;
use winnow::stream::Stream as _;

use std::fmt;

use crate::terms::*;
use crate::triples::*;

use winnow::ascii::{line_ending, multispace0, space0, till_line_ending};
use winnow::combinator::trace as p_trace;
use winnow::combinator::{
    alt, delimited, dispatch, eof, fail, opt, peek, preceded, repeat, terminated,
};
use winnow::error::ContextError;
use winnow::stream::Stateful;
use winnow::token::{none_of, one_of, take, take_while};
use winnow::{ModalResult, Parser as _};

use futures::{Future, FutureExt, Stream};
use std::pin::Pin;
use std::task::{Context, Poll};

use tracing::{debug, error, trace};

#[derive(Debug, Clone)]
pub struct State {
    pub profile: Arc<Mutex<BasicProfile>>,
}

impl Default for State {
    fn default() -> Self {
        Self::new()
    }
}

impl State {
    pub fn new() -> Self {
        Self {
            profile: Arc::new(BasicProfile::new().into()),
        }
    }

    pub fn from_profile(profile: Arc<Mutex<BasicProfile>>) -> Self {
        Self { profile }
    }

    pub fn blank_node(&mut self, label: String) -> Arc<Term> {
        // FIXME probably move to an Environment struct
        let mut profile = self.profile.lock().unwrap();
        match profile.terms.resolve_unchecked(label.as_str()) {
            Some(t) => t.clone(),
            None => {
                let term = Arc::new(Term::BlankNode(BlankNode::new()));
                profile.terms.set(label, term.clone());
                term
            }
        }
    }
}

pub type TokenStream<'i> = Stateful<&'i str, &'i mut State>;

const PN_CHARS_BASE: (
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
) = (
    ('A'..='Z'),
    ('a'..='z'),
    ('\u{00c0}'..='\u{00d6}'),
    ('\u{00d8}'..='\u{00f6}'),
    ('\u{00f8}'..='\u{02ff}'),
    ('\u{0370}'..='\u{037d}'),
    ('\u{037f}'..='\u{1fff}'),
    ('\u{200c}'..='\u{200d}'),
    ('\u{2070}'..='\u{218f}'),
    ('\u{2c00}'..='\u{2fef}'),
    ('\u{3001}'..='\u{d7ff}'),
    ('\u{f900}'..='\u{fdcf}'),
    ('\u{fdf0}'..='\u{fffd}'),
    ('\u{10000}'..='\u{effff}'),
);
const PN_CHARS_U: (
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    char,
) = (
    ('A'..='Z'),
    ('a'..='z'),
    ('\u{00c0}'..='\u{00d6}'),
    ('\u{00d8}'..='\u{00f6}'),
    ('\u{00f8}'..='\u{02ff}'),
    ('\u{0370}'..='\u{037d}'),
    ('\u{037f}'..='\u{1fff}'),
    ('\u{200c}'..='\u{200d}'),
    ('\u{2070}'..='\u{218f}'),
    ('\u{2c00}'..='\u{2fef}'),
    ('\u{3001}'..='\u{d7ff}'),
    ('\u{f900}'..='\u{fdcf}'),
    ('\u{fdf0}'..='\u{fffd}'),
    ('\u{10000}'..='\u{effff}'),
    '_',
);
const PN_CHARS: (
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
    char,
    char,
    std::ops::RangeInclusive<char>,
    char,
    std::ops::RangeInclusive<char>,
    std::ops::RangeInclusive<char>,
) = (
    ('A'..='Z'),
    ('a'..='z'),
    ('\u{00c0}'..='\u{00d6}'),
    ('\u{00d8}'..='\u{00f6}'),
    ('\u{00f8}'..='\u{02ff}'),
    ('\u{0370}'..='\u{037d}'),
    ('\u{037f}'..='\u{1fff}'),
    ('\u{200c}'..='\u{200d}'),
    ('\u{2070}'..='\u{218f}'),
    ('\u{2c00}'..='\u{2fef}'),
    ('\u{3001}'..='\u{d7ff}'),
    ('\u{f900}'..='\u{fdcf}'),
    ('\u{fdf0}'..='\u{fffd}'),
    ('\u{10000}'..='\u{effff}'),
    '_',
    '-',
    ('0'..='9'),
    '\u{00b7}',
    ('\u{0300}'..='\u{036f}'),
    ('\u{203f}'..='\u{2040}'),
);

pub fn uchar16_parser(input: &mut TokenStream<'_>) -> ModalResult<char> {
    let chars = p_trace(
        "uchar16_parser",
        preceded(
            "\\u",
            take_while(4..=4, (('A'..='F'), ('a'..='f'), ('0'..='9'))),
        ),
    )
    .parse_next(input)?;

    let mut res = 0u32;
    for c in chars.as_bytes() {
        let val = (*c as char).to_digit(16).expect("parser yielded hex chars");
        res = (res << 4) + val;
    }

    char::from_u32(res).ok_or(ErrMode::Cut(ContextError::new()))
}

pub fn uchar32_parser(input: &mut TokenStream<'_>) -> ModalResult<char> {
    let chars = p_trace(
        "uchar32_parser",
        preceded(
            "\\U",
            take_while(8..=8, (('A'..='F'), ('a'..='f'), ('0'..='9'))),
        ),
    )
    .parse_next(input)?;

    let mut res = 0u32;
    for c in chars.as_bytes() {
        let val = (*c as char).to_digit(16).unwrap();
        res = (res << 4) + val;
    }

    char::from_u32(res).ok_or(ErrMode::Cut(ContextError::new()))
}

pub fn iriref_parser(input: &mut TokenStream<'_>) -> ModalResult<Iri> {
    let iriref: String = p_trace(
        "iriref_parser",
        delimited(
            '<',
            repeat(
                0..,
                alt((
                    none_of((
                        ('\x00'..='\x20'),
                        '<',
                        '>',
                        '"',
                        '{',
                        '}',
                        '|',
                        '^',
                        '`',
                        '\\',
                    )),
                    uchar16_parser,
                    uchar32_parser,
                )),
            ),
            '>',
        ),
    )
    .parse_next(input)?;

    Iri::new(iriref).map_err(|_| ErrMode::Cut(ContextError::new()))
}

pub fn blank_node_label_parser(input: &mut TokenStream<'_>) -> ModalResult<Arc<Term>> {
    let start = input.checkpoint();

    let mut label = {
        let res = p_trace(
            "blank_node_label_parser",
            preceded(
                "_:",
                (
                    one_of((PN_CHARS_U, '0'..='9')),
                    take_while(0.., (PN_CHARS, '.')),
                    take_while(0..=1, PN_CHARS),
                ),
            ),
        )
        .parse_next(input)?;

        let mut b = [0; 3];
        [res.0.encode_utf8(&mut b), res.1, res.2].join("")
    };

    if label.ends_with('.') {
        let end = input.checkpoint();
        let offset = end.offset_from(&start) - 1;
        input.reset(&start);
        take(offset).parse_next(input)?;
        label.pop();
    }

    Ok(input.state.blank_node(label))
}

pub fn echar_parser(input: &mut TokenStream<'_>) -> ModalResult<char> {
    p_trace(
        "echar_parser",
        preceded(
            '\\',
            alt((
                't'.value('\t'),
                'b'.value('\x08'),
                'n'.value('\n'),
                'r'.value('\r'),
                'f'.value('\x0c'),
                '"'.value('"'),
                '\''.value('\''),
                '\\'.value('\\'),
            )),
        ),
    )
    .parse_next(input)
}

pub fn string_literal_quote_parser(input: &mut TokenStream<'_>) -> ModalResult<String> {
    p_trace(
        "string_literal_quote_parser",
        delimited(
            '"',
            repeat(
                0..,
                alt((
                    none_of(('\x22', '\x5c', '\x0a', '\x0d')),
                    echar_parser,
                    uchar16_parser,
                    uchar32_parser,
                )),
            ),
            '"',
        ),
    )
    .parse_next(input)
}

pub fn literal_datatype_parser(input: &mut TokenStream<'_>) -> ModalResult<Iri> {
    p_trace("literal_datatype_parser", preceded("^^", iriref_parser)).parse_next(input)
}

pub fn langtag_parser(
    input: &mut TokenStream<'_>,
) -> ModalResult<(LanguageTag, Option<BaseDirection>)> {
    let (primary, subs, dir) = p_trace(
        "langtag_parser",
        preceded(
            "@",
            (
                take_while(1.., (('A'..='Z'), ('a'..='z'))),
                repeat(
                    0..,
                    preceded(
                        '-',
                        take_while(1.., (('A'..='Z'), ('a'..='z'), ('0'..='9'))),
                    ),
                )
                .fold(
                    || String::with_capacity(11),
                    |mut acc: String, item| {
                        acc.push('-');
                        acc.push_str(item);
                        acc
                    },
                ),
                opt(preceded("--", take_while(1.., (('A'..='Z'), ('a'..='z'))))),
            ),
        ),
    )
    .parse_next(input)?;

    let mut tag = String::with_capacity(primary.len() + subs.len() + 1);
    tag.push_str(primary);
    tag.push_str(subs.as_str());

    let language =
        LanguageTag::parse(tag.as_str()).map_err(|_| ErrMode::Cut(ContextError::new()))?;
    let direction = match dir {
        Some(d) => Some(BaseDirection::try_from(d).map_err(|_| ErrMode::Cut(ContextError::new()))?),
        None => None,
    };

    // FIXME handle error
    Ok((language, direction))
}

pub fn literal_parser(input: &mut TokenStream<'_>) -> ModalResult<Arc<Term>> {
    let (lexical, datatype, tag) = p_trace(
        "literal_parser",
        (
            string_literal_quote_parser,
            opt(literal_datatype_parser),
            opt(langtag_parser),
        ),
    )
    .parse_next(input)?;

    Ok(Arc::new(Term::Literal(
        if let Some((language, direction)) = tag {
            Literal::new(lexical, datatype, Some(language), direction)
                .map_err(|_| ErrMode::Cut(ContextError::new()))?
        } else {
            Literal::new(lexical, datatype, None, None)
                .map_err(|_| ErrMode::Cut(ContextError::new()))?
        },
    )))
}

pub fn triple_term_parser(input: &mut TokenStream<'_>) -> ModalResult<Arc<Term>> {
    let (s, p, o) = p_trace(
        "triple_term_parser",
        delimited(
            // FIXME parenthesis aren't optional, according to RDF 1.2; awaiting final spec
            ("<<(", space0),
            (subject_parser, predicate_parser, object_parser),
            ")>>",
        ),
    )
    .parse_next(input)?;

    Ok(Arc::new(Term::TripleTerm(
        Triple::new(s.clone(), p.clone(), o.clone())
            .map_err(|_| ErrMode::Cut(ContextError::new()))?,
    )))
}

pub fn subject_parser(input: &mut TokenStream<'_>) -> ModalResult<Arc<Term>> {
    p_trace(
        "subject_parser",
        terminated(
            dispatch!(peek(take(1usize));
                "<" => iriref_parser.map(|iri: Iri| Arc::new(Term::NamedNode(iri.clone()))),
                "_" => blank_node_label_parser,
                _ => fail,
            ),
            space0,
        ),
    )
    .parse_next(input)
}

pub fn predicate_parser(input: &mut TokenStream<'_>) -> ModalResult<Arc<Term>> {
    p_trace(
        "predicate_parser",
        terminated(
            dispatch!(peek(take(1usize));
                "<" => iriref_parser.map(|iri: Iri| Arc::new(Term::NamedNode(iri.clone()))),
                _ => fail,
            ),
            space0,
        ),
    )
    .parse_next(input)
}

pub fn object_parser(input: &mut TokenStream<'_>) -> ModalResult<Arc<Term>> {
    if peek(take::<_, _, ContextError>(3usize)).parse_next(input) == Ok("<<(") {
        return p_trace(
            "object_parser_for_triple_term",
            terminated(triple_term_parser, space0),
        )
        .parse_next(input);
    }

    p_trace(
        "object_parser",
        terminated(
            dispatch!(peek(take(1usize));
                "<" => iriref_parser.map(|iri: Iri| Arc::new(Term::NamedNode(iri.clone()))),
                "_" => blank_node_label_parser,
                "\"" => literal_parser,
                _ => fail,
            ),
            space0,
        ),
    )
    .parse_next(input)
}

pub fn comment_parser<'i>(input: &mut TokenStream<'i>) -> ModalResult<&'i str> {
    p_trace("comment_parser", preceded((space0, '#'), till_line_ending)).parse_next(input)
}

pub fn comment_or_blank_line_parser<'i>(input: &mut TokenStream<'i>) -> ModalResult<&'i str> {
    p_trace(
        "comment_or_blank_line_parser",
        terminated(alt((comment_parser, space0)), alt((line_ending, eof))),
    )
    .parse_next(input)
}

pub fn triple_parser(input: &mut TokenStream<'_>) -> ModalResult<Arc<Triple>> {
    let (s, p, o) = p_trace(
        "triple_parser",
        delimited(
            multispace0,
            (subject_parser, predicate_parser, object_parser),
            ('.', comment_or_blank_line_parser),
        ),
    )
    .parse_next(input)?;

    Triple::new(s.clone(), p.clone(), o.clone()).map_err(|_| ErrMode::Cut(ContextError::new()))
}

#[derive(Debug)]
pub enum LineType {
    Triple(Arc<Triple>),
    CommentOrBlank,
}

pub fn line_parser(input: &mut TokenStream<'_>) -> ModalResult<LineType> {
    let checkpoint = input.checkpoint();
    match triple_parser.parse_next(input) {
        Ok(triple) => Ok(LineType::Triple(triple.clone())),
        Err(ErrMode::Backtrack(e)) => {
            input.reset(&checkpoint);
            match comment_or_blank_line_parser.parse_next(input) {
                Ok(_) => Ok(LineType::CommentOrBlank),
                Err(_) => Err(ErrMode::Cut(e)),
            }
        }
        Err(e) => Err(e),
    }
}

const CHUNK_SIZE: usize = 1024;

/// # Example
///
/// ```
/// use taganak_core::{sources::parsers::n_triples::NTriplesParser, environment::profile::BasicProfile};
/// use std::sync::{Arc, Mutex};
/// use futures::{Stream, StreamExt};
///
/// env_logger::builder().is_test(true).try_init();
///
/// const TRIPLES: &str = r##"
/// <https://example.com/foo#Emil> <https://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://example.com/foo#Turtle> .
/// <https://example.com/foo#Turtle> <https://www.w3.org/2000/01/rdf-schema#label> "Schildkröte"@de .
///
/// <https://example.com/foo#Emil> <https://example.com/foo/shell> _:panzer .
/// _:panzer <https://example.com/foo/color> "#00ff00" .
///
/// <https://example.com/foo#Emil> <https://example.com/foo#heroName> "Emil \"Panzerino\" Turtle" .
/// "##;
///
/// let mut state = Arc::new(Mutex::new(BasicProfile::new()));
/// let mut parser = NTriplesParser::new(TRIPLES.as_bytes(), state);
///
/// tokio_test::block_on(async {
///     assert_eq!(parser.next().await.unwrap().unwrap().to_string(), r#"<https://example.com/foo#Emil> <https://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://example.com/foo#Turtle> ."#);
///     assert_eq!(parser.next().await.unwrap().unwrap().to_string(), r#"<https://example.com/foo#Turtle> <https://www.w3.org/2000/01/rdf-schema#label> "Schildkröte"@de ."#);
///
///     let t1 = parser.next().await.unwrap().unwrap();
///     let t2 = parser.next().await.unwrap().unwrap();
///     assert_eq!(t1.object(), t2.subject());
///
///     let t = parser.next().await.unwrap().unwrap();
///     let hero_name: String = t.object().to_literal().cloned().unwrap().try_into().unwrap();
///     assert_eq!(hero_name, r#"Emil "Panzerino" Turtle"#);
///
///     assert!(parser.next().await.is_none());
/// });
/// ```
pub struct NTriplesParser<Lines> {
    // FIXME can we get by with a generic here?
    stream: Lines,
    state: State,
}

impl<R> fmt::Debug for NTriplesParser<R> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("TriplesParser").finish()
    }
}

impl<R: AsyncBufRead + AsyncBufReadExt> NTriplesParser<tokio::io::Lines<R>> {
    pub fn new(stream: R, state: Arc<Mutex<BasicProfile>>) -> Self {
        debug!("Initialising new NTriplesParser for stream");
        Self {
            stream: stream.lines(),
            state: State::from_profile(state),
        }
    }
}

impl<R: BufRead> NTriplesParser<R> {
    pub fn new_sync(buf: R, state: Arc<Mutex<BasicProfile>>) -> Self {
        Self { state: State::from_profile(state), stream: buf }
    }
}

impl<R: AsyncBufRead + AsyncBufReadExt + Unpin> Stream for NTriplesParser<tokio::io::Lines<R>> {
    type Item = Result<Arc<Triple>, SourceError>;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        trace!("Reading n-triples line into buffer with {CHUNK_SIZE} bytes");
        let mut buf = String::with_capacity(CHUNK_SIZE);

        // Loop will run until a triple line was read, or EOF, or failure
        loop {
            trace!(buf, "buffer is");
            let mut state = self.state.clone();
            let next_line = self.stream.next_line();
            let line = core::pin::pin!(next_line);
            match line.poll(cx) {
                Poll::Pending => {
                    trace!(buf, "read pending, buffer is");
                    return Poll::Pending;
                }
                Poll::Ready(Err(e)) => {
                    error!("Could not read n-triples stream: {e}");
                    return Poll::Ready(Some(Err(SourceError::Io(e))));
                }
                Poll::Ready(Ok(None)) => {
                    trace!("Reached end of n-triples stream");
                    return Poll::Ready(None);
                }
                Poll::Ready(Ok(Some(mut s))) => {
                    trace!(buf, len = buf.len(), "read buffer line");
                    trace!("Parsing {} bytes of n-triples data", buf.len());
                    let input = TokenStream {
                        input: s.as_mut_str(),
                        state: &mut state,
                    };

                    match line_parser.parse(input) {
                        Ok(LineType::CommentOrBlank) => {
                            trace!("Parsed n-triples comment");
                            // Continue loop until we reach a triple line
                            buf.clear();
                            continue;
                        }
                        Ok(LineType::Triple(triple)) => {
                            trace!("Parsed n-triples triple: {:#?}", &triple);
                            return Poll::Ready(Some(Ok(triple.clone())));
                        }
                        Err(e) => {
                            error!("Failed to parse n-triples line");
                            debug!("Failed line was: {buf}");
                            return Poll::Ready(Some(Err(SourceError::Syntax(e.to_string()))));
                        }
                    }
                }
            }
        }
    }
}

impl<R: BufRead> Iterator for NTriplesParser<R> {
    type Item = Result<Arc<Triple>, SourceError>;

    fn next(&mut self) -> Option<Self::Item> {
        let mut buf = String::with_capacity(CHUNK_SIZE);
        loop {
            let mut line = String::new();
            let res = self.stream.read_line(&mut line);
            match res {
                Ok(0) => return None,
                Ok(_) => {
                    let input = TokenStream {
                        input: line.as_mut_str(),
                        state: &mut self.state,
                    };
                    match line_parser.parse(input) {
                        Ok(LineType::CommentOrBlank) => {
                            buf.clear();
                            continue;
                        }
                        Ok(LineType::Triple(triple)) => return Some(Ok(triple.clone())),
                        Err(e) => return Some(Err(SourceError::Syntax(e.to_string()))),
                    }
                }
                Err(e) => {
                    return Some(Err(SourceError::Io(e)));
                }
            }
        }
    }
}
