#![feature(test)]

#[macro_use]
extern crate chan;
extern crate rand;
extern crate test;

use std::sync::Arc;
use std::sync::mpsc;
use std::thread;

use rand::Rng;
use test::Bencher;

fn get_data() -> Vec<u8> {
    rand::thread_rng().gen_iter().take(1000).collect()
}

fn get_data_sum<I: IntoIterator<Item=u8>>(xs: I) -> u64 {
    xs.into_iter().fold(0, |sum, x| sum + (x as u64))
}

macro_rules! bench_chan_spsc {
    ($name:ident, $cons:expr) => {
        #[bench]
        fn $name(b: &mut Bencher) {
            let data = Arc::new(get_data());
            let sum = get_data_sum(data.iter().cloned());
            b.bytes = data.len() as u64;
            b.iter(|| {
                let data = data.clone();
                let (s, r) = $cons;
                thread::spawn(move || {
                    for &datum in &*data {
                        let _ = s.send(datum);
                    }
                });
                assert_eq!(sum, get_data_sum(r.iter()));
            });
        }
    }
}

bench_chan_spsc!(spsc_chan_sync_unbuffered, chan::sync(0));
bench_chan_spsc!(spsc_chan_sync_buffered, chan::sync(1));
bench_chan_spsc!(spsc_chan_sync_buffered_all, chan::sync(1000));
bench_chan_spsc!(spsc_chan_async, chan::async());
bench_chan_spsc!(std_spsc_chan_sync_unbuffered, mpsc::sync_channel(0));
bench_chan_spsc!(std_spsc_chan_sync_buffered, mpsc::sync_channel(1));
bench_chan_spsc!(std_spsc_chan_sync_buffered_all, mpsc::sync_channel(1000));
bench_chan_spsc!(std_spsc_chan_async, mpsc::channel());

macro_rules! bench_chan_mpsc {
    ($name:ident, $cons:expr) => {
        #[bench]
        fn $name(b: &mut Bencher) {
            let data = Arc::new(get_data());
            let sum = get_data_sum(data.iter().cloned());
            b.bytes = data.len() as u64;
            b.iter(|| {
                let (s, r) = $cons;
                for i in 0..4 {
                    let data = data.clone();
                    let s = s.clone();
                    let start = i * (data.len() / 4);
                    let end = start + (data.len() / 4);
                    thread::spawn(move || {
                        for &datum in &(&*data)[start..end] {
                            let _ = s.send(datum);
                        }
                    });
                }
                drop(s);
                assert_eq!(sum, get_data_sum(r.iter()));
            });
        }
    }
}

bench_chan_mpsc!(mpsc_chan_sync_unbuffered, chan::sync(0));
bench_chan_mpsc!(mpsc_chan_sync_buffered, chan::sync(1));
bench_chan_mpsc!(mpsc_chan_sync_buffered_all, chan::sync(1000));
bench_chan_mpsc!(mpsc_chan_async, chan::async());
bench_chan_mpsc!(std_mpsc_chan_sync_unbuffered, mpsc::sync_channel(0));
bench_chan_mpsc!(std_mpsc_chan_sync_buffered, mpsc::sync_channel(1));
bench_chan_mpsc!(std_mpsc_chan_sync_buffered_all, mpsc::sync_channel(1000));
bench_chan_mpsc!(std_mpsc_chan_async, mpsc::channel());

macro_rules! bench_select_no_init {
    ($name:ident, $cons:expr) => {
        #[bench]
        fn $name(b: &mut Bencher) {
            let data = Arc::new(get_data());
            let sum = get_data_sum(data.iter().cloned());
            b.bytes = data.len() as u64;
            b.iter(|| {
                let mut rs = vec![];
                for i in 0..4 {
                    let (s, r) = $cons;
                    rs.push(r);
                    let data = data.clone();
                    let start = i * (data.len() / 4);
                    let end = start + (data.len() / 4);
                    thread::spawn(move || {
                        for &datum in &(&*data)[start..end] {
                            let _ = s.send(datum);
                        }
                    });
                }
                let mut recv = vec![];
                let (r0, r1, r2, r3) = (&rs[0], &rs[1], &rs[2], &rs[3]);
                let mut done = &mut [false, false, false, false];
                loop {
                    chan_select! {
                        r0.recv() -> d => {
                            if let Some(d) = d {
                                recv.push(d);
                            } else {
                                done[0] = true;
                            }
                        },
                        r1.recv() -> d => {
                            if let Some(d) = d {
                                recv.push(d);
                            } else {
                                done[1] = true;
                            }
                        },
                        r2.recv() -> d => {
                            if let Some(d) = d {
                                recv.push(d);
                            } else {
                                done[2] = true;
                            }
                        },
                        r3.recv() -> d => {
                            if let Some(d) = d {
                                recv.push(d);
                            } else {
                                done[3] = true;
                            }
                        },
                    }
                    if done.iter().all(|&b| b) {
                        break;
                    }
                }
                assert_eq!(sum, get_data_sum(recv.into_iter()));
            });
        }
    }
}

bench_select_no_init!(select_no_init_chan_sync_unbuffered, chan::sync(0));
bench_select_no_init!(select_no_init_chan_sync_buffered, chan::sync(1));
bench_select_no_init!(select_no_init_chan_sync_buffered_all, chan::sync(1000));
bench_select_no_init!(select_no_init_chan_async, chan::async());

macro_rules! bench_select_with_init {
    ($name:ident, $cons:expr) => {
        #[bench]
        fn $name(b: &mut Bencher) {
            let data = Arc::new(get_data());
            let sum = get_data_sum(data.iter().cloned());
            b.bytes = data.len() as u64;
            b.iter(|| {
                let mut rs = vec![];
                for i in 0..4 {
                    let (s, r) = $cons;
                    rs.push(r);
                    let data = data.clone();
                    let start = i * (data.len() / 4);
                    let end = start + (data.len() / 4);
                    thread::spawn(move || {
                        for &datum in &(&*data)[start..end] {
                            let _ = s.send(datum);
                        }
                    });
                }
                let mut recv = vec![];
                let (r0, r1, r2, r3) = (&rs[0], &rs[1], &rs[2], &rs[3]);
                let mut done = &mut [false, false, false, false];
                let mut sel = chan::Select::new();
                loop {
                    chan_select! {sel,
                        r0.recv() -> d => {
                            if let Some(d) = d {
                                recv.push(d);
                            } else {
                                done[0] = true;
                            }
                        },
                        r1.recv() -> d => {
                            if let Some(d) = d {
                                recv.push(d);
                            } else {
                                done[1] = true;
                            }
                        },
                        r2.recv() -> d => {
                            if let Some(d) = d {
                                recv.push(d);
                            } else {
                                done[2] = true;
                            }
                        },
                        r3.recv() -> d => {
                            if let Some(d) = d {
                                recv.push(d);
                            } else {
                                done[3] = true;
                            }
                        },
                    }
                    if done.iter().all(|&b| b) {
                        break;
                    }
                }
                assert_eq!(sum, get_data_sum(recv.into_iter()));
            });
        }
    }
}

bench_select_with_init!(select_with_init_chan_sync_unbuffered, chan::sync(0));
bench_select_with_init!(select_with_init_chan_sync_buffered, chan::sync(1));
bench_select_with_init!(select_with_init_chan_sync_buffered_all, chan::sync(1000));
bench_select_with_init!(select_with_init_chan_async, chan::async());
