Documentation
use nom::branch::alt;
use nom::bytes::complete::{tag, tag_no_case, take_while1};
use nom::character::complete::{
    alphanumeric1, digit0, digit1, i32, multispace0, one_of, satisfy, space0, u32,
};
use nom::combinator::{map, not, opt, recognize};
use nom::multi::many0;
use nom::sequence::{delimited, pair};
use nom::{AsChar, IResult, Parser};

use super::error::DbcParseError;

pub fn spacey<I, O, E>(
    f: impl Parser<I, Output = O, Error = E>,
) -> impl Parser<I, Output = O, Error = E>
where
    I: nom::Input,
    <I as nom::Input>::Item: AsChar + Clone,
    E: nom::error::ParseError<I>,
{
    delimited(space0, f, space0)
}

pub fn multispacey<I, O, E>(
    f: impl Parser<I, Output = O, Error = E>,
) -> impl Parser<I, Output = O, Error = E>
where
    I: nom::Input,
    <I as nom::Input>::Item: AsChar + Clone,
    E: nom::error::ParseError<I>,
{
    delimited(multispace0, f, multispace0)
}

pub fn c_identifier(input: &str) -> IResult<&str, &str, DbcParseError> {
    recognize((
        alt((tag("_"), recognize(satisfy(AsChar::is_alpha)))),
        opt(recognize(many0(alt((tag("_"), alphanumeric1))))),
    ))
    .parse(input)
}

pub fn digit1to9(input: &str) -> IResult<&str, char, DbcParseError> {
    one_of("123456789").parse(input)
}

pub fn uint(input: &str) -> IResult<&str, &str, DbcParseError> {
    alt((tag("0"), recognize(pair(digit1to9, digit0)))).parse(input)
}

pub fn integer_body(input: &str) -> IResult<&str, &str, DbcParseError> {
    recognize(pair(opt(tag("-")), uint)).parse(input)
}

pub fn integer_value(input: &str) -> IResult<&str, i64, DbcParseError> {
    let (remain, raw_int) = integer_body(input)?;
    match raw_int.parse::<i64>() {
        Ok(i) => Ok((remain, i)),
        Err(_) => Err(nom::Err::Error(DbcParseError::BadInt)),
    }
}

pub fn frac(input: &str) -> IResult<&str, &str, DbcParseError> {
    recognize(pair(tag("."), digit1)).parse(input)
}

pub fn exp(input: &str) -> IResult<&str, &str, DbcParseError> {
    recognize((tag_no_case("e"), opt(alt((tag("-"), tag("+")))), digit1)).parse(input)
}

pub fn float_body(input: &str) -> IResult<&str, &str, DbcParseError> {
    recognize((
        opt(tag("-")),
        uint,
        alt((recognize(pair(frac, opt(exp))), exp)),
    ))
    .parse(input)
}

pub fn float_value(input: &str) -> IResult<&str, f64, DbcParseError> {
    let (remain, raw_float) = float_body(input)?;
    match raw_float.parse::<f64>() {
        Ok(f) => Ok((remain, f)),
        Err(_) => Err(nom::Err::Error(DbcParseError::BadFloat)),
    }
}

pub fn number_value(input: &str) -> IResult<&str, f64, DbcParseError> {
    alt((map(float_value, |f| f), map(integer_value, |i| i as f64))).parse(input)
}

pub fn unsigned_integer(input: &str) -> IResult<&str, u32, DbcParseError> {
    u32.parse(input)
}

pub fn signed_integer(input: &str) -> IResult<&str, i32, DbcParseError> {
    i32.parse(input)
}

pub fn dbc_key_word(input: &str) -> IResult<&str, &str, DbcParseError> {
    alt((
        alt((
            tag("VERSION"),
            tag("NS_"),
            tag("NS_DESC_"),
            tag("CM_"),
            tag("BA_DEF_"),
            tag("BA_"),
            tag("VAL_"),
            tag("CAT_DEF_"),
            tag("CAT_"),
            tag("FILTER"),
        )),
        alt((
            tag("BA_DEF_DEF_"),
            tag("EV_DATA_"),
            tag("ENVVAR_DATA_"),
            tag("SGTYPE_"),
            tag("SGTYPE_VAL_"),
            tag("BA_DEF_SGTYPE_"),
            tag("BA_SGTYPE_"),
            tag("SIG_TYPE_REF_"),
            tag("VAL_TABLE_"),
            tag("SIG_GROUP_"),
        )),
        alt((
            tag("SIG_VALTYPE_"),
            tag("SIGTYPE_VALTYPE_"),
            tag("BO_TX_BU_"),
            tag("BA_DEF_REL_"),
            tag("BA_REL_"),
            tag("BA_DEF_DEF_REL_"),
            tag("BU_SG_REL_"),
            tag("BU_EV_REL_"),
            tag("BU_BO_REL_"),
            tag("SG_MUL_VAL_"),
        )),
        alt((
            tag("BS_"),
            tag("BU_"),
            tag("BO_"),
            tag("SG_"),
            tag("EV_"),
            tag("VECTOR__INDEPENDENT_SIG_MSG"),
            tag("VECTOR__XXX"),
        )),
    ))
    .parse(input)
}

pub fn dbc_object_name(input: &str) -> IResult<&str, &str, DbcParseError> {
    take_while1(|c: char| c.is_alphanumeric() || c == '_').parse(input)
}

pub fn dbc_identifier_01(input: &str) -> IResult<&str, &str, DbcParseError> {
    recognize((
        alt((tag("_"), recognize(satisfy(AsChar::is_alpha)))),
        opt(recognize(many0(alt((tag("_"), tag("-"), alphanumeric1))))),
    ))
    .parse(input)
}

pub fn dbc_identifier(input: &str) -> IResult<&str, &str, DbcParseError> {
    let res = not(dbc_key_word).parse(input);
    match res {
        Ok((remain, ())) => dbc_identifier_01(remain),
        Err(_) => Err(nom::Err::Error(DbcParseError::UseKeywordAsIdentifier)),
    }
}

pub fn parser_node_name(input: &str) -> IResult<&str, &str, DbcParseError> {
    dbc_identifier(input)
}

pub fn parser_message_id(input: &str) -> IResult<&str, u32, DbcParseError> {
    unsigned_integer(input)
}

pub fn parser_signal_name(input: &str) -> IResult<&str, &str, DbcParseError> {
    dbc_identifier(input)
}

pub fn parser_env_var_name(input: &str) -> IResult<&str, &str, DbcParseError> {
    dbc_identifier(input)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_float_value_01() {
        assert_eq!(float_value("0.0"), Ok(("", 0.0)));
    }

    #[test]
    fn test_float_value_02() {
        assert_eq!(float_value("1.52588e-05"), Ok(("", 1.52588e-05)));
    }

    #[test]
    fn test_float_value_03() {
        assert_eq!(float_value("1.52588E-05"), Ok(("", 1.52588e-05)));
    }

    #[test]
    fn test_c_identifier_01() {
        assert_eq!(c_identifier("a"), Ok(("", "a")));
    }

    #[test]
    fn test_c_identifier_02() {
        assert_eq!(c_identifier("abc"), Ok(("", "abc")));
    }

    #[test]
    fn test_c_identifier_03() {
        assert_eq!(c_identifier("hello_world"), Ok(("", "hello_world")));
    }

    #[test]
    fn test_c_identifier_04() {
        assert_eq!(c_identifier("_hello_world"), Ok(("", "_hello_world")));
    }

    #[test]
    fn test_c_identifier_05() {
        assert_eq!(
            c_identifier("_hello_world_123zzz"),
            Ok(("", "_hello_world_123zzz"))
        );
    }

    #[test]
    fn test_c_identifier_06() {
        assert_eq!(c_identifier("BigData"), Ok(("", "BigData")));
    }

    #[test]
    fn test_c_identifier_07() {
        assert_eq!(c_identifier("Big-Data"), Ok(("-Data", "Big")));
    }

    #[test]
    fn test_dbc_identifier_01() {
        assert_eq!(dbc_identifier("a"), Ok(("", "a")));
    }

    #[test]
    fn test_dbc_identifier_02() {
        assert_eq!(
            dbc_identifier("BS_"),
            Err(nom::Err::Error(DbcParseError::UseKeywordAsIdentifier))
        );
    }

    #[test]
    fn test_dbc_identifier_03() {
        assert_eq!(dbc_identifier("hello_world"), Ok(("", "hello_world")));
    }

    #[test]
    fn test_dbc_identifier_04() {
        assert_eq!(dbc_identifier("_HelloWorld"), Ok(("", "_HelloWorld")));
    }
}