#![feature(test)]
#![warn(rust_2018_idioms)]

extern crate test;

use bytes::Buf;
use test::Bencher;

/// Dummy Buf implementation
struct TestBuf {
    buf: &'static [u8],
    readlens: &'static [usize],
    init_pos: usize,
    pos: usize,
    readlen_pos: usize,
    readlen: usize,
}
impl TestBuf {
    fn new(buf: &'static [u8], readlens: &'static [usize], init_pos: usize) -> TestBuf {
        let mut buf = TestBuf {
            buf,
            readlens,
            init_pos,
            pos: 0,
            readlen_pos: 0,
            readlen: 0,
        };
        buf.reset();
        buf
    }
    fn reset(&mut self) {
        self.pos = self.init_pos;
        self.readlen_pos = 0;
        self.next_readlen();
    }
    /// Compute the length of the next read :
    /// - use the next value specified in readlens (capped by remaining) if any
    /// - else the remaining
    fn next_readlen(&mut self) {
        self.readlen = self.buf.len() - self.pos;
        if let Some(readlen) = self.readlens.get(self.readlen_pos) {
            self.readlen = std::cmp::min(self.readlen, *readlen);
            self.readlen_pos += 1;
        }
    }
}
impl Buf for TestBuf {
    fn remaining(&self) -> usize {
        return self.buf.len() - self.pos;
    }
    fn advance(&mut self, cnt: usize) {
        self.pos += cnt;
        assert!(self.pos <= self.buf.len());
        self.next_readlen();
    }
    fn chunk(&self) -> &[u8] {
        if self.readlen == 0 {
            Default::default()
        } else {
            &self.buf[self.pos..self.pos + self.readlen]
        }
    }
}

/// Dummy Buf implementation
///  version with methods forced to not be inlined (to simulate costly calls)
struct TestBufC {
    inner: TestBuf,
}
impl TestBufC {
    fn new(buf: &'static [u8], readlens: &'static [usize], init_pos: usize) -> TestBufC {
        TestBufC {
            inner: TestBuf::new(buf, readlens, init_pos),
        }
    }
    fn reset(&mut self) {
        self.inner.reset()
    }
}
impl Buf for TestBufC {
    #[inline(never)]
    fn remaining(&self) -> usize {
        self.inner.remaining()
    }
    #[inline(never)]
    fn advance(&mut self, cnt: usize) {
        self.inner.advance(cnt)
    }
    #[inline(never)]
    fn chunk(&self) -> &[u8] {
        self.inner.chunk()
    }
}

macro_rules! bench {
    ($fname:ident, testbuf $testbuf:ident $readlens:expr, $method:ident $(,$arg:expr)*) => (
        #[bench]
        fn $fname(b: &mut Bencher) {
            let mut bufs = [
                $testbuf::new(&[1u8; 8+0], $readlens, 0),
                $testbuf::new(&[1u8; 8+1], $readlens, 1),
                $testbuf::new(&[1u8; 8+2], $readlens, 2),
                $testbuf::new(&[1u8; 8+3], $readlens, 3),
                $testbuf::new(&[1u8; 8+4], $readlens, 4),
                $testbuf::new(&[1u8; 8+5], $readlens, 5),
                $testbuf::new(&[1u8; 8+6], $readlens, 6),
                $testbuf::new(&[1u8; 8+7], $readlens, 7),
            ];
            b.iter(|| {
                for i in 0..8 {
                    bufs[i].reset();
                    let buf: &mut dyn Buf =  &mut bufs[i]; // type erasure
                    test::black_box(buf.$method($($arg,)*));
                }
            })
        }
    );
    ($fname:ident, slice, $method:ident $(,$arg:expr)*) => (
        #[bench]
        fn $fname(b: &mut Bencher) {
            // buf must be long enough for one read of 8 bytes starting at pos 7
            let arr = [1u8; 8+7];
            b.iter(|| {
                for i in 0..8 {
                    let mut buf = &arr[i..];
                    let buf = &mut buf as &mut dyn Buf; // type erasure
                    test::black_box(buf.$method($($arg,)*));
                }
            })
        }
    );
    ($fname:ident, option) => (
        #[bench]
        fn $fname(b: &mut Bencher) {
            let data = [1u8; 1];
            b.iter(|| {
                for _ in 0..8 {
                    let mut buf = Some(data);
                    let buf = &mut buf as &mut dyn Buf; // type erasure
                    test::black_box(buf.get_u8());
                }
            })
        }
    );
}

macro_rules! bench_group {
    ($method:ident $(,$arg:expr)*) => (
        bench!(slice, slice, $method $(,$arg)*);
        bench!(tbuf_1,        testbuf TestBuf  &[],  $method $(,$arg)*);
        bench!(tbuf_1_costly, testbuf TestBufC &[],  $method $(,$arg)*);
        bench!(tbuf_2,        testbuf TestBuf  &[1], $method $(,$arg)*);
        bench!(tbuf_2_costly, testbuf TestBufC &[1], $method $(,$arg)*);
        // bench!(tbuf_onebyone,        testbuf TestBuf  &[1,1,1,1,1,1,1,1], $method $(,$arg)*);
        // bench!(tbuf_onebyone_costly, testbuf TestBufC &[1,1,1,1,1,1,1,1], $method $(,$arg)*);
    );
}

mod get_u8 {
    use super::*;
    bench_group!(get_u8);
}
mod get_u16 {
    use super::*;
    bench_group!(get_u16);
}
mod get_u32 {
    use super::*;
    bench_group!(get_u32);
}
mod get_u64 {
    use super::*;
    bench_group!(get_u64);
}
mod get_f32 {
    use super::*;
    bench_group!(get_f32);
}
mod get_f64 {
    use super::*;
    bench_group!(get_f64);
}
mod get_uint24 {
    use super::*;
    bench_group!(get_uint, 3);
}
