use nom::branch::alt;
use nom::bytes::complete::{tag, take, take_until, take_while_m_n};
use nom::character::char;
use nom::character::complete::hex_digit1;
use nom::combinator::{map_parser, map_res, success, value, verify};
use nom::error::{Error, ErrorKind, make_error};
use nom::multi::{fold_many0, many0_count};
use nom::number::complete::hex_u32;
use nom::sequence::delimited;
use nom::{AsChar, Finish, IResult, Parser};
use std::time::Duration;
use tinyvec::ArrayVec;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CommsDirection {
    Rx,
    Tx,
}

#[derive(Debug, Clone)]
pub struct CanMessage {
    pub timestamp: Duration,
    pub direction: CommsDirection,
    pub can_id: u32,
    pub data: ArrayVec<[u8; 8]>,
}

/// Parse a CAN log file as produced by the Microchip CAN Bus Analyzer Tool used with the APGDT002.
///
/// # Note:
/// In longer log files, the timestamps overflow.
/// If this occurs, this parser will try to correct the timestamps as best it can,
/// but it's not 100% accurate, because the exact value where the overflow occurs is not known.
///
/// Example:
/// ```
/// let input =
/// r#"//---------------------------------
/// Microchip Technology Inc.
/// CAN BUS Analyzer
/// Released August 22nd 2014 - BETA
///
/// PC Software Information:
///      GUI Version: 2.2
/// Firmware Information:
///      USB Version: 2.0
///      CAN Version: 2.2
///
/// Logging Started: 16/02/2026 18:32:20
/// //---------------------------------
/// 1075.5273;RX;0x777;2;0x00;0x00;;;;;;;
/// 1075.5282;RX;0x679;8;0x00;0x00;0x00;0x00;0x08;0x79;0x00;0x00;"#;
///
/// let parsed = apgdt002_log_parser::parse(input).expect("valid can log file");
///
/// assert_eq!(parsed.len(), 2);
///
/// assert_eq!(parsed[0].can_id, 0x777);
/// assert_eq!(parsed[0].data.len(), 2);
/// assert_eq!(parsed[0].data[0], 0);
///
/// assert_eq!(parsed[1].data.len(), 8);
/// assert_eq!(parsed[1].data[4], 0x08);
/// ```
pub fn parse(i: &str) -> Result<Vec<CanMessage>, Error<&str>> {
    let upper_bound_of_num_msgs_estimate = i
        .len()
        .div_euclid(const { "1.0000;RX;0x000;0;;;;;;;;;".len() + 1 });
    (
        discard_many_multiline_comments,
        fold_many0(
            alt((can_msg, discard_garbage_can_msg)),
            || Vec::with_capacity(upper_bound_of_num_msgs_estimate),
            |mut acc, msg: Option<CanMessage>| {
                if let Some(msg) = msg {
                    acc.push(msg);
                }
                acc
            },
        ),
    )
        .parse(i)
        .finish()
        .map(|(_rest, ((), messages))| correct_overflowed_timestamps(messages.as_slice()))
}

fn correct_overflowed_timestamps(parsed_messages: &[CanMessage]) -> Vec<CanMessage> {
    let mut correction = Duration::from_secs(0);
    let tail = parsed_messages
        .iter()
        .zip(parsed_messages.iter().skip(1))
        .map(|(previous, correction_candidate)| {
            // is current ts earlier than previous ts? -> overflow
            if correction_candidate.timestamp < previous.timestamp {
                // record corrective amount
                correction += previous.timestamp;
            }
            // correct timestamp
            // will be zero before the first overflow so no change
            // but then will apply corrections
            let mut copy = correction_candidate.clone();
            copy.timestamp += correction;
            copy
        });
    let head = parsed_messages.iter().take(1).cloned();
    std::iter::chain(head, tail).collect()
}

fn can_msg(i: &str) -> IResult<&str, Option<CanMessage>> {
    // 1075.5273;RX;0x777;2;0x00;0x00;;;;;;;
    // 1075.5282;RX;0x679;8;0x00;0x00;0x00;0x00;0x08;0x79;0x00;0x00;
    let (i, timestamp) = timestamp(i)?;
    let (i, ()) = discard_semicolon(i)?;
    let (i, comms_direction) = parse_comms_direction(i)?;
    let (i, ()) = discard_semicolon(i)?;
    let (i, can_id) = hex_to_u32(i)?;
    let (i, ()) = discard_semicolon(i)?;
    let (i, data_length) = parse_length(i)?;
    let mut data = ArrayVec::<[u8; 8]>::new();
    let (mut i, ()) = discard_semicolon(i)?;
    for _ in 0..data_length {
        let (j, data_octet) = hex_to_octet(i)?;
        data.push(data_octet);
        let (j, ()) = discard_semicolon(j)?;
        i = j;
    }
    for _ in data_length..8 {
        let (j, ()) = discard_semicolon(i)?;
        i = j;
    }
    let (i, ()) = discard_optional_newline(i)?;

    Ok((
        i,
        Some(CanMessage {
            timestamp,
            direction: comms_direction,
            can_id,
            data,
        }),
    ))
}

fn discard_garbage_can_msg(i: &str) -> IResult<&str, Option<CanMessage>> {
    let (i, _) = take_until("\n").parse(i)?;
    value(None, alt((tag("\r\n"), tag("\n")))).parse(i)
}

fn discard_semicolon(i: &str) -> IResult<&str, ()> {
    value((), tag(";")).parse(i)
}

fn discard_many_multiline_comments(i: &str) -> IResult<&str, ()> {
    static COMMENT_MARKER: &str = "//---------------------------------";
    value(
        (),
        many0_count(delimited(
            (tag(COMMENT_MARKER), discard_optional_newline),
            take_until(COMMENT_MARKER),
            (tag(COMMENT_MARKER), discard_optional_newline),
        )),
    )
    .parse(i)
}

fn discard_optional_newline(i: &str) -> IResult<&str, ()> {
    value((), alt((tag("\r\n"), tag("\n"), success("")))).parse(i)
}

fn decimal_1_4(i: &str) -> IResult<&str, u32> {
    map_parser(
        take_while_m_n(1, 4, AsChar::is_dec_digit),
        nom::character::complete::u32,
    )
    .parse(i)
}

fn timestamp(i: &str) -> IResult<&str, Duration> {
    (decimal_1_4, char('.'), decimal_1_4).parse(i).map(
        |(rest, (seconds, _dot, ten_thousands_of_a_second))| {
            let millis = ten_thousands_of_a_second.div_euclid(10);
            let seconds_as_millis = seconds.saturating_mul(1_000);
            (
                rest,
                Duration::from_millis(seconds_as_millis.saturating_add(millis).into()),
            )
        },
    )
}

fn parse_comms_direction(i: &str) -> IResult<&str, CommsDirection> {
    alt((
        value(CommsDirection::Rx, tag("RX")),
        value(CommsDirection::Tx, tag("TX")),
    ))
    .parse(i)
}

fn hex_to_u32(i: &str) -> IResult<&str, u32> {
    let (rest, _discarded_hex_prefix) = tag("0x").parse(i)?;
    map_parser(hex_digit1, hex_u32).parse(rest)
}

fn hex_to_octet(i: &str) -> IResult<&str, u8> {
    map_res(hex_to_u32, |x| {
        u8::try_from(x).map_err(|_| make_error::<&str, Error<&str>>(i, ErrorKind::TooLarge))
    })
    .parse(i)
}

fn parse_length(i: &str) -> IResult<&str, u8> {
    verify(
        map_parser(take(1usize), nom::character::complete::u8),
        |len| *len > 0 && *len <= 8,
    )
    .parse(i)
}

#[cfg(test)]
mod test {
    use crate::{
        CanMessage, CommsDirection, correct_overflowed_timestamps, discard_many_multiline_comments,
        parse,
    };
    use std::time::Duration;
    use tinyvec::array_vec;

    static INPUT: &str = r#"//---------------------------------

        garbage
//---------------------------------
//---------------------------------
more garbage
//---------------------------------
1075.5273;RX;0x777;2;0x00;0x00;;;;;;;"#;

    #[test]
    fn parse_multiline_comments() {
        let res = discard_many_multiline_comments(INPUT);
        assert!(res.is_ok(), "{res:?}")
    }

    #[test]
    fn parse_file() {
        let res = parse(INPUT);
        assert!(res.is_ok(), "{res:?}");
        let res = res.unwrap();
        assert_eq!(res.len(), 1);
        let res = res.first().unwrap();
        assert_eq!(
            res.timestamp,
            Duration::from_secs(1075) + Duration::from_millis(527)
        );
        assert_eq!(res.direction, CommsDirection::Rx);
        assert_eq!(res.can_id, 0x777);
        assert_eq!(res.data.len(), 2);
        assert_eq!(*res.data.get(0).unwrap(), 0);
        assert_eq!(*res.data.get(1).unwrap(), 0);
    }

    #[test]
    fn overflow_correction() {
        let input = [
            CanMessage {
                timestamp: Duration::from_secs(100),
                can_id: 0,
                direction: CommsDirection::Rx,
                data: array_vec!(),
            },
            CanMessage {
                timestamp: Duration::from_secs(50),
                can_id: 0,
                direction: CommsDirection::Rx,
                data: array_vec!(),
            },
            CanMessage {
                timestamp: Duration::from_secs(100),
                can_id: 0,
                direction: CommsDirection::Rx,
                data: array_vec!(),
            },
        ];
        let corrected = correct_overflowed_timestamps(&input);
        assert_eq!(
            corrected.get(0).unwrap().timestamp,
            Duration::from_secs(100)
        );
        assert_eq!(
            corrected.get(1).unwrap().timestamp,
            Duration::from_secs(150)
        );
        assert_eq!(
            corrected.get(2).unwrap().timestamp,
            Duration::from_secs(200)
        );
    }
}
