use std::fmt;

use crate::parser::{Input, ParseResult, Parser, Span};

use super::{
    code::{self, Code},
    ParseError,
};

/// Add a context to the error with a message. The code is set to
/// [`ERR_CONTEXT`](super::code).
///
/// # Examples
///
/// ```rust
/// # use kamo::parser::{prelude::*, code, Input, Position};
/// let mut parser = context(tag("hello"), "Expected `hello`");
///
/// assert_eq!(parser.parse("hello world".into()),
///     Ok(("hello", Input::from(" world"))));
/// assert_eq!(parser.parse("world".into()), Err(ParseError::new(
///     Position::new(0, 1, 1),
///     code::ERR_CONTEXT,
///     "Expected `hello`"
/// )));
/// ```
pub fn context<'a, 'b, F, O, M>(mut f: F, msg: M) -> impl FnMut(Input<'a>) -> ParseResult<'a, O>
where
    O: 'b,
    F: Parser<'a, 'b, O> + 'b,
    M: fmt::Display,
{
    let msg = msg.to_string();

    move |input| {
        f.parse(input).map_err(|mut err| {
            err.push(
                Span::new(input.position(), err.span().end()),
                code::ERR_CONTEXT,
                msg.to_owned(),
            );
            err
        })
    }
}

/// Add a context to the error with a message and a custom code.
///
/// # Examples
///
/// ```rust
/// # use kamo::parser::{prelude::*, code, Input, Position};
/// let mut parser = context_as(tag("hello"), code::ERR_TAG, "Expected `hello`");
///
/// assert_eq!(parser.parse("hello world".into()),
///     Ok(("hello", Input::from(" world"))));
/// assert_eq!(parser.parse("world".into()), Err(ParseError::new(
///     Position::new(0, 1, 1),
///     code::ERR_TAG,
///     "Expected `hello`"
/// )));
pub fn context_as<'a, 'b, F, O, M>(
    mut f: F,
    code: Code,
    msg: M,
) -> impl FnMut(Input<'a>) -> ParseResult<'a, O>
where
    O: 'b,
    F: Parser<'a, 'b, O> + 'b,
    M: fmt::Display,
{
    let msg = msg.to_string();

    move |input| {
        f.parse(input).map_err(|mut err| {
            err.push(
                Span::new(input.position(), err.span().end()),
                code,
                msg.to_owned(),
            );
            err
        })
    }
}

/// Add a context to the error with a custom code and a custom message. The
/// message and code is generated by the closure.
///
/// # Examples
///
/// ```rust
/// # use kamo::parser::{prelude::*, code, Input, Position};
/// let mut parser = context_and(tag("hello"), |err| {
///     (code::ERR_TAG, "Expected `hello`".into())
/// });
/// 
/// assert_eq!(parser.parse("hello world".into()),
///     Ok(("hello", Input::from(" world"))));
/// assert_eq!(parser.parse("world".into()), Err(ParseError::new(
///     Position::new(0, 1, 1),
///     code::ERR_TAG,
///     "Expected `hello`"
/// )));
/// ```
pub fn context_and<'a, 'b, F1, F2, O>(
    mut f: F1,
    mut g: F2,
) -> impl FnMut(Input<'a>) -> ParseResult<'a, O>
where
    O: 'b,
    F1: Parser<'a, 'b, O> + 'b,
    F2: FnMut(&ParseError) -> (Code, String),
{
    move |input| {
        f.parse(input).map_err(|mut err| {
            let (code, msg) = g(&err);
            err.push(Span::new(input.position(), err.span().end()), code, msg);
            err
        })
    }
}
