use crate::prelude::*;
use crate::{
    stringify::{ParseError, ParseErrorKind},
    Value,
};
pub fn parse_hex(s: &mut &str) -> Option<Result<Value<()>, ParseError>> {
    if !s.starts_with("0x") {
        return None;
    }
    let bytes = s.as_bytes();
    let mut composite_values = vec![];
    let mut idx = 2;
    let mut last_nibble = None;
    loop {
        let Some(b) = bytes.get(idx) else {
            break;
        };
        if !b.is_ascii_alphanumeric() {
            break;
        }
        let hex_nibble = match *b {
            b'A'..=b'F' => b - b'A' + 10,
            b'a'..=b'f' => b - b'a' + 10,
            b'0'..=b'9' => b - b'0',
            b => {
                return Some(Err(
                    ParseErrorKind::custom(ParseHexError::InvalidChar(b as char)).at(idx)
                ))
            }
        };
        match last_nibble {
            None => {
                last_nibble = Some(hex_nibble)
            }
            Some(n) => {
                let byte = n * 16 + hex_nibble;
                composite_values.push(Value::u128(byte as u128));
                last_nibble = None;
            }
        }
        idx += 1;
    }
    if last_nibble.is_some() {
        return Some(Err(ParseErrorKind::custom(ParseHexError::WrongLength).between(0, idx)));
    }
    *s = unsafe { core::str::from_utf8_unchecked(&bytes[idx..]) };
    Some(Ok(Value::unnamed_composite(composite_values)))
}
#[derive(Debug, PartialEq, Clone, derive_more::Display)]
#[allow(missing_docs)]
pub enum ParseHexError {
    #[display(fmt = "Invalid hex character: {_0}")]
    InvalidChar(char),
    #[display(fmt = "Hex string is the wrong length; should be an even length")]
    WrongLength,
}
#[cfg(feature = "std")]
impl std::error::Error for ParseHexError {}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn parses_same_as_hex_crate() {
        let expects = ["0x", "0x00", "0x000102030405060708090A0B", "0xDEADBEEF", "0x00BAB10C"];
        for input in expects {
            let expected_hex = hex::decode(input.trim_start_matches("0x")).expect("valid hex");
            let cursor = &mut &*input;
            let hex = parse_hex(cursor).expect("valid hex expected").expect("no error expected");
            assert_eq!(hex, Value::from_bytes(expected_hex), "values should match");
        }
    }
    #[test]
    fn consumes_parsed_hex() {
        let expects =
            [("0x foo", " foo"), ("0x00,bar", ",bar"), ("0x123456-2", "-2"), ("0xDEADBEEF ", " ")];
        for (input, expected_remaining) in expects {
            let cursor = &mut &*input;
            let _ = parse_hex(cursor).expect("valid hex expected").expect("no error expected");
            assert_eq!(*cursor, expected_remaining);
        }
    }
    #[test]
    fn err_wrong_length() {
        let expects = ["0x1", "0x123"];
        for input in expects {
            let cursor = &mut &*input;
            let err =
                parse_hex(cursor).expect("some result expected").expect_err("an error is expected");
            assert_eq!(err.start_loc, 0);
            assert_eq!(err.end_loc, Some(input.len()));
            let ParseErrorKind::Custom(err) = err.err else { panic!("expected custom error") };
            assert_eq!(err, ParseHexError::WrongLength.to_string());
            assert_eq!(input, *cursor);
        }
    }
    #[test]
    fn err_invalid_char() {
        let expects = [("0x12345x", 'x', 7), ("0x123h4", 'h', 5), ("0xG23h4", 'G', 2)];
        for (input, bad_char, pos) in expects {
            let cursor = &mut &*input;
            let err =
                parse_hex(cursor).expect("some result expected").expect_err("an error is expected");
            assert_eq!(err.start_loc, pos);
            assert!(err.end_loc.is_none());
            let ParseErrorKind::Custom(err) = err.err else { panic!("expected custom error") };
            assert_eq!(err, ParseHexError::InvalidChar(bad_char).to_string());
            assert_eq!(input, *cursor);
        }
    }
    #[test]
    fn empty_string_doesnt_panic() {
        assert!(parse_hex(&mut "").is_none());
        assert!(parse_hex(&mut "0").is_none());
    }
}