#![allow(clippy::integer_arithmetic)]
#![feature(test)]

extern crate test;

use {
    rand::distributions::{Distribution, Uniform},
    solana_core::unprocessed_packet_batches::*,
    solana_measure::measure::Measure,
    solana_perf::packet::{Packet, PacketBatch},
    solana_sdk::{hash::Hash, signature::Keypair, system_transaction},
    test::Bencher,
};

fn build_packet_batch(packet_per_batch_count: usize) -> (PacketBatch, Vec<usize>) {
    let packet_batch = PacketBatch::new(
        (0..packet_per_batch_count)
            .map(|sender_stake| {
                let tx = system_transaction::transfer(
                    &Keypair::new(),
                    &solana_sdk::pubkey::new_rand(),
                    1,
                    Hash::new_unique(),
                );
                let mut packet = Packet::from_data(None, &tx).unwrap();
                packet.meta.sender_stake = sender_stake as u64;
                packet
            })
            .collect(),
    );
    let packet_indexes: Vec<usize> = (0..packet_per_batch_count).collect();

    (packet_batch, packet_indexes)
}

fn build_randomized_packet_batch(packet_per_batch_count: usize) -> (PacketBatch, Vec<usize>) {
    let mut rng = rand::thread_rng();
    let distribution = Uniform::from(0..200_000);

    let packet_batch = PacketBatch::new(
        (0..packet_per_batch_count)
            .map(|_| {
                let tx = system_transaction::transfer(
                    &Keypair::new(),
                    &solana_sdk::pubkey::new_rand(),
                    1,
                    Hash::new_unique(),
                );
                let mut packet = Packet::from_data(None, &tx).unwrap();
                let sender_stake = distribution.sample(&mut rng);
                packet.meta.sender_stake = sender_stake as u64;
                packet
            })
            .collect(),
    );
    let packet_indexes: Vec<usize> = (0..packet_per_batch_count).collect();

    (packet_batch, packet_indexes)
}

fn insert_packet_batches(
    buffer_max_size: usize,
    batch_count: usize,
    packet_per_batch_count: usize,
    randomize: bool,
) {
    solana_logger::setup();
    let mut unprocessed_packet_batches = UnprocessedPacketBatches::with_capacity(buffer_max_size);

    let mut timer = Measure::start("insert_batch");
    (0..batch_count).for_each(|_| {
        let (packet_batch, packet_indexes) = if randomize {
            build_randomized_packet_batch(packet_per_batch_count)
        } else {
            build_packet_batch(packet_per_batch_count)
        };
        let deserialized_packets = deserialize_packets(&packet_batch, &packet_indexes);
        unprocessed_packet_batches.insert_batch(deserialized_packets);
    });
    timer.stop();
    log::info!(
        "inserted {} batch, elapsed {}",
        buffer_max_size,
        timer.as_us()
    );
}

#[bench]
#[allow(clippy::unit_arg)]
fn bench_packet_clone(bencher: &mut Bencher) {
    let batch_count = 1000;
    let packet_per_batch_count = 128;

    let packet_batches: Vec<PacketBatch> = (0..batch_count)
        .map(|_| build_packet_batch(packet_per_batch_count).0)
        .collect();

    bencher.iter(|| {
        test::black_box(packet_batches.iter().for_each(|packet_batch| {
            let mut outer_packet = Packet::default();

            let mut timer = Measure::start("insert_batch");
            packet_batch.iter().for_each(|packet| {
                let mut packet = packet.clone();
                packet.meta.sender_stake *= 2;
                if packet.meta.sender_stake > 2 {
                    outer_packet = packet;
                }
            });

            timer.stop();
        }));
    });
}

//*
// v1, bench: 5,600,038,163 ns/iter (+/- 940,818,988)
// v2, bench: 5,265,382,750 ns/iter (+/- 153,623,264)
#[bench]
#[ignore]
fn bench_unprocessed_packet_batches_within_limit(bencher: &mut Bencher) {
    let buffer_capacity = 1_000 * 128;
    let batch_count = 1_000;
    let packet_per_batch_count = 128;

    bencher.iter(|| {
        insert_packet_batches(buffer_capacity, batch_count, packet_per_batch_count, false);
    });
}

// v1, bench: 6,607,014,940 ns/iter (+/- 768,191,361)
// v2, bench: 5,692,753,323 ns/iter (+/- 548,959,624)
#[bench]
#[ignore]
fn bench_unprocessed_packet_batches_beyond_limit(bencher: &mut Bencher) {
    let buffer_capacity = 1_000 * 128;
    let batch_count = 1_100;
    let packet_per_batch_count = 128;

    // this is the worst scenario testing: all batches are uniformly populated with packets from
    // priority 100..228, so in order to drop a batch, algo will have to drop all packets that has
    // priority < 228, plus one 228. That's 2000 batch * 127 packets + 1
    // Also, since all batches have same stake distribution, the new one is always the one got
    // dropped. Tho it does not change algo complexity.
    bencher.iter(|| {
        insert_packet_batches(buffer_capacity, batch_count, packet_per_batch_count, false);
    });
}
// */
// v1, bench: 5,843,307,086 ns/iter (+/- 844,249,298)
// v2, bench: 5,139,525,951 ns/iter (+/- 48,005,521)
#[bench]
#[ignore]
fn bench_unprocessed_packet_batches_randomized_within_limit(bencher: &mut Bencher) {
    let buffer_capacity = 1_000 * 128;
    let batch_count = 1_000;
    let packet_per_batch_count = 128;

    bencher.iter(|| {
        insert_packet_batches(buffer_capacity, batch_count, packet_per_batch_count, true);
    });
}

// v1, bench: 6,497,623,849 ns/iter (+/- 3,206,382,212)
// v2, bench: 5,762,071,682 ns/iter (+/- 168,244,418)
#[bench]
#[ignore]
fn bench_unprocessed_packet_batches_randomized_beyond_limit(bencher: &mut Bencher) {
    let buffer_capacity = 1_000 * 128;
    let batch_count = 1_100;
    let packet_per_batch_count = 128;

    bencher.iter(|| {
        insert_packet_batches(buffer_capacity, batch_count, packet_per_batch_count, true);
    });
}
