#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;

use criterion::*;

use nom::{
  bytes::complete::{is_a, tag, take_till, take_while},
  character::complete::{alphanumeric1 as alphanumeric, char, not_line_ending, space0 as space},
  combinator::opt,
  multi::many,
  sequence::{delimited, pair, terminated, tuple},
  IResult, Parser,
};

use std::collections::HashMap;

fn is_line_ending_or_comment(chr: char) -> bool {
  chr == ';' || chr == '\n'
}

fn space_or_line_ending(i: &str) -> IResult<&str, &str> {
  is_a(" \r\n")(i)
}

fn category(i: &str) -> IResult<&str, &str> {
  terminated(
    delimited(char('['), take_while(|c| c != ']'), char(']')),
    opt(is_a(" \r\n")),
  )
  .parse(i)
}

fn key_value(i: &str) -> IResult<&str, (&str, &str)> {
  let (i, key) = alphanumeric(i)?;
  let (i, _) = tuple((opt(space), tag("="), opt(space)))(i)?;
  let (i, val) = take_till(is_line_ending_or_comment)(i)?;
  let (i, _) = opt(space).parse_complete(i)?;
  let (i, _) = opt(pair(tag(";"), not_line_ending)).parse_complete(i)?;
  let (i, _) = opt(space_or_line_ending).parse_complete(i)?;
  Ok((i, (key, val)))
}

fn keys_and_values(input: &str) -> IResult<&str, HashMap<&str, &str>> {
  many(0.., key_value).parse_complete(input)
}

fn category_and_keys(i: &str) -> IResult<&str, (&str, HashMap<&str, &str>)> {
  pair(category, keys_and_values).parse_complete(i)
}

fn categories(input: &str) -> IResult<&str, HashMap<&str, HashMap<&str, &str>>> {
  many(0.., category_and_keys).parse_complete(input)
}

fn bench_ini_str(c: &mut Criterion) {
  let s = "[owner]
name=John Doe
organization=Acme Widgets Inc.

[database]
server=192.0.2.62
port=143
file=payroll.dat
";

  let mut group = c.benchmark_group("ini str");
  group.throughput(Throughput::Bytes(s.len() as u64));
  group.bench_function(BenchmarkId::new("parse", s.len()), |b| {
    b.iter(|| categories(s).unwrap())
  });
}

criterion_group!(benches, bench_ini_str);
criterion_main!(benches);
