use std::rc::Rc;

use nom::{
    AsChar, FindToken, IResult, Input, Parser,
    branch::alt,
    bytes::streaming::{is_not, tag},
    character::{
        anychar,
        streaming::{char, one_of},
    },
    combinator::{map, map_res, opt, recognize, value, verify},
    error::{Error, ErrorKind, FromExternalError, ParseError},
    multi::{fold, fold_many1, many0},
    sequence::{delimited, preceded},
};

use crate::vm::value::{Keyword, NumberValue, Value, Vector};

struct IdentifierHead;
struct IdentifierTail;

impl FindToken<char> for IdentifierHead {
    fn find_token(&self, token: char) -> bool {
        token.is_alphabetic() || "~!@$%^&*-=_+<>?/|≠≥≤·:".contains(token)
    }
}

impl FindToken<char> for IdentifierTail {
    fn find_token(&self, token: char) -> bool {
        token.is_alphanumeric() || "~!@$%^&*-=_+<>?/|≠≥≤·:".contains(token)
    }
}

fn parse_sign(input: &str) -> IResult<&str, bool> {
    map(
        opt(alt((value(true, char('-')), value(false, char('+'))))),
        |sign| sign.unwrap_or(false),
    )
    .parse(input)
}

fn parse_hex_digit(input: &str) -> IResult<&str, char> {
    let (tail, ch) = anychar(input)?;
    match ch {
        '0'..='9' | 'a'..='f' | 'A'..='F' => Ok((tail, ch)),
        _ if ch.is_alphabetic() => Err(nom::Err::Failure(Error::from_char(input, ch))),
        _ => Err(nom::Err::Error(Error::from_char(input, ch))),
    }
}

fn parse_dec_digit(input: &str) -> IResult<&str, char> {
    let (tail, ch) = anychar(input)?;
    match ch {
        '0'..='9' => Ok((tail, ch)),
        _ if ch.is_alphabetic() => Err(nom::Err::Failure(Error::from_char(input, ch))),
        _ => Err(nom::Err::Error(Error::from_char(input, ch))),
    }
}

fn parse_oct_digit(input: &str) -> IResult<&str, char> {
    let (tail, ch) = anychar(input)?;
    match ch {
        '0'..='7' => Ok((tail, ch)),
        _ if ch.is_alphanumeric() => Err(nom::Err::Failure(Error::from_char(input, ch))),
        _ => Err(nom::Err::Error(Error::from_char(input, ch))),
    }
}

struct OverflowError;

fn parse_integer_inner<I, E, P, D>(input: I, prefix: P, radix: u64, digit: D) -> IResult<I, u64, E>
where
    I: Input,
    E: ParseError<I>,
    E: FromExternalError<I, OverflowError>,
    P: Parser<I, Error = E>,
    D: Parser<I, Output = char, Error = E>,
{
    #[coverage(off)]
    fn digit_to_u8(ch: char) -> u8 {
        match ch {
            '0'..='9' => ch as u8 - b'0',
            'a'..='f' => ch as u8 - b'a' + 10,
            'A'..='F' => ch as u8 - b'A' + 10,
            _ => unreachable!(),
        }
    }

    let (output, result) = preceded(
        prefix,
        fold_many1(
            digit,
            || Ok(0u64),
            move |acc, ch| match acc {
                Ok(acc)
                    if let Some(acc) = acc
                        .checked_mul(radix)
                        .and_then(|acc| acc.checked_add(digit_to_u8(ch) as u64)) =>
                {
                    Ok(acc)
                }
                Ok(_) => Err(OverflowError),
                Err(e) => Err(e),
            },
        ),
    )
    .parse(input.clone())?;

    match result {
        Ok(result) => Ok((output, result)),
        Err(OverflowError) => Err(nom::Err::Failure(E::from_external_error(
            input,
            ErrorKind::Fold,
            OverflowError,
        ))),
    }
}

fn check_decimal(input: &str) -> IResult<&str, ()> {
    let (_, ch) = anychar(input)?;
    if ch.is_ascii_digit() {
        Ok((input, ()))
    } else {
        Err(nom::Err::Error(Error::from_char(input, ch)))
    }
}

fn parse_integer_oct(input: &str) -> IResult<&str, u64> {
    parse_integer_inner(input, alt((tag("0o"), tag("0O"))), 8, parse_oct_digit)
}

fn parse_integer_dec(input: &str) -> IResult<&str, u64> {
    parse_integer_inner(input, check_decimal, 10, parse_dec_digit)
}

fn parse_integer_hex(input: &str) -> IResult<&str, u64> {
    parse_integer_inner(input, alt((tag("0x"), tag("0X"))), 16, parse_hex_digit)
}

fn parse_integer(input: &str) -> IResult<&str, Value> {
    map_res(
        (
            parse_sign,
            alt((parse_integer_hex, parse_integer_oct, parse_integer_dec)),
        ),
        |(minus, value)| {
            i64::try_from(value)
                .map(|value| Value::Number((if minus { -value } else { value }).into()))
        },
    )
    .parse(input)
}

fn parse_number(input: &str) -> IResult<&str, Value> {
    alt((
        value(Value::Number(NumberValue::nan()), tag("#NAN")),
        value(Value::Number(NumberValue::nan()), tag("#NaN")),
        value(Value::Number(NumberValue::nan()), tag("#nan")),
        value(Value::Number(NumberValue::infinity()), tag("#inf")),
        value(Value::Number(NumberValue::infinity()), tag("#INF")),
        value(Value::Number(NumberValue::neg_infinity()), tag("-#inf")),
        value(Value::Number(NumberValue::neg_infinity()), tag("-#INF")),
        parse_integer,
    ))
    .parse(input)
}

fn parse_identifier(input: &str) -> IResult<&str, &str> {
    recognize(preceded(
        one_of(IdentifierHead),
        many0(one_of(IdentifierTail)),
    ))
    .parse(input)
}

fn parse_identifier_or_keyword_or_nil(input: &str) -> IResult<&str, Value> {
    map(parse_identifier, |ident| match ident {
        "NIL" | "nil" => Value::Nil,
        _ if let Some(keyword) = Keyword::parse(ident) => Value::Keyword(keyword),
        _ => Value::Identifier(ident.into()),
    })
    .parse(input)
}

fn parse_list_or_nil(input: &str) -> IResult<&str, Value> {
    let list_parens = delimited(
        char('('),
        many0(preceded(skip_comment_and_whitespace, parse_value)),
        preceded(skip_comment_and_whitespace, char(')')),
    );
    let list_brackets = delimited(
        char('['),
        many0(preceded(skip_comment_and_whitespace, parse_value)),
        preceded(skip_comment_and_whitespace, char(']')),
    );

    alt((list_parens, list_brackets))
        .map(Value::list_or_nil)
        .parse(input)
}

fn parse_boolean(input: &str) -> IResult<&str, Value> {
    alt((
        value(true, tag("#t")),
        value(true, tag("#T")),
        value(false, tag("#f")),
        value(false, tag("#F")),
    ))
    .map(Into::into)
    .parse(input)
}

enum StringSegment<'a> {
    Literal(&'a str),
    Escape(char),
}

fn parse_literal_string_segment(input: &str) -> IResult<&str, &str> {
    verify(is_not("\"\\"), |s: &str| !s.is_empty()).parse(input)
}

fn parse_escaped_char(input: &str) -> IResult<&str, char> {
    preceded(
        char('\\'),
        alt((
            value('\n', char('n')),
            value('\r', char('r')),
            value('\t', char('t')),
            value('\\', char('\\')),
            value('"', char('"')),
        )),
    )
    .parse(input)
}

fn parse_string_segment(input: &str) -> IResult<&str, StringSegment<'_>> {
    alt((
        map(parse_literal_string_segment, StringSegment::Literal),
        map(parse_escaped_char, StringSegment::Escape),
    ))
    .parse(input)
}

fn parse_string(input: &str) -> IResult<&str, Value> {
    delimited(
        char('"'),
        fold(
            0..,
            parse_string_segment,
            String::new,
            |mut string, segment| {
                match segment {
                    StringSegment::Literal(s) => string.push_str(s),
                    StringSegment::Escape(c) => string.push(c),
                };
                string
            },
        ),
        char('"'),
    )
    .map(|s| Value::String(s.into()))
    .parse(input)
}

pub fn skip_comment_and_whitespace(mut input: &str) -> IResult<&str, ()> {
    let mut in_comment = false;
    while let Some(ch) = input.chars().next() {
        let next = input.ceil_char_boundary(1);
        if next >= input.len() {
            break;
        }

        if ch == ';' {
            in_comment = true;
        }

        if in_comment {
            if ch.is_newline() {
                in_comment = false;
            }

            input = &input[next..];
            continue;
        } else if ch.is_whitespace() || ch.is_newline() {
            input = &input[next..];
            continue;
        }

        break;
    }
    input = input.trim_start();
    Ok((input, ()))
}

fn parse_quasi(input: &str) -> IResult<&str, Value> {
    preceded(char('`'), parse_value)
        .map(Rc::new)
        .map(Value::Quasi)
        .parse(input)
}

fn parse_unquote(input: &str) -> IResult<&str, Value> {
    preceded(char(','), parse_value)
        .map(Rc::new)
        .map(Value::Unquote)
        .parse(input)
}
fn parse_unquote_splice(input: &str) -> IResult<&str, Value> {
    preceded(tag(",@"), parse_value)
        .map(Rc::new)
        .map(Value::UnquoteSplice)
        .parse(input)
}

fn parse_quote(input: &str) -> IResult<&str, Value> {
    preceded(char('\''), parse_value)
        .map(Rc::new)
        .map(Value::Quote)
        .parse(input)
}

fn parse_vector(input: &str) -> IResult<&str, Value> {
    delimited(
        tag("#["),
        many0(preceded(skip_comment_and_whitespace, parse_value)),
        preceded(skip_comment_and_whitespace, char(']')),
    )
    .map(Vector::from_iter)
    .map(Into::into)
    .map(Value::Vector)
    .parse(input)
}

pub fn parse_value(input: &str) -> IResult<&str, Value> {
    alt((
        parse_list_or_nil,
        parse_vector,
        parse_boolean,
        parse_unquote_splice,
        parse_quote,
        parse_quasi,
        parse_unquote,
        parse_number,
        parse_string,
        parse_identifier_or_keyword_or_nil,
    ))
    .parse(input)
}

#[cfg(test)]
mod tests {
    use nom::error::{Error, FromExternalError, ParseError};

    use crate::{
        parse::{
            OverflowError, parse_boolean, parse_identifier, parse_identifier_or_keyword_or_nil,
            parse_integer, parse_integer_dec, parse_integer_hex, parse_integer_oct,
            parse_list_or_nil, parse_value,
        },
        vm::value::{Keyword, Value},
    };

    #[test]
    fn test_integer_dec() {
        let (r, v) = parse_integer_dec("1234\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, 1234);
        let (r, v) = parse_integer_dec("18446744073709551615\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, u64::MAX);

        // illegal digit
        let e = parse_integer_dec("1234A").unwrap_err();
        assert_eq!(e, nom::Err::Failure(Error::from_char("A", 'A')));

        // overflow
        let e = parse_integer_dec("9999999999999999999999999").unwrap_err();
        assert_eq!(
            e,
            nom::Err::Failure(Error::from_external_error(
                "9999999999999999999999999",
                nom::error::ErrorKind::Fold,
                OverflowError
            ))
        );
    }

    #[test]
    fn test_integer_oct() {
        let (r, v) = parse_integer_oct("0o1234\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, 0o1234);
        let (r, v) = parse_integer_oct("0O1234\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, 0o1234);

        // illegal digit
        let e = parse_integer_oct("0o12349").unwrap_err();
        assert_eq!(e, nom::Err::Failure(Error::from_char("9", '9')));
    }

    #[test]
    fn test_integer_hex() {
        let (r, v) = parse_integer_hex("0x123Fa4\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, 0x123FA4);
        let (r, v) = parse_integer_hex("0X123Fa4\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, 0x123FA4);

        // illegal digit
        let e = parse_integer_hex("0x1AG").unwrap_err();
        assert_eq!(e, nom::Err::Failure(Error::from_char("G", 'G')));
    }

    #[test]
    fn test_integer() {
        // Dec path
        let (r, v) = parse_integer("1234\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Number(1234.into()));
        let (r, v) = parse_integer("+1234\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Number(1234.into()));
        let (r, v) = parse_integer("-1234\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Number((-1234).into()));
        // Oct path
        let (r, v) = parse_integer("0o1234\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Number(0o1234.into()));
        let (r, v) = parse_integer("+0O1234\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Number(0o1234.into()));
        let (r, v) = parse_integer("-0o1234\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Number((-0o1234).into()));
        // Hex path
        let (r, v) = parse_integer("0x1234AF\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Number(0x1234AF.into()));
        let (r, v) = parse_integer("+0X1234AF\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Number(0x1234AF.into()));
        let (r, v) = parse_integer("-0x1234AF\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Number((-0x1234AF).into()));

        // Illegal path
        let e = parse_integer("0x12V34").unwrap_err();
        assert_eq!(e, nom::Err::Failure(Error::from_char("V34", 'V')));
        let e = parse_integer("-0X12V34").unwrap_err();
        assert_eq!(e, nom::Err::Failure(Error::from_char("V34", 'V')));
        let e = parse_integer("-0o81").unwrap_err();
        assert_eq!(e, nom::Err::Failure(Error::from_char("81", '8')));
    }

    #[test]
    fn test_identifier() {
        let (r, v) = parse_identifier("+\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, "+");
        let (r, v) = parse_identifier("a-1\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, "a-1");
    }

    #[test]
    fn test_identifier_or_keyword_or_nil() {
        let (r, v) = parse_identifier_or_keyword_or_nil("+\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Identifier("+".into()));
        let (r, v) = parse_identifier_or_keyword_or_nil("lambda\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Keyword(Keyword::Lambda));
        let (r, v) = parse_identifier_or_keyword_or_nil("NIL\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Nil);
    }

    #[test]
    fn test_list_or_nil() {
        let (r, v) = parse_list_or_nil("()\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Nil);
        let (r, v) = parse_list_or_nil("(   )\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Nil);
        let (r, v) = parse_list_or_nil("( a   b -1 )\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(
            v,
            Value::list_or_nil([
                Value::Identifier("a".into()),
                Value::Identifier("b".into()),
                Value::Number((-1).into()),
            ])
        );
    }

    #[test]
    fn test_boolean() {
        let (r, v) = parse_boolean("#t\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Boolean(true.into()));
        let (r, v) = parse_boolean("#T\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Boolean(true.into()));
        let (r, v) = parse_boolean("#F\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Boolean(false.into()));
        let (r, v) = parse_boolean("#F\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Boolean(false.into()));
    }

    #[test]
    fn test_value() {
        let (r, v) = parse_value("+123\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Number(123.into()));
        let (r, v) = parse_value("-0x123\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Number((-0x123).into()));
        let (r, v) = parse_value("abcdef-ghijkl\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Identifier("abcdef-ghijkl".into()));
        let (r, v) = parse_value("+\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Identifier("+".into()));
        let (r, v) = parse_value("+x\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Identifier("+x".into()));
        let (r, v) = parse_value("lambda-\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Identifier("lambda-".into()));
        let (r, v) = parse_value("lambda\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(v, Value::Keyword(Keyword::Lambda));
        let (r, v) = parse_value("(f #T -0x1)\n").unwrap();
        assert_eq!(r, "\n");
        assert_eq!(
            v,
            Value::list_or_nil([
                Value::Identifier("f".into()),
                Value::Boolean(true.into()),
                Value::Number((-0x1).into())
            ])
        );
    }
}
