use criterion::{criterion_group, criterion_main, Criterion};
use faststr::FastStr;
use pilota::{
    prost::bytes::BytesMut,
    thrift::{
        binary::TBinaryProtocol,
        binary_unsafe::{TBinaryUnsafeInputProtocol, TBinaryUnsafeOutputProtocol},
        Message,
    },
};
use rand::{distributions::Alphanumeric, Rng};

include!("../test_data/thrift/normal.rs");
include!("../test_data/unknown_fields.rs");

fn decode_encode_all_fields_safe(bytes: &[u8]) {
    let a = crate::normal::normal::ObjReq::decode(&mut TBinaryProtocol::new(
        &mut BytesMut::from(bytes).freeze(),
        true,
    ))
    .unwrap();

    let size = a.size(&mut TBinaryProtocol::new((), false));
    let mut linked_bytes = linkedbytes::LinkedBytes::with_capacity(size);
    a.encode(&mut TBinaryProtocol::new(&mut linked_bytes, true))
        .unwrap();
}

fn decode_encode_all_fields_unsafe(bytes: &[u8]) {
    let a = unsafe {
        crate::normal::normal::ObjReq::decode(&mut TBinaryUnsafeInputProtocol::new(
            &mut BytesMut::from(bytes).freeze(),
        ))
    }
    .unwrap();

    let size = a.size(&mut TBinaryProtocol::new((), false));
    let mut linked_bytes = linkedbytes::LinkedBytes::with_capacity(size);
    let buf = unsafe {
        let l = linked_bytes.bytes_mut().len();
        std::slice::from_raw_parts_mut(
            linked_bytes.bytes_mut().as_mut_ptr().offset(l as isize),
            linked_bytes.bytes_mut().capacity() - l,
        )
    };
    unsafe {
        a.encode(&mut TBinaryUnsafeOutputProtocol::new(
            &mut linked_bytes,
            buf,
            true,
        ))
        .unwrap();
    }
}

fn decode_encode_unknown_fields_safe(bytes: &[u8]) {
    let a = crate::unknown_fields::unknown_fields::ObjReq::decode(&mut TBinaryProtocol::new(
        &mut BytesMut::from(bytes).freeze(),
        true,
    ))
    .unwrap();

    let size = a.size(&mut TBinaryProtocol::new((), false));
    let mut linked_bytes = linkedbytes::LinkedBytes::with_capacity(size);
    a.encode(&mut TBinaryProtocol::new(&mut linked_bytes, true))
        .unwrap();
}

fn decode_encode_unknown_fields_unsafe(bytes: &[u8]) {
    let a = unsafe {
        crate::unknown_fields::unknown_fields::ObjReq::decode(&mut TBinaryUnsafeInputProtocol::new(
            &mut BytesMut::from(bytes).freeze(),
        ))
    }
    .unwrap();

    let size = a.size(&mut TBinaryProtocol::new((), false));
    let mut linked_bytes = linkedbytes::LinkedBytes::with_capacity(size);
    let buf = unsafe {
        let l = linked_bytes.bytes_mut().len();
        std::slice::from_raw_parts_mut(
            linked_bytes.bytes_mut().as_mut_ptr().offset(l as isize),
            linked_bytes.bytes_mut().capacity() - l,
        )
    };
    unsafe {
        a.encode(&mut TBinaryUnsafeOutputProtocol::new(
            &mut linked_bytes,
            buf,
            true,
        ))
        .unwrap();
    }
}

fn codegen(c: &mut Criterion) {
    let mut group = c.benchmark_group("Bench Unknown Fields");
    let lens = [16, 64, 128, 512, 2 * 1024, 128 * 1024, 10 * 128 * 1024];
    for len in lens {
        let a = prepare_obj_req(len);
        let size = a.size(&mut TBinaryProtocol::new((), false));
        let mut buf = BytesMut::with_capacity(size);
        a.encode(&mut TBinaryProtocol::new(&mut buf, false))
            .unwrap();
        group.bench_function(
            format!("TBinaryProtocol all_fields decode_encode {} bytes", len * 8),
            |b| b.iter(|| decode_encode_all_fields_safe(&buf)),
        );
        group.bench_function(
            format!(
                "TBinaryUnsafeProtocol all_fields decode_encode {} bytes",
                len * 8
            ),
            |b| b.iter(|| decode_encode_all_fields_unsafe(&buf)),
        );
        group.bench_function(
            format!(
                "TBinaryProtocol unknown_fields decode_encode {} bytes",
                len * 8
            ),
            |b| b.iter(|| decode_encode_unknown_fields_safe(&buf)),
        );
        group.bench_function(
            format!(
                "TBinaryUnsafeProtocol unknown_fields decode_encode {} bytes",
                len * 8
            ),
            |b| b.iter(|| decode_encode_unknown_fields_unsafe(&buf)),
        );
    }
}

criterion_group!(benches, codegen);
criterion_main!(benches);

fn prepare_obj_req(size: usize) -> crate::normal::normal::ObjReq {
    let mut req = crate::normal::normal::ObjReq::default();
    let sub_msg_1 = crate::normal::normal::SubMessage {
        value: Some(generate_message(size / 2)),
    };
    let sub_msg_2 = crate::normal::normal::SubMessage {
        value: Some(generate_message(size / 2)),
    };
    // size
    let sub_msg_list = vec![sub_msg_1, sub_msg_2];

    // 2 * size
    let msg = crate::normal::normal::Message {
        value: Some(generate_message(size)),
        sub_messages: Some(sub_msg_list.clone()),
    };

    let mut msg_map = std::collections::HashMap::new();
    msg_map.insert(
        crate::normal::normal::Message {
            value: None,
            sub_messages: Some(sub_msg_list.clone()),
        },
        crate::normal::normal::SubMessage {
            value: Some(generate_message(size)),
        },
    );

    let mut msg_set = std::collections::HashSet::new();
    msg_set.insert(msg.clone());

    req.msg = msg; // 2 * size
    req.msg_map = msg_map; // 2 * size
    req.msg_set = Some(msg_set); // 2 * size
    req.sub_msgs = sub_msg_list; // 2 * size

    req
}

fn generate_message(size: usize) -> FastStr {
    rand::thread_rng()
        .sample_iter(&Alphanumeric)
        .take(size)
        .map(char::from)
        .collect()
}
