// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use bencher::Bencher;
use flexbuffers::*;

fn push_vec_u64_to_map(b: &mut Bencher) {
    let va = vec![u64::max_value() - 10; 512];
    let vb = vec![u64::max_value() - 20; 512];
    let vc = vec![u64::max_value() - 30; 512];
    let mut n = 0;

    b.iter(|| {
        let mut fxb = Builder::default();
        let mut m = fxb.start_map();
        let mut ma = m.start_vector("a");
        for &a in va.iter() {
            ma.push(a);
        }
        ma.end_vector();
        let mut mb = m.start_vector("b");
        for &b in vb.iter() {
            mb.push(b);
        }
        mb.end_vector();
        let mut mc = m.start_vector("c");
        for &c in vc.iter() {
            mc.push(c);
        }
        mc.end_vector();
        m.end_map();
        n = fxb.view().len();
    });
    b.bytes = n as u64;
}
fn push_vec_u64_to_map_reused(b: &mut Bencher) {
    let va = vec![u64::max_value() - 10; 512];
    let vb = vec![u64::max_value() - 20; 512];
    let vc = vec![u64::max_value() - 30; 512];
    let mut fxb = Builder::default();
    let mut n = 0;
    let mut go = || {
        let mut m = fxb.start_map();
        let mut ma = m.start_vector("a");
        for &a in va.iter() {
            ma.push(a);
        }
        ma.end_vector();
        let mut mb = m.start_vector("b");
        for &b in vb.iter() {
            mb.push(b);
        }
        mb.end_vector();
        let mut mc = m.start_vector("c");
        for &c in vc.iter() {
            mc.push(c);
        }
        mc.end_vector();
        m.end_map();
        n = fxb.view().len();
    };
    go(); // warm up allocations.
    b.iter(go);
    b.bytes = n as u64;
}
fn push_vec_u64_to_map_direct(b: &mut Bencher) {
    let va = vec![u64::max_value() - 10; 512];
    let vb = vec![u64::max_value() - 20; 512];
    let vc = vec![u64::max_value() - 30; 512];
    let mut n = 0;

    b.iter(|| {
        let mut fxb = Builder::default();
        let mut m = fxb.start_map();
        m.push("a", &va);
        m.push("b", &vb);
        m.push("c", &vc);
        m.end_map();
        n = fxb.view().len();
    });
    b.bytes = n as u64;
}
fn push_vec_u64_to_map_direct_reused(b: &mut Bencher) {
    let va = vec![u64::max_value() - 10; 512];
    let vb = vec![u64::max_value() - 20; 512];
    let vc = vec![u64::max_value() - 30; 512];
    let mut n = 0;
    let mut fxb = Builder::default();
    let mut go = || {
        let mut m = fxb.start_map();
        m.push("a", &va);
        m.push("b", &vb);
        m.push("c", &vc);
        m.end_map();
        n = fxb.view().len();
    };
    go(); // warm up allocations.
    b.iter(go);
    b.bytes = n as u64;
}

fn push_vec_without_indirect(b: &mut Bencher) {
    let mut builder = Builder::default();
    let mut n = 0;
    let mut go = || {
        let mut b = builder.start_vector();
        for i in 0..1024u16 {
            b.push(i);
        }
        b.push(i64::max_value());
        b.end_vector();
        n = builder.view().len();
    };
    go(); // warm up allocations.
    b.iter(go);
    b.bytes = n as u64;
}
// This isn't actually faster than the alternative but it is a lot smaller.
// Based on the above benchmarks a lot of time is stuck in the `values` stack.
fn push_vec_with_indirect(b: &mut Bencher) {
    let mut builder = Builder::default();
    let mut n = 0;
    let mut go = || {
        let mut b = builder.start_vector();
        for i in 0..1024u16 {
            b.push(i);
        }
        b.push(IndirectInt(i64::max_value()));
        b.end_vector();
        n = builder.view().len();
    };
    go(); // warm up allocations.
    b.iter(go);
    b.bytes = n as u64;
}

fn example_map<'a>(m: &mut MapBuilder<'a>) {
    m.push("some_ints", &[256; 5]);
    m.push("some_uints", &[256u16; 5]);
    m.push("some_floats", &[256f32; 5]);
    m.push("some_strings", "muahahahahaha");
}
fn hundred_maps(b: &mut Bencher) {
    let mut builder = Builder::default();
    let mut n = 0;
    let mut go = || {
        let mut v = builder.start_vector();
        for _ in 0..100 {
            example_map(&mut v.start_map());
        }
        v.end_vector();
        n = builder.view().len();
    };
    go(); // Warm up allocations.
    b.iter(go);
    b.bytes = n as u64;
}
fn hundred_maps_pooled(b: &mut Bencher) {
    let mut builder = Builder::default();
    let mut n = 0;
    let mut go = || {
        let mut v = builder.start_vector();
        for _ in 0..100 {
            example_map(&mut v.start_map());
        }
        v.end_vector();
        n = builder.view().len();
    };
    go(); // Warm up allocations.
    b.iter(go);
    b.bytes = n as u64;
}
fn make_monster(mut monster: MapBuilder) {
    monster.push("type", "great orc");
    monster.push("age", 100u8);
    monster.push("name", "Mr. Orc");
    monster.push("coins", &[1, 25, 50, 100, 250]);
    monster.push("color", &[255u8, 0, 0, 0]);
    {
        let mut weapons = monster.start_vector("weapons");
        {
            let mut hammer = weapons.start_map();
            hammer.push("name", "hammer");
            hammer.push("damage type", "crush");
            hammer.push("damage", 20);
        }
        {
            let mut axe = weapons.start_map();
            axe.push("name", "Great Axe");
            axe.push("damage type", "slash");
            axe.push("damage", 30);
        }
    }
    {
        let mut sounds = monster.start_vector("sounds");
        sounds.push("grr");
        sounds.push("rawr");
        sounds.push("muahaha");
    }
}
fn serialize_monsters(b: &mut Bencher) {
    let mut builder = Builder::default();
    let mut n = 0;
    let mut go = || {
        let mut monsters = builder.start_vector();
        for _ in 0..100 {
            make_monster(monsters.start_map())
        }
        monsters.end_vector();
        n = builder.view().len();
    };
    go(); // Warm up allocations.
    b.iter(go);
    b.bytes = n as u64;
}
fn validate_monster(r: MapReader) {
    assert_eq!(r.idx("type").as_str(), "great orc");
    assert_eq!(r.idx("age").as_u8(), 100);
    assert_eq!(r.idx("name").as_str(), "Mr. Orc");
    assert!(r
        .idx("coins")
        .as_vector()
        .iter()
        .map(|c| c.as_i16())
        .eq([1, 25, 50, 100, 250].iter().cloned()));
    assert!(r
        .idx("color")
        .as_vector()
        .iter()
        .map(|c| c.as_u8())
        .eq([255, 0, 0, 0].iter().cloned()));

    let weapons = r.idx("weapons").as_vector();
    assert_eq!(weapons.len(), 2);

    let hammer = weapons.idx(0).as_map();
    assert_eq!(hammer.idx("name").as_str(), "hammer");
    assert_eq!(hammer.idx("damage type").as_str(), "crush");
    assert_eq!(hammer.idx("damage").as_u64(), 20);

    let axe = weapons.idx(1).as_map();
    assert_eq!(axe.idx("name").as_str(), "Great Axe");
    assert_eq!(axe.idx("damage type").as_str(), "slash");
    assert_eq!(axe.idx("damage").as_u64(), 30);

    assert!(r
        .idx("sounds")
        .as_vector()
        .iter()
        .map(|s| s.as_str())
        .eq(["grr", "rawr", "muahaha"].iter().cloned()));
}
fn read_monsters(b: &mut Bencher) {
    let mut builder = Builder::default();
    let mut monsters = builder.start_vector();
    for _ in 0..100 {
        make_monster(monsters.start_map());
    }
    monsters.end_vector();
    b.bytes = builder.view().len() as u64;
    let go = || {
        let r = Reader::get_root(builder.view()).unwrap().as_vector();
        assert_eq!(r.len(), 100);
        for i in 0..100 {
            validate_monster(r.idx(i).as_map());
        }
    };
    b.iter(go);
}

benchmark_group!(
    benches,
    push_vec_u64_to_map,
    push_vec_u64_to_map_reused,
    push_vec_u64_to_map_direct,
    push_vec_u64_to_map_direct_reused,
    push_vec_without_indirect,
    push_vec_with_indirect,
    hundred_maps,
    hundred_maps_pooled,
    serialize_monsters,
    read_monsters,
);
benchmark_main!(benches);
