use criterion::{criterion_group, criterion_main, Criterion};
use preserves::de;
use preserves::ser;
use preserves::value::BinarySource;
use preserves::value::BytesBinarySource;
use preserves::value::IOBinarySource;
use preserves::value::IOValueDomainCodec;
use preserves::value::PackedWriter;
use preserves::value::Reader;
use preserves::value::Writer;
use preserves::value::packed::annotated_iovalue_from_bytes;
use preserves::value;
use std::fs::File;
use std::io::Read;
use std::io::Seek;
use std::io;

#[path = "../tests/samples/mod.rs"]
mod samples;
use samples::TestCases;

pub fn bench_decoder_bytes(c: &mut Criterion) {
    let mut fh = File::open("../../../tests/samples.bin").unwrap();
    let mut bs = vec![];
    fh.read_to_end(&mut bs).ok();
    c.bench_function("decode samples.bin via bytes", |b| b.iter_with_large_drop(
        || annotated_iovalue_from_bytes(&bs[..]).unwrap()));
}

pub fn bench_decoder_file(c: &mut Criterion) {
    let mut fh = File::open("../../../tests/samples.bin").unwrap();
    c.bench_function("decode samples.bin via file", |b| b.iter_with_large_drop(|| {
        fh.seek(io::SeekFrom::Start(0)).ok();
        IOBinarySource::new(&mut fh).packed_iovalues().demand_next(true).unwrap()
    }));
}

pub fn bench_decoder_buffered_file(c: &mut Criterion) {
    let mut fh = io::BufReader::new(File::open("../../../tests/samples.bin").unwrap());
    c.bench_function("decode samples.bin via buffered file", |b| b.iter_with_large_drop(|| {
        fh.seek(io::SeekFrom::Start(0)).ok();
        IOBinarySource::new(&mut fh).packed_iovalues().demand_next(true).unwrap()
    }));
}

pub fn bench_encoder(c: &mut Criterion) {
    let mut fh = File::open("../../../tests/samples.bin").unwrap();
    let v = IOBinarySource::new(&mut fh).packed_iovalues().demand_next(true).unwrap();
    c.bench_function("encode samples.bin", |b| b.iter_with_large_drop(
        || PackedWriter::encode_iovalue(&v).unwrap()));
}

pub fn bench_de(c: &mut Criterion) {
    let mut fh = File::open("../../../tests/samples.bin").unwrap();
    let mut bs = vec![];
    fh.read_to_end(&mut bs).ok();
    c.bench_function("deserialize samples.bin", |b| b.iter_with_large_drop(
        || de::from_bytes::<TestCases>(&bs[..]).unwrap()));
}

pub fn bench_ser(c: &mut Criterion) {
    let mut fh = File::open("../../../tests/samples.bin").unwrap();
    let v: TestCases = de::from_read(&mut fh).unwrap();
    c.bench_function("serialize samples.bin", |b| b.iter_with_large_drop(|| {
        let mut bs = vec![];
        ser::to_writer(&mut PackedWriter::new(&mut bs), &v).unwrap();
        bs
    }));
}

pub fn bench_decoder_de(c: &mut Criterion) {
    let mut fh = File::open("../../../tests/samples.bin").unwrap();
    let mut bs = vec![];
    fh.read_to_end(&mut bs).ok();
    c.bench_function("decode-then-deserialize samples.bin", |b| b.iter_with_large_drop(
        || value::de::from_value::<TestCases>(&annotated_iovalue_from_bytes(&bs[..]).unwrap()).unwrap()));
}

pub fn bench_ser_encoder(c: &mut Criterion) {
    let mut fh = File::open("../../../tests/samples.bin").unwrap();
    let v: TestCases = de::from_read(&mut fh).unwrap();
    c.bench_function("serialize-then-encode samples.bin", |b| b.iter_with_large_drop(
        || PackedWriter::encode_iovalue(&value::ser::to_value(&v)).unwrap()));
}

pub fn large_testdata_decoder_with_ann(c: &mut Criterion) {
    c.bench_function("decode testdata.bin with annotations", |b| {
        let mut fh = File::open("benches/testdata.bin").unwrap();
        let mut bs = vec![];
        fh.read_to_end(&mut bs).ok();
        b.iter(|| {
            let mut src = BytesBinarySource::new(&bs[..]);
            let mut r = src.packed_iovalues();
            while let Some(_) = r.next(true).unwrap() {}
        })
    });
}

pub fn large_testdata_decoder_without_ann(c: &mut Criterion) {
    c.bench_function("decode testdata.bin without annotations", |b| {
        let mut fh = File::open("benches/testdata.bin").unwrap();
        let mut bs = vec![];
        fh.read_to_end(&mut bs).ok();
        b.iter(|| {
            let mut src = BytesBinarySource::new(&bs[..]);
            let mut r = src.packed_iovalues();
            while let Some(_) = r.next(false).unwrap() {}
        })
    });
}

pub fn large_testdata_encoder(c: &mut Criterion) {
    c.bench_function("encode testdata.bin", |b| {
        let mut fh = io::BufReader::new(File::open("benches/testdata.bin").unwrap());
        let mut vs = vec![];
        let mut src = IOBinarySource::new(&mut fh);
        let mut r = src.packed_iovalues();
        while let Some(v) = r.next(true).unwrap() {
            vs.push(v);
        }
        b.iter_with_large_drop(|| {
            let mut bs = vec![];
            let mut w = PackedWriter::new(&mut bs);
            let mut enc = IOValueDomainCodec;
            for v in &vs {
                w.write(&mut enc, v).unwrap();
            }
            bs
        })
    });
}

criterion_group!(codec,
                 bench_decoder_bytes, bench_decoder_file, bench_decoder_buffered_file,
                 bench_encoder);
criterion_group!(serde, bench_de, bench_ser);
criterion_group!(codec_then_serde, bench_decoder_de, bench_ser_encoder);
criterion_group!{
    name = large_testdata;
    config = Criterion::default().sample_size(10);
    targets = large_testdata_decoder_with_ann, large_testdata_decoder_without_ann, large_testdata_encoder
}
criterion_main!(codec, serde, codec_then_serde, large_testdata);
