use adler32::RollingAdler32;
use cyclic_poly_23::{CyclicPoly32, CyclicPoly64};
use easybench::bench;
use rand::Rng;

fn random_vector(length: usize) -> Vec<u8> {
    let mut ret = vec![0; length];
    let mut rng = rand::thread_rng();
    rng.fill(&mut ret[..]);
    ret
}

fn cyclic_poly32_block(block: &[u8]) -> u32 {
    CyclicPoly32::from_block(block).value()
}

fn cyclic_poly64_block(block: &[u8]) -> u64 {
    CyclicPoly64::from_block(block).value()
}

fn adler32_block(block: &[u8]) -> u32 {
    RollingAdler32::from_buffer(block).hash()
}

fn megabytes_per_second(bytes: usize, ns: f64) -> usize {
    let megabytes = bytes as f64 / 1000000.0;
    (megabytes * 1000000000.0 / ns) as usize
}

fn single_block(cap: usize) {
    let kb = cap as f64 / 1000.0;
    let v = random_vector(cap);
    let cyclic_poly32 = bench(|| cyclic_poly32_block(&v));
    let cyclic_poly64 = bench(|| cyclic_poly64_block(&v));
    let adler32 = bench(|| adler32_block(&v));
    let cyclic_poly32_mbps = megabytes_per_second(cap, cyclic_poly32.ns_per_iter);
    let cyclic_poly64_mbps = megabytes_per_second(cap, cyclic_poly64.ns_per_iter);
    let adler32_mbps = megabytes_per_second(cap, adler32.ns_per_iter);
    println!(
        "Cyclic Poly 32 {: >4} KB block  : {} MB/sec, {}",
        kb, cyclic_poly32_mbps, cyclic_poly32
    );
    println!(
        "Cyclic Poly 64 {: >4} KB block  : {} MB/sec, {}",
        kb, cyclic_poly64_mbps, cyclic_poly64
    );
    println!(
        "Adler32        {: >4} KB block  : {} MB/sec, {}",
        kb, adler32_mbps, adler32
    );
}

fn cyclic_poly32_rolling(v: &[u8], blocksize: usize) -> u32 {
    let mut cpoly = CyclicPoly32::new(blocksize);
    cpoly.calculate(&v[0..blocksize]);
    for i in blocksize..v.len() {
        cpoly.rotate(v[i - blocksize], v[i]);
    }
    cpoly.value()
}

fn cyclic_poly64_rolling(v: &[u8], blocksize: usize) -> u64 {
    let mut cpoly = CyclicPoly64::new(blocksize);
    cpoly.calculate(&v[0..blocksize]);
    for i in blocksize..v.len() {
        cpoly.rotate(v[i - blocksize], v[i]);
    }
    cpoly.value()
}

fn adler32_rolling(v: &[u8], blocksize: usize) -> u32 {
    let mut adler32 = RollingAdler32::from_buffer(&v[0..blocksize]);
    for i in blocksize..v.len() {
        adler32.remove(blocksize, v[i - blocksize]);
        adler32.update(v[i]);
    }
    adler32.hash()
}

fn rolling(blocksize: usize) {
    let cap = 1000 * 1000;
    let v = random_vector(cap);
    let cyclic_poly32 = bench(|| cyclic_poly32_rolling(&v, blocksize));
    let cyclic_poly32_mbps = megabytes_per_second(cap, cyclic_poly32.ns_per_iter);
    let cyclic_poly64 = bench(|| cyclic_poly64_rolling(&v, blocksize));
    let cyclic_poly64_mbps = megabytes_per_second(cap, cyclic_poly64.ns_per_iter);
    let adler32 = bench(|| adler32_rolling(&v, blocksize));
    let adler32_mbps = megabytes_per_second(cap, adler32.ns_per_iter);
    println!(
        "Cyclic Poly 32 rolling {: >4} B : {} MB/sec, {}",
        blocksize, cyclic_poly32_mbps, cyclic_poly32
    );
    println!(
        "Cyclic Poly 64 rolling {: >4} B : {} MB/sec, {}",
        blocksize, cyclic_poly64_mbps, cyclic_poly64
    );
    println!(
        "Alder32 rolling        {: >4} B : {} MB/sec, {}",
        blocksize, adler32_mbps, adler32
    );
}

fn main() {
    single_block(1000 * 1000);
    single_block(1000);
    rolling(1000);
}
