use criterion::{criterion_group, criterion_main, Criterion};

mod my_map_mod {
    // use std::mem::MaybeUninit;

    enum MyMapState { S, E }

    struct MyMap<I: Iterator, R, F: FnMut(I::Item) -> R> {
        state: MyMapState,
        param_iter: I,
        param_f: F,
        // var_temp: ...
    }

    impl<I: Iterator, R, F: FnMut(I::Item) -> R> Iterator for MyMap<I, R, F> {
        type Item = R;

        fn size_hint(&self) -> (usize, Option<usize>) {
            self.iter.size_hint()
        }

        fn next(&mut self) -> Option<R> {
            loop {
                match self.state {
                    MyMapState::S => {
                        match self.param_iter.next() {
                            Some(x) => { return Some((self.param_f)(x)); }
                            None => { self.state = MyMapState::E; }
                        }
                    },
                    MyMapState::E => {
                        return None
                    }
                }
            }
        }
    }

    pub fn my_map<I: Iterator, R, F: FnMut(I::Item) -> R>(iter: I, f: F) -> impl Iterator<Item = R> {
        // let size_hint = { self.iter.size_hint() }
        // give_size_hint! { self.iter.size_hint() }
        MyMap { state: MyMapState::S, param_iter: iter, param_f: f }
    }
}

use my_map_mod::my_map;

#[giver]
fn my_map<I, F, R>(iter: I, f: F) -> R
where
    I: Iterator,
    F: FnMut(I::Item) -> R,
    R: Iterator<Item=I::Item>,
{
    for x in iter {
        give!(f(x))
    }
}

mod simp_map_mod {
    pub struct SimpMap<I: Iterator, R, F: FnMut(I::Item) -> R> {
        iter: I,
        f: F,
    }


    impl<I: Iterator, R, F: FnMut(I::Item) -> R> Iterator for SimpMap<I, R, F> {
        type Item = R;

        fn size_hint(&self) -> (usize, Option<usize>) {
            self.iter.size_hint()
        }

        fn next(&mut self) -> Option<R> {
            self.iter.next().map(&mut self.f)
        }
    }

    pub fn simp_map<I: Iterator, R, F: FnMut(I::Item) -> R>(iter: I, f: F) -> SimpMap<I, R, F> {
        SimpMap { iter, f }
    }
}

use simp_map_mod::simp_map;

fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("std map", |b| b.iter(||
        (1..1024).map(|x| x + 1).map(|x| (((x + 12) * 24) ^ 42)/x).collect::<Vec<_>>()
    ));

    c.bench_function("my map", |b| b.iter(||
        my_map(my_map(1..1024, |x| x + 1), |x| (((x + 12) * 24) ^ 42)/x).collect::<Vec<_>>()
    ));

    c.bench_function("simp map", |b| b.iter(||
        simp_map(simp_map(1..1024, |x| x + 1), |x| (((x + 12) * 24) ^ 42)/x).collect::<Vec<_>>()
    ));

    // c.bench_function("std map sum", |b| b.iter(||
    //     (1..1024).map(|x| x + 1).map(|x| x + 12 * 24 ^ 42).sum::<i64>()
    // ));

    // c.bench_function("my map sum", |b| b.iter(||
    //     my_map(my_map(1..1024, |x| x + 1), |x| x + 12 * 24 ^ 42).sum::<i64>()
    // ));

    // c.bench_function("simp map sum", |b| b.iter(||
    //     simp_map(simp_map(1..1024, |x| x + 1), |x| x + 12 * 24 ^ 42).sum::<i64>()
    // ));
}

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