use std::hint::black_box;

use criterion::Criterion;
use neure::prelude::*;

#[derive(Debug, PartialEq)]
pub struct Color {
    pub red: u8,
    pub green: u8,
    pub blue: u8,
}

fn bench_color(c: &mut Criterion) {
    let color_str = "#2F14DF";

    c.bench_function("color of nom", {
        move |b| {
            b.iter(|| {
                color_nom::parse(black_box(color_str));
                black_box(())
            })
        }
    });

    c.bench_function("color of neure", {
        move |b| {
            b.iter(|| {
                color_neure::parse(black_box(color_str));
                black_box(())
            })
        }
    });
}

criterion::criterion_group!(
    name = benches;
    config = Criterion::default().configure_from_args();
    targets = bench_color
);

criterion::criterion_main!(benches);

mod color_nom {
    use super::*;
    use nom::{
        IResult, Parser,
        bytes::complete::{tag, take_while_m_n},
        combinator::map_res,
    };

    fn from_hex(input: &str) -> Result<u8, std::num::ParseIntError> {
        u8::from_str_radix(input, 16)
    }

    fn is_hex_digit(c: char) -> bool {
        ('0'..':').contains(&c) || ('A'..'G').contains(&c)
    }

    fn hex_primary(input: &str) -> IResult<&str, u8> {
        map_res(take_while_m_n(2, 2, is_hex_digit), from_hex).parse(input)
    }

    fn hex_color(input: &str) -> IResult<&str, Color> {
        let (input, _) = tag("#")(input)?;
        let (input, (red, green, blue)) = (hex_primary, hex_primary, hex_primary).parse(input)?;

        Ok((input, Color { red, green, blue }))
    }

    pub fn parse(str: &str) {
        let (_, color) = hex_color(str).unwrap();

        assert_eq!(
            color,
            Color {
                red: 47,
                green: 20,
                blue: 223,
            }
        );
    }
}

mod color_neure {
    use super::*;

    fn is_hex_digit(c: &char) -> bool {
        ('0'..':').contains(c) || ('A'..'G').contains(c)
    }

    fn parser(str: &str) -> Result<Color, neure::err::Error> {
        let hex = is_hex_digit.count::<2>(); // better performance than ..=
        let hex = hex.try_map(map::from_str_radix::<u8>(16));
        let parser = hex
            .then(hex.then(hex))
            .map(|(red, (green, blue))| Color { red, green, blue });
        let parser = parser.prefix("#");

        RegexCtx::new(str).ctor(&parser)
    }

    pub fn parse(str: &str) {
        let color = parser(str).unwrap();

        assert_eq!(
            color,
            Color {
                red: 47,
                green: 20,
                blue: 223,
            }
        );
    }
}
