use combine::{
    attempt, char,
    char::{char, crlf, newline as lf},
    choice, combine_parse_partial, combine_parser_impl, eof, optional, parse_mode,
    parser,
    range::{recognize, take_while, take_while1},
    skip_many, skip_many1,
    stream::RangeStream,
    ParseError, StreamOnce,
};

// wschar = ( %x20 /              ; Space
//            %x09 )              ; Horizontal tab
#[inline]
fn is_wschar(c: char) -> bool { matches!(c, ' ' | '\t') }

// ws = *wschar
parse!(ws() -> &'a str, {
    take_while(is_wschar)
});

// non-eol = %x09 / %x20-10FFFF
#[inline]
fn is_non_eol(c: char) -> bool { matches!(c, '\u{09}' | '\u{20}'..='\u{10FFFF}') }

// comment-start-symbol = %x23 ; #
const COMMENT_START_SYMBOL: char = '#';

// comment = comment-start-symbol *non-eol
parse!(comment() -> &'a str, {
    recognize((
        attempt(char(COMMENT_START_SYMBOL)),
        take_while(is_non_eol),
    ))
});

// newline = ( %x0A /              ; LF
//             %x0D.0A )           ; CRLF
parse!(newline() -> char, {
    choice((lf(), crlf()))
        .map(|_| '\n')
        .expected("a newline")
});

// ws-newline       = *( wschar / newline )
parse!(ws_newline() -> &'a str, {
    recognize(
        skip_many(choice((
            newline().map(|_| "\n"),
            take_while1(is_wschar),
        ))),
    )
});

// ws-newlines      = newline *( wschar / newline )
parse!(ws_newlines() -> &'a str, {
    recognize((
        newline(),
        ws_newline(),
    ))
});

// note: this rule is not present in the original grammar
// ws-comment-newline = *( ws-newline-nonempty / comment )
parse!(ws_comment_newline() -> &'a str, {
    recognize(
        skip_many(
            choice((
                skip_many1(
                    choice((
                        take_while1(is_wschar),
                        newline().map(|_| "\n"),
                    ))
                ),
                comment().map(|_| ()),
            ))
        )
    )
});

// note: this rule is not present in the original grammar
// line-ending = newline / eof
parse!(line_ending() -> &'a str, {
    choice((
        newline().map(|_| "\n"),
        eof().map(|_| "")
    ))
});

// note: this rule is not present in the original grammar
// line-trailing = ws [comment] skip-line-ending
parse!(line_trailing() -> &'a str, {
    recognize((
        ws(),
        optional(comment()),
    )).skip(line_ending())
});
