use crate::pairing::{
    CurveAffine,
    CurveProjective,
    Engine
};
use crate::pairing::ff::{
    PrimeField,
    Field,
    PrimeFieldRepr,
    ScalarEngine};
use std::sync::Arc;
use super::source::*;
use std::future::{Future};
use std::task::{Context, Poll};
use std::pin::{Pin};
extern crate futures;
use self::futures::future::{join_all, JoinAll};
use self::futures::executor::block_on;
use super::worker::{Worker, WorkerFuture};
use super::SynthesisError;
use cfg_if;
fn multiexp_inner<Q, D, G, S>(
    pool: &Worker,
    bases: S,
    density_map: D,
    exponents: Arc<Vec<<G::Scalar as PrimeField>::Repr>>,
    skip: u32,
    c: u32,
    handle_trivial: bool
) -> WorkerFuture< <G as CurveAffine>::Projective, SynthesisError>
    where for<'a> &'a Q: QueryDensity,
          D: Send + Sync + 'static + Clone + AsRef<Q>,
          G: CurveAffine,
          S: SourceBuilder<G>
{
        let this = {
                        
                pool.compute(move || {
                        let mut acc = G::Projective::zero();
                        let mut bases = bases.new();
                        
                        let mut buckets = vec![<G as CurveAffine>::Projective::zero(); (1 << c) - 1];
            let zero = <G::Engine as ScalarEngine>::Fr::zero().into_repr();
            let one = <G::Engine as ScalarEngine>::Fr::one().into_repr();
                        for (&exp, density) in exponents.iter().zip(density_map.as_ref().iter()) {
                                if density {
                    if exp == zero {
                        bases.skip(1)?;
                    } else if exp == one {
                        if handle_trivial {
                            bases.add_assign_mixed(&mut acc)?;
                        } else {
                            bases.skip(1)?;
                        }
                    } else {
                                                                                                                                                let mut exp = exp;
                        exp.shr(skip);
                        let exp = exp.as_ref()[0] % (1 << c);
                        if exp != 0 {
                            bases.add_assign_mixed(&mut buckets[(exp - 1) as usize])?;
                        } else {
                            bases.skip(1)?;
                        }
                    }
                }
            }
            
                        let start = std::time::Instant::now();
                                                            let mut running_sum = G::Projective::zero();
            for exp in buckets.into_iter().rev() {
                running_sum.add_assign(&exp);
                acc.add_assign(&running_sum);
            }
            if skip == 0 {
                let duration_ns = start.elapsed().as_nanos() as f64;
                println!("Elapsed {} ns for special loop", duration_ns);
            }
            Ok(acc)
        })
    };
    this
}
#[inline(always)]
fn multiexp_inner_impl<Q, D, G, S>(
    pool: &Worker,
    bases: S,
    density_map: D,
    exponents: Arc<Vec<<G::Scalar as PrimeField>::Repr>>,
    skip: u32,
    c: u32,
    handle_trivial: bool
) -> WorkerFuture< <G as CurveAffine>::Projective, SynthesisError>
    where for<'a> &'a Q: QueryDensity,
        D: Send + Sync + 'static + Clone + AsRef<Q>,
        G: CurveAffine,
        S: SourceBuilder<G>
{
    multiexp_inner(pool, bases, density_map, exponents, skip, c, handle_trivial)
    }
fn multiexp_inner_with_prefetch_stable<Q, D, G, S>(
    pool: &Worker,
    bases: S,
    density_map: D,
    exponents: Arc<Vec<<G::Scalar as PrimeField>::Repr>>,
    skip: u32,
    c: u32,
    handle_trivial: bool
) -> WorkerFuture< <G as CurveAffine>::Projective, SynthesisError>
    where for<'a> &'a Q: QueryDensity,
          D: Send + Sync + 'static + Clone + AsRef<Q>,
          G: CurveAffine,
          S: SourceBuilder<G>
{
        let this = {
        let bases = bases.clone();
        let exponents = exponents.clone();
        let density_map = density_map.clone();
                pool.compute(move || {
                        let mut acc = G::Projective::zero();
                        let mut bases = bases.new();
                        
                        let mut buckets = vec![<G as CurveAffine>::Projective::zero(); (1 << c) - 1];
            let zero = <G::Engine as ScalarEngine>::Fr::zero().into_repr();
            let one = <G::Engine as ScalarEngine>::Fr::one().into_repr();
            let padding = Arc::new(vec![zero]);
            let mask = (1u64 << c) - 1;
                        for ((&exp, &next_exp), density) in exponents.iter()
                        .zip(exponents.iter().skip(1).chain(padding.iter()))
                        .zip(density_map.as_ref().iter()) {
                                if next_exp != zero && next_exp != one {
                    let mut next_exp = next_exp;
                    next_exp.shr(skip);
                    let next_exp = next_exp.as_ref()[0] & mask;
                    if next_exp != 0 {
                        let p: *const <G as CurveAffine>::Projective = &buckets[(next_exp - 1) as usize];
                        crate::prefetch::prefetch_l1_pointer(p);
                    }
                    
                }
                                if density {
                    if exp == zero {
                        bases.skip(1)?;
                    } else if exp == one {
                        if handle_trivial {
                            bases.add_assign_mixed(&mut acc)?;
                        } else {
                            bases.skip(1)?;
                        }
                    } else {
                                                                                                                                                let mut exp = exp;
                        exp.shr(skip);
                        let exp = exp.as_ref()[0] % mask;
                        if exp != 0 {
                            bases.add_assign_mixed(&mut buckets[(exp - 1) as usize])?;
                        } else {
                            bases.skip(1)?;
                        }
                    }
                }
            }
                                                            let mut running_sum = G::Projective::zero();
            for exp in buckets.into_iter().rev() {
                running_sum.add_assign(&exp);
                acc.add_assign(&running_sum);
            }
            Ok(acc)
        })
    };
    this
}
pub fn future_based_multiexp<G: CurveAffine>(
    pool: &Worker,
    bases: Arc<Vec<G>>,
    exponents: Arc<Vec<<G::Scalar as PrimeField>::Repr>>
) -> ChunksJoiner< <G as CurveAffine>::Projective >
{
    assert!(exponents.len() <= bases.len());
    let c = if exponents.len() < 32 {
        3u32
    } else {
        let mut width = (f64::from(exponents.len() as u32)).ln().ceil() as u32;
        let mut num_chunks = <G::Scalar as PrimeField>::NUM_BITS / width;
        if <G::Scalar as PrimeField>::NUM_BITS % width != 0 {
            num_chunks += 1;
        }
        if num_chunks < pool.cpus as u32 {
            width = <G::Scalar as PrimeField>::NUM_BITS / (pool.cpus as u32);
            if <G::Scalar as PrimeField>::NUM_BITS % (pool.cpus as u32) != 0 {
                width += 1;
            }
        }
        
        width
    };
    let mut skip = 0;
    let mut futures = Vec::with_capacity((<G::Engine as ScalarEngine>::Fr::NUM_BITS / c + 1) as usize);
    while skip < <G::Engine as ScalarEngine>::Fr::NUM_BITS {
        let chunk_future = if skip == 0 {
            future_based_dense_multiexp_impl(pool, bases.clone(), exponents.clone(), 0, c, true)
        } else {
            future_based_dense_multiexp_impl(pool, bases.clone(), exponents.clone(), skip, c, false)
        };
        futures.push(chunk_future);
        skip += c;
    }
    let join = join_all(futures);
    ChunksJoiner {
        join,
        c
    } 
}
pub fn future_based_dense_multiexp_over_fixed_width_windows<G: CurveAffine>(
    pool: &Worker,
    bases: Arc<Vec<G>>,
    exponents: Arc<Vec<<G::Scalar as PrimeField>::Repr>>,
    c: u32
) -> ChunksJoiner< <G as CurveAffine>::Projective >
{
    assert!(exponents.len() <= bases.len());
    let mut skip = 0;
    let mut futures = Vec::with_capacity((<G::Engine as ScalarEngine>::Fr::NUM_BITS / c + 1) as usize);
    while skip < <G::Engine as ScalarEngine>::Fr::NUM_BITS {
        let chunk_future = if skip == 0 {
                        future_based_dense_multiexp_impl(pool, bases.clone(), exponents.clone(), 0, c, true)
        } else {
                        future_based_dense_multiexp_impl(pool, bases.clone(), exponents.clone(), skip, c, false)
        };
        futures.push(chunk_future);
        skip += c;
    }
    let join = join_all(futures);
    ChunksJoiner {
        join,
        c
    } 
}
fn future_based_dense_multiexp_impl<G: CurveAffine>(
    pool: &Worker,
    bases: Arc<Vec<G>>,
    exponents: Arc<Vec<<G::Scalar as PrimeField>::Repr>>,
    skip: u32,
    c: u32,
    handle_trivial: bool
) -> WorkerFuture< <G as CurveAffine>::Projective, SynthesisError>
{
        let this = {
        let bases = bases.clone();
        let exponents = exponents.clone();
        let bases = bases.clone();
                pool.compute(move || {
                        let mut acc = G::Projective::zero();
                        
                        let mut buckets = vec![<G as CurveAffine>::Projective::zero(); (1 << c) - 1];
            let zero = <G::Engine as ScalarEngine>::Fr::zero().into_repr();
            let one = <G::Engine as ScalarEngine>::Fr::one().into_repr();
            let padding = Arc::new(vec![zero]);
            let mask = 1 << c;
                        for ((&exp, base), &next_exp) in exponents.iter()
                        .zip(bases.iter())
                        .zip(exponents.iter().skip(1).chain(padding.iter())) {
                                if next_exp != zero && next_exp != one {
                    let mut next_exp = next_exp;
                    next_exp.shr(skip);
                    let next_exp = next_exp.as_ref()[0] % mask;
                    if next_exp != 0 {
                        let p: *const <G as CurveAffine>::Projective = &buckets[(next_exp - 1) as usize];
                        crate::prefetch::prefetch_l1_pointer(p);
                    }
                    
                }
                                if exp == zero {
                    continue
                } else if exp == one {
                    if handle_trivial {
                        acc.add_assign_mixed(base);
                    } else {
                        continue
                    }
                } else {
                                                                                                                        let mut exp = exp;
                    exp.shr(skip);
                    let exp = exp.as_ref()[0] % mask;
                    if exp != 0 {
                        (&mut buckets[(exp - 1) as usize]).add_assign_mixed(base);
                    } else {
                        continue;
                    }
                }
            }
                                                            let mut running_sum = G::Projective::zero();
            for exp in buckets.into_iter().rev() {
                running_sum.add_assign(&exp);
                acc.add_assign(&running_sum);
            }
            Ok(acc)
        })
    };
    this
}
fn future_based_buffered_dense_multiexp_impl<G: CurveAffine>(
    pool: &Worker,
    bases: Arc<Vec<G>>,
    exponents: Arc<Vec<<G::Scalar as PrimeField>::Repr>>,
    skip: u32,
    c: u32,
    handle_trivial: bool
) -> WorkerFuture< <G as CurveAffine>::Projective, SynthesisError>
{
        let this = {
        let bases = bases.clone();
        let exponents = exponents.clone();
        let bases = bases.clone();
                pool.compute(move || {
                        let mut acc = G::Projective::zero();
                        
                        let mut buckets = vec![<G as CurveAffine>::Projective::zero(); (1 << c) - 1];
            let zero = <G::Engine as ScalarEngine>::Fr::zero().into_repr();
            let one = <G::Engine as ScalarEngine>::Fr::one().into_repr();
            let mask = 1 << c;
            const BUFFER_SIZE: usize = 64;
            let mut buffers: Vec<Vec<G>> = vec![Vec::with_capacity(BUFFER_SIZE); (1 << c) - 1];
                        for (&exp, &base) in exponents.iter()
                        .zip(bases.iter()) {
                                if exp == zero {
                    continue
                } else if exp == one {
                    if handle_trivial {
                        acc.add_assign_mixed(&base);
                    } else {
                        continue
                    }
                } else {
                                                                                                                        let mut exp = exp;
                    exp.shr(skip);
                    let exp = exp.as_ref()[0] % mask;
                    if exp != 0 {
                        let idx = (exp - 1) as usize;
                        if buffers[idx].len() == BUFFER_SIZE {
                            let mut el = buckets[idx];
                            for b in buffers[idx].iter(){
                                el.add_assign_mixed(&b);
                            }
                            buffers[idx].truncate(0);
                            buckets[idx] = el;
                        }
                        buffers[idx].push(base);
                    } else {
                        continue;
                    }
                }
            }
                        for (idx, buffer) in buffers.into_iter().enumerate() {
                let mut el = buckets[idx];
                for b in buffer.into_iter() {
                    el.add_assign_mixed(&b);
                }
                buckets[idx] = el;
            }
                                                            let mut running_sum = G::Projective::zero();
            for exp in buckets.into_iter().rev() {
                running_sum.add_assign(&exp);
                acc.add_assign(&running_sum);
            }
            Ok(acc)
        })
    };
    this
}
pub fn multiexp<Q, D, G, S>(
    pool: &Worker,
    bases: S,
    density_map: D,
    exponents: Arc<Vec<<<G::Engine as ScalarEngine>::Fr as PrimeField>::Repr>>
) -> ChunksJoiner< <G as CurveAffine>::Projective >
    where for<'a> &'a Q: QueryDensity,
          D: Send + Sync + 'static + Clone + AsRef<Q>,
          G: CurveAffine,
          S: SourceBuilder<G>
{
    let c = if exponents.len() < 32 {
        3u32
    } else {
        (f64::from(exponents.len() as u32)).ln().ceil() as u32
    };
    if let Some(query_size) = density_map.as_ref().get_query_size() {
                
        assert!(query_size == exponents.len());
    }
    let mut skip = 0;
    let mut futures = Vec::with_capacity((<G::Engine as ScalarEngine>::Fr::NUM_BITS / c + 1) as usize);
    while skip < <G::Engine as ScalarEngine>::Fr::NUM_BITS {
        let chunk_future = if skip == 0 {
            multiexp_inner_impl(pool, bases.clone(), density_map.clone(), exponents.clone(), 0, c, true)
        } else {
            multiexp_inner_impl(pool, bases.clone(), density_map.clone(), exponents.clone(), skip, c, false)
        };
        futures.push(chunk_future);
        skip += c;
    }
    let join = join_all(futures);
    ChunksJoiner {
        join,
        c
    } 
}
pub(crate) fn multiexp_with_fixed_width<Q, D, G, S>(
    pool: &Worker,
    bases: S,
    density_map: D,
    exponents: Arc<Vec<<<G::Engine as ScalarEngine>::Fr as PrimeField>::Repr>>,
    c: u32
) -> ChunksJoiner< <G as CurveAffine>::Projective >
    where for<'a> &'a Q: QueryDensity,
          D: Send + Sync + 'static + Clone + AsRef<Q>,
          G: CurveAffine,
          S: SourceBuilder<G>
{
    if let Some(query_size) = density_map.as_ref().get_query_size() {
                
        assert!(query_size == exponents.len());
    }
    let mut skip = 0;
    let mut futures = Vec::with_capacity((<G::Engine as ScalarEngine>::Fr::NUM_BITS / c + 1) as usize);
    while skip < <G::Engine as ScalarEngine>::Fr::NUM_BITS {
        let chunk_future = if skip == 0 {
            multiexp_inner_impl(pool, bases.clone(), density_map.clone(), exponents.clone(), 0, c, true)
        } else {
            multiexp_inner_impl(pool, bases.clone(), density_map.clone(), exponents.clone(), skip, c, false)
        };
        futures.push(chunk_future);
        skip += c;
    }
    let join = join_all(futures);
    ChunksJoiner {
        join,
        c
    } 
}
pub struct ChunksJoiner<G: CurveProjective> {
    join: JoinAll< WorkerFuture<G, SynthesisError> >,
    c: u32
}
impl<G: CurveProjective> Future for ChunksJoiner<G> {
    type Output = Result<G, SynthesisError>;
    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>
    {
        let c = self.as_ref().c;
        let join = unsafe { self.map_unchecked_mut(|s| &mut s.join) };
        match join.poll(cx) {
            Poll::Ready(v) => {
                let v = join_chunks(v, c);
                return Poll::Ready(v);
            },
            Poll::Pending => {
                return Poll::Pending;
            }
        }
    }
}
impl<G: CurveProjective> ChunksJoiner<G> {
    pub fn wait(self) -> <Self as Future>::Output {
        block_on(self)
    }
}
fn join_chunks<G: CurveProjective>
    (chunks: Vec<Result<G, SynthesisError>>, c: u32) -> Result<G, SynthesisError> {
    if chunks.len() == 0 {
        return Ok(G::zero());
    }
    let mut iter = chunks.into_iter().rev();
    let higher = iter.next().expect("is some chunk result");
    let mut higher = higher?;
    for chunk in iter {
        let this = chunk?;
        for _ in 0..c {
            higher.double();
        }
        higher.add_assign(&this);
    }
    Ok(higher)
}
#[allow(dead_code)]
pub fn dense_multiexp<G: CurveAffine>(
    pool: &Worker,
    bases: & [G],
    exponents: & [<<G::Engine as ScalarEngine>::Fr as PrimeField>::Repr]
) -> Result<<G as CurveAffine>::Projective, SynthesisError>
{
    if exponents.len() != bases.len() {
        return Err(SynthesisError::AssignmentMissing);
    }
                    let c = if exponents.len() < 32 {
        3u32
    } else {
        let chunk_len = pool.get_chunk_size(exponents.len());
        (f64::from(chunk_len as u32)).ln().ceil() as u32
            };
        dense_multiexp_inner(pool, bases, exponents, 0, c, true)
}
fn dense_multiexp_inner<G: CurveAffine>(
    pool: &Worker,
    bases: & [G],
    exponents: & [<<G::Engine as ScalarEngine>::Fr as PrimeField>::Repr],
    mut skip: u32,
    c: u32,
    handle_trivial: bool
) -> Result<<G as CurveAffine>::Projective, SynthesisError>
{   
    use std::sync::{Mutex};
            let this = {
                let this_region = Mutex::new(<G as CurveAffine>::Projective::zero());
        let arc = Arc::new(this_region);
        pool.scope(bases.len(), |scope, chunk| {
            for (base, exp) in bases.chunks(chunk).zip(exponents.chunks(chunk)) {
                let this_region_rwlock = arc.clone();
                                scope.spawn(move |_| {
                    let mut buckets = vec![<G as CurveAffine>::Projective::zero(); (1 << c) - 1];
                                        let mut acc = G::Projective::zero();
                    let zero = <G::Engine as ScalarEngine>::Fr::zero().into_repr();
                    let one = <G::Engine as ScalarEngine>::Fr::one().into_repr();
                    for (base, &exp) in base.iter().zip(exp.iter()) {
                        
                                                                        
                        
                        if exp != zero {
                            if exp == one {
                                if handle_trivial {
                                    acc.add_assign_mixed(base);
                                }
                            } else {
                                let mut exp = exp;
                                exp.shr(skip);
                                let exp = exp.as_ref()[0] % (1 << c);
                                if exp != 0 {
                                    buckets[(exp - 1) as usize].add_assign_mixed(base);
                                }
                            }
                        }
                    }
                                        let mut running_sum = G::Projective::zero();
                    for exp in buckets.into_iter().rev() {
                        running_sum.add_assign(&exp);
                        acc.add_assign(&running_sum);
                    }
                    let mut guard = match this_region_rwlock.lock() {
                        Ok(guard) => guard,
                        Err(_) => {
                            panic!("poisoned!"); 
                                                    }
                    };
                    (*guard).add_assign(&acc);
                });
        
            }
        });
        let this_region = Arc::try_unwrap(arc).unwrap();
        let this_region = this_region.into_inner().unwrap();
        this_region
    };
    skip += c;
    if skip >= <G::Engine as ScalarEngine>::Fr::NUM_BITS {
                return Ok(this);
    } else {
                let mut next_region = dense_multiexp_inner(
            pool, bases, exponents, skip, c, false).unwrap();
        for _ in 0..c {
            next_region.double();
        }
        next_region.add_assign(&this);
        return Ok(next_region);
    }
}
fn get_window_size_for_length(length: usize, chunk_length: usize) -> u32 {
    if length < 32 {
        return 3u32;
    } else {
        let exact = (f64::from(chunk_length as u32)).ln();
        let floor = exact.floor();
        if exact > floor + 0.5f64 {
            return exact.ceil() as u32;
        } else {
            return floor as u32;
        }
                    };
}
#[cfg(test)]
mod test {
    use super::*;
    fn naive_multiexp<G: CurveAffine>(
        bases: Arc<Vec<G>>,
        exponents: Arc<Vec<<G::Scalar as PrimeField>::Repr>>
    ) -> G::Projective
    {
        assert_eq!(bases.len(), exponents.len());
        let mut acc = G::Projective::zero();
        for (base, exp) in bases.iter().zip(exponents.iter()) {
            acc.add_assign(&base.mul(*exp));
        }
        acc
    }
    #[test]
    fn test_new_multiexp_with_bls12() {
        use rand::{self, Rand};
        use crate::pairing::bls12_381::Bls12;
        use self::futures::executor::block_on;
        const SAMPLES: usize = 1 << 14;
        let rng = &mut rand::thread_rng();
        let v = Arc::new((0..SAMPLES).map(|_| <Bls12 as ScalarEngine>::Fr::rand(rng).into_repr()).collect::<Vec<_>>());
        let g = Arc::new((0..SAMPLES).map(|_| <Bls12 as Engine>::G1::rand(rng).into_affine()).collect::<Vec<_>>());
        let naive = naive_multiexp(g.clone(), v.clone());
        let pool = Worker::new();
        let fast = block_on(
            multiexp(
                &pool,
                (g, 0),
                FullDensity,
                v
            )
        ).unwrap();
        assert_eq!(naive, fast);
    }
    #[test]
    fn test_valid_bn254_multiexp() {
        use rand::{self, Rand};
        use crate::pairing::bn256::Bn256;
        const SAMPLES: usize = 1 << 22;
        let pool = Worker::new();
        let rng = &mut rand::thread_rng();
        let v = (0..SAMPLES).map(|_| <Bn256 as ScalarEngine>::Fr::rand(rng).into_repr()).collect::<Vec<_>>();
        let g = (0..SAMPLES).map(|_| <Bn256 as Engine>::G1::rand(rng).into_affine()).collect::<Vec<_>>();
        let dense = dense_multiexp(
            &pool,
            &g,
            &v,
        ).unwrap();
        let v = Arc::new(v);
        let g = Arc::new(g);
        let naive = naive_multiexp(g.clone(), v.clone());
        assert_eq!(dense, naive);
        use self::futures::executor::block_on;
        let fast_dense = future_based_multiexp(
            &pool,
            g.clone(),
            v.clone()
        ).wait().unwrap();
        assert_eq!(naive, fast_dense);
        let fast = block_on(
            multiexp(
                &pool,
                (g, 0),
                FullDensity,
                v
            )
        ).unwrap();
        assert_eq!(naive, fast);
    }
    #[test]
    #[ignore]
    fn test_new_multexp_speed_with_bn256() {
        
        use rand::{self, Rand};
        use crate::pairing::bn256::Bn256;
        use num_cpus;
        let cpus = num_cpus::get();
        const SAMPLES: usize = 1 << 22;
        let rng = &mut rand::thread_rng();
        let v = Arc::new((0..SAMPLES).map(|_| <Bn256 as ScalarEngine>::Fr::rand(rng).into_repr()).collect::<Vec<_>>());
        let g = Arc::new((0..SAMPLES).map(|_| <Bn256 as Engine>::G1::rand(rng).into_affine()).collect::<Vec<_>>());
        let pool = Worker::new();
        use self::futures::executor::block_on;
        let start = std::time::Instant::now();
        let _fast = block_on(
            multiexp(
                &pool,
                (g, 0),
                FullDensity,
                v
            )
        ).unwrap();
        let duration_ns = start.elapsed().as_nanos() as f64;
        println!("Elapsed {} ns for {} samples", duration_ns, SAMPLES);
        let time_per_sample = duration_ns/(SAMPLES as f64);
        println!("Tested on {} samples on {} CPUs with {} ns per multiplication", SAMPLES, cpus, time_per_sample);
    }
    fn calculate_parameters(size: usize, threads: usize, bits: u32) {
        let mut chunk_len = size / threads;
        if size / threads != 0 {
            chunk_len += 1;
        }
        let raw_size = (f64::from(chunk_len as u32)).ln();
        let new_window_size = if raw_size.floor() + 0.5 < raw_size {
            raw_size.ceil() as u32
        } else {
            raw_size.floor() as u32
        };
        let window_size = (f64::from(chunk_len as u32)).ln().ceil() as u32;
        let mut num_windows = bits / window_size;
        let leftover = bits % window_size;
        if leftover != 0 {
            num_windows += 1;
        }
        let uncompensated_window = (f64::from(size as u32)).ln().ceil() as u32;
        let mut num_uncompensated_windows = bits / uncompensated_window;
        let uncompensated_leftover = bits % uncompensated_window;
        if uncompensated_leftover != 0 {
            num_uncompensated_windows += 1;
        }
        println!("For size {} and {} cores: chunk len {}, {} windows, average window {} bits, leftover {} bits. Alternative window size = {}", size, threads, chunk_len, num_windows, window_size, leftover, new_window_size);
                
            }
    #[test]
    fn test_sizes_for_bn254() {
        let sizes = vec![1<<23, 1<<24];
        let cores = vec![8, 12, 16, 24, 32, 48];
        for size in sizes {
            for &core in &cores {
                calculate_parameters(size, core, 254);
            }
        }
    }
    #[test]
    fn bench_bls_addition() {
        use rand::{self, Rand};
        use crate::pairing::bls12_381::Bls12;
        let size = 100000u32;
        let rng = &mut rand::thread_rng();
        let A = (0..size).map(|_| <Bls12 as Engine>::G1::rand(rng)).collect::<Vec<_>>();
        let B = (0..size).map(|_| <Bls12 as Engine>::G1::rand(rng)).collect::<Vec<_>>();
        let start = std::time::Instant::now();
        let C = (0..size).map(|i| {
            let mut temp = A[i as usize];
            temp.add_assign(&B[i as usize]);
            temp
        }).collect::<Vec<_>>();
        let duration_ns = start.elapsed().as_nanos() as f64;
        println!("Elapsed {} ns for {} samples", duration_ns, size);
        let time_per_sample = duration_ns/(size as f64);
        println!("Elapsed {} ns per sample", time_per_sample);
    }
    
    #[test]
    fn bench_bls_doubling() {
        use rand::{self, Rand};
        use crate::pairing::bls12_381::Bls12;
        let size = 100000u32;
        let rng = &mut rand::thread_rng();
        let A = (0..size).map(|_| <Bls12 as Engine>::G1::rand(rng)).collect::<Vec<_>>();
        let start = std::time::Instant::now();
        let B = (0..size).map(|i| {
            let mut temp = A[i as usize];
            temp.double();
            temp
        }).collect::<Vec<_>>();
        let duration_ns = start.elapsed().as_nanos() as f64;
        println!("Elapsed {} ns for {} samples", duration_ns, size);
        let time_per_sample = duration_ns/(size as f64);
        println!("Elapsed {} ns per sample", time_per_sample);
    }
    #[test]
    fn bench_Pippenger_with_small_chunk() {
        use rand::{self, Rand};
        use crate::pairing::bls12_381::Bls12;
        let size = 1000000u32;
        let rng = &mut rand::thread_rng();
        let v = Arc::new((0..size).map(|_| <Bls12 as ScalarEngine>::Fr::rand(rng).into_repr()).collect::<Vec<_>>());
        let g = Arc::new((0..size).map(|_| <Bls12 as Engine>::G1::rand(rng).into_affine()).collect::<Vec<_>>());
        let pool = Worker::new();
        println!("loading {} cpus", pool.cpus);
        let start = std::time::Instant::now();
        let fast = block_on(
            multiexp(
                &pool,
                (g, 0),
                FullDensity,
                v
            )
        ).unwrap();
        let duration_ns = start.elapsed().as_nanos() as f64;
        println!("Elapsed {} ns for Pippenger", duration_ns);
        let time_per_sample = duration_ns/(size as f64);
        println!("Elapsed {} ns per sample", time_per_sample);
    }
}