use super::parser_context::ContextElement;
use super::parser_context::PreviousElementNode;
use super::token::Token;
use super::Context;
use nom::error::ErrorKind;
use nom::error::ParseError;
use nom::IResult;
use nom::InputLength;

pub fn context_many1<'r, 's, I, O, E, M>(
    context: Context<'r, 's>,
    mut many_matcher: M,
) -> impl FnMut(I) -> IResult<I, Vec<Token<'s>>, E> + 'r
where
    I: Clone + InputLength,
    E: ParseError<I>,
    M: for<'x> Fn(Context<'x, 's>, I) -> IResult<I, O, E> + 'r,
    O: Into<Token<'s>>,
{
    move |mut i: I| {
        let mut err = None;
        // TODO: Can I eliminate the clone? I think this is incrementing the reference count
        let mut current_context = context.clone();
        // Despite the clone, the Rc should still point to the same value.
        assert!(current_context.ptr_eq(context));
        loop {
            match many_matcher(&current_context, i.clone()) {
                Ok((remaining, many_elem)) => {
                    current_context = current_context.with_additional_node(
                        ContextElement::PreviousElementNode(PreviousElementNode {
                            element: many_elem.into(),
                        }),
                    );
                    i = remaining;
                }
                the_error @ Err(_) => {
                    err = Some(the_error);
                    break;
                }
            }
        }
        let mut elements: Vec<Token<'s>> = current_context
            .into_iter_until(context)
            .filter_map(|context_element| match context_element {
                ContextElement::PreviousElementNode(elem) => Some(elem.element),
                _ => None,
            })
            .collect();
        if elements.is_empty() {
            if let Some(err) = err {
                err?;
            }
        }
        elements.reverse();
        Ok((i, elements))
    }
}

pub fn context_many_till<'r, 's, I, O, E, F, M, T>(
    context: Context<'r, 's>,
    mut many_matcher: M,
    mut till_matcher: T,
) -> impl FnMut(I) -> IResult<I, (Vec<Token<'s>>, F), E> + 'r
where
    I: Clone + InputLength,
    E: ParseError<I>,
    M: for<'x> Fn(Context<'x, 's>, I) -> IResult<I, O, E> + 'r,
    T: for<'x> Fn(Context<'x, 's>, I) -> IResult<I, F, E> + 'r,
    O: Into<Token<'s>>,
{
    move |mut i: I| {
        // TODO: Can I eliminate the clone? I think this is incrementing the reference count
        let mut current_context = context.clone();
        // Despite the clone, the Rc should still point to the same value, otherwise we'll get stuck in an endless loop.
        assert!(current_context.ptr_eq(context));
        loop {
            let len = i.input_len();
            match till_matcher(&current_context, i.clone()) {
                Ok((remaining, finish)) => {
                    let mut ret = Vec::new();
                    while !current_context.ptr_eq(context) {
                        let (context_element, next_context) = current_context.pop_front();
                        let context_element = context_element.expect("We only pop off context elements created in this function, so they are all Some()");
                        current_context = next_context;
                        match context_element {
                            ContextElement::PreviousElementNode(PreviousElementNode {
                                element: token,
                            }) => {
                                ret.push(token);
                            }
                            _ => {}
                        };
                    }
                    ret.reverse();
                    return Ok((remaining, (ret, finish)));
                }
                Err(nom::Err::Error(_)) => {
                    match many_matcher(&current_context, i.clone()) {
                        Err(nom::Err::Error(err)) => {
                            return Err(nom::Err::Error(E::append(i, ErrorKind::ManyTill, err)))
                        }
                        Err(e) => return Err(e),
                        Ok((remaining, many_elem)) => {
                            // infinite loop check: the parser must always consume
                            if remaining.input_len() == len {
                                return Err(nom::Err::Error(E::from_error_kind(
                                    remaining,
                                    ErrorKind::ManyTill,
                                )));
                            }

                            current_context = current_context.with_additional_node(
                                ContextElement::PreviousElementNode(PreviousElementNode {
                                    element: many_elem.into(),
                                }),
                            );
                            i = remaining;
                        }
                    }
                }
                Err(e) => return Err(e),
            };
        }
    }
}