#![feature(test)]

extern crate test;

use std::io;

use serde::{de::DeserializeOwned, Deserialize, Serialize};
use test::Bencher;

use csv::{
    ByteRecord, Reader, ReaderBuilder, StringRecord, Trim, Writer,
    WriterBuilder,
};

static NFL: &str = include_str!("../examples/data/bench/nfl.csv");
static GAME: &str = include_str!("../examples/data/bench/game.csv");
static POP: &str = include_str!("../examples/data/bench/worldcitiespop.csv");
static MBTA: &str =
    include_str!("../examples/data/bench/gtfs-mbta-stop-times.csv");

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct NFLRowOwned {
    gameid: String,
    qtr: i32,
    min: Option<i32>,
    sec: Option<i32>,
    off: String,
    def: String,
    down: Option<i32>,
    togo: Option<i32>,
    ydline: Option<i32>,
    description: String,
    offscore: i32,
    defscore: i32,
    season: i32,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct NFLRowBorrowed<'a> {
    gameid: &'a str,
    qtr: i32,
    min: Option<i32>,
    sec: Option<i32>,
    off: &'a str,
    def: &'a str,
    down: Option<i32>,
    togo: Option<i32>,
    ydline: Option<i32>,
    description: &'a str,
    offscore: i32,
    defscore: i32,
    season: i32,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct GAMERowOwned(String, String, String, String, i32, String);

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct GAMERowBorrowed<'a>(&'a str, &'a str, &'a str, &'a str, i32, &'a str);

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
struct POPRowOwned {
    country: String,
    city: String,
    accent_city: String,
    region: String,
    population: Option<i32>,
    latitude: f64,
    longitude: f64,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
struct POPRowBorrowed<'a> {
    country: &'a str,
    city: &'a str,
    accent_city: &'a str,
    region: &'a str,
    population: Option<i32>,
    latitude: f64,
    longitude: f64,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct MBTARowOwned {
    trip_id: String,
    arrival_time: String,
    departure_time: String,
    stop_id: String,
    stop_sequence: i32,
    stop_headsign: String,
    pickup_type: i32,
    drop_off_type: i32,
    timepoint: i32,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct MBTARowBorrowed<'a> {
    trip_id: &'a str,
    arrival_time: &'a str,
    departure_time: &'a str,
    stop_id: &'a str,
    stop_sequence: i32,
    stop_headsign: &'a str,
    pickup_type: i32,
    drop_off_type: i32,
    timepoint: i32,
}

#[derive(Default)]
struct ByteCounter {
    count: usize,
}
impl io::Write for ByteCounter {
    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
        self.count += data.len();
        Ok(data.len())
    }
    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

macro_rules! bench {
    ($name:ident, $data:ident, $counter:ident, $result:expr) => {
        #[bench]
        fn $name(b: &mut Bencher) {
            let data = $data.as_bytes();
            b.bytes = data.len() as u64;
            b.iter(|| {
                let mut rdr =
                    ReaderBuilder::new().has_headers(false).from_reader(data);
                assert_eq!($counter(&mut rdr), $result);
            })
        }
    };
}

macro_rules! bench_trimmed {
    ($name:ident, $data:ident, $counter:ident, $result:expr) => {
        #[bench]
        fn $name(b: &mut Bencher) {
            let data = $data.as_bytes();
            b.bytes = data.len() as u64;
            b.iter(|| {
                let mut rdr = ReaderBuilder::new()
                    .has_headers(false)
                    .trim(Trim::All)
                    .from_reader(data);
                assert_eq!($counter(&mut rdr), $result);
            })
        }
    };
}

macro_rules! bench_serde {
    (no_headers,
     $name_de:ident, $name_ser:ident, $data:ident, $counter:ident, $type:ty, $result:expr) => {
        #[bench]
        fn $name_de(b: &mut Bencher) {
            let data = $data.as_bytes();
            b.bytes = data.len() as u64;
            b.iter(|| {
                let mut rdr =
                    ReaderBuilder::new().has_headers(false).from_reader(data);
                assert_eq!($counter::<_, $type>(&mut rdr), $result);
            })
        }
        #[bench]
        fn $name_ser(b: &mut Bencher) {
            let data = $data.as_bytes();
            let values = ReaderBuilder::new()
                .has_headers(false)
                .from_reader(data)
                .deserialize()
                .collect::<Result<Vec<$type>, _>>()
                .unwrap();

            let do_it = || {
                let mut counter = ByteCounter::default();
                {
                    let mut wtr = WriterBuilder::new()
                        .has_headers(false)
                        .from_writer(&mut counter);
                    for val in &values {
                        wtr.serialize(val).unwrap();
                    }
                }
                counter.count
            };
            b.bytes = do_it() as u64;
            b.iter(do_it)
        }
    };
    ($name_de:ident, $name_ser:ident, $data:ident, $counter:ident, $type:ty, $result:expr) => {
        #[bench]
        fn $name_de(b: &mut Bencher) {
            let data = $data.as_bytes();
            b.bytes = data.len() as u64;
            b.iter(|| {
                let mut rdr =
                    ReaderBuilder::new().has_headers(true).from_reader(data);
                assert_eq!($counter::<_, $type>(&mut rdr), $result);
            })
        }
        #[bench]
        fn $name_ser(b: &mut Bencher) {
            let data = $data.as_bytes();
            let values = ReaderBuilder::new()
                .has_headers(true)
                .from_reader(data)
                .deserialize()
                .collect::<Result<Vec<$type>, _>>()
                .unwrap();

            let do_it = || {
                let mut counter = ByteCounter::default();
                {
                    let mut wtr = WriterBuilder::new()
                        .has_headers(true)
                        .from_writer(&mut counter);
                    for val in &values {
                        wtr.serialize(val).unwrap();
                    }
                }
                counter.count
            };
            b.bytes = do_it() as u64;
            b.iter(do_it)
        }
    };
}

macro_rules! bench_serde_borrowed_bytes {
    ($name:ident, $data:ident, $type:ty, $headers:expr, $result:expr) => {
        #[bench]
        fn $name(b: &mut Bencher) {
            let data = $data.as_bytes();
            b.bytes = data.len() as u64;
            b.iter(|| {
                let mut rdr = ReaderBuilder::new()
                    .has_headers($headers)
                    .from_reader(data);
                let mut count = 0;
                let mut rec = ByteRecord::new();
                while rdr.read_byte_record(&mut rec).unwrap() {
                    let _: $type = rec.deserialize(None).unwrap();
                    count += 1;
                }
                count
            })
        }
    };
}

macro_rules! bench_serde_borrowed_str {
    ($name:ident, $data:ident, $type:ty, $headers:expr, $result:expr) => {
        #[bench]
        fn $name(b: &mut Bencher) {
            let data = $data.as_bytes();
            b.bytes = data.len() as u64;
            b.iter(|| {
                let mut rdr = ReaderBuilder::new()
                    .has_headers($headers)
                    .from_reader(data);
                let mut count = 0;
                let mut rec = StringRecord::new();
                while rdr.read_record(&mut rec).unwrap() {
                    let _: $type = rec.deserialize(None).unwrap();
                    count += 1;
                }
                count
            })
        }
    };
}

bench_serde!(
    count_nfl_deserialize_owned_bytes,
    count_nfl_serialize_owned_bytes,
    NFL,
    count_deserialize_owned_bytes,
    NFLRowOwned,
    9999
);
bench_serde!(
    count_nfl_deserialize_owned_str,
    count_nfl_serialize_owned_str,
    NFL,
    count_deserialize_owned_str,
    NFLRowOwned,
    9999
);
bench_serde_borrowed_bytes!(
    count_nfl_deserialize_borrowed_bytes,
    NFL,
    NFLRowBorrowed,
    true,
    9999
);
bench_serde_borrowed_str!(
    count_nfl_deserialize_borrowed_str,
    NFL,
    NFLRowBorrowed,
    true,
    9999
);
bench!(count_nfl_iter_bytes, NFL, count_iter_bytes, 130000);
bench_trimmed!(count_nfl_iter_bytes_trimmed, NFL, count_iter_bytes, 130000);
bench!(count_nfl_iter_str, NFL, count_iter_str, 130000);
bench_trimmed!(count_nfl_iter_str_trimmed, NFL, count_iter_str, 130000);
bench!(count_nfl_read_bytes, NFL, count_read_bytes, 130000);
bench!(count_nfl_read_str, NFL, count_read_str, 130000);
bench_serde!(
    no_headers,
    count_game_deserialize_owned_bytes,
    count_game_serialize_owned_bytes,
    GAME,
    count_deserialize_owned_bytes,
    GAMERowOwned,
    100000
);
bench_serde!(
    no_headers,
    count_game_deserialize_owned_str,
    count_game_serialize_owned_str,
    GAME,
    count_deserialize_owned_str,
    GAMERowOwned,
    100000
);
bench_serde_borrowed_bytes!(
    count_game_deserialize_borrowed_bytes,
    GAME,
    GAMERowBorrowed,
    true,
    100000
);
bench_serde_borrowed_str!(
    count_game_deserialize_borrowed_str,
    GAME,
    GAMERowBorrowed,
    true,
    100000
);
bench!(count_game_iter_bytes, GAME, count_iter_bytes, 600000);
bench!(count_game_iter_str, GAME, count_iter_str, 600000);
bench!(count_game_read_bytes, GAME, count_read_bytes, 600000);
bench!(count_game_read_str, GAME, count_read_str, 600000);
bench_serde!(
    count_pop_deserialize_owned_bytes,
    count_pop_serialize_owned_bytes,
    POP,
    count_deserialize_owned_bytes,
    POPRowOwned,
    20000
);
bench_serde!(
    count_pop_deserialize_owned_str,
    count_pop_serialize_owned_str,
    POP,
    count_deserialize_owned_str,
    POPRowOwned,
    20000
);
bench_serde_borrowed_bytes!(
    count_pop_deserialize_borrowed_bytes,
    POP,
    POPRowBorrowed,
    true,
    20000
);
bench_serde_borrowed_str!(
    count_pop_deserialize_borrowed_str,
    POP,
    POPRowBorrowed,
    true,
    20000
);
bench!(count_pop_iter_bytes, POP, count_iter_bytes, 140007);
bench!(count_pop_iter_str, POP, count_iter_str, 140007);
bench!(count_pop_read_bytes, POP, count_read_bytes, 140007);
bench!(count_pop_read_str, POP, count_read_str, 140007);
bench_serde!(
    count_mbta_deserialize_owned_bytes,
    count_mbta_serialize_owned_bytes,
    MBTA,
    count_deserialize_owned_bytes,
    MBTARowOwned,
    9999
);
bench_serde!(
    count_mbta_deserialize_owned_str,
    count_mbta_serialize_owned_str,
    MBTA,
    count_deserialize_owned_str,
    MBTARowOwned,
    9999
);
bench_serde_borrowed_bytes!(
    count_mbta_deserialize_borrowed_bytes,
    MBTA,
    MBTARowBorrowed,
    true,
    9999
);
bench_serde_borrowed_str!(
    count_mbta_deserialize_borrowed_str,
    MBTA,
    MBTARowBorrowed,
    true,
    9999
);
bench!(count_mbta_iter_bytes, MBTA, count_iter_bytes, 90000);
bench!(count_mbta_iter_str, MBTA, count_iter_str, 90000);
bench!(count_mbta_read_bytes, MBTA, count_read_bytes, 90000);
bench!(count_mbta_read_str, MBTA, count_read_str, 90000);

macro_rules! bench_write {
    ($name:ident, $data:ident) => {
        #[bench]
        fn $name(b: &mut Bencher) {
            let data = $data.as_bytes();
            b.bytes = data.len() as u64;
            let records = collect_records(data);

            b.iter(|| {
                let mut wtr = Writer::from_writer(vec![]);
                for r in &records {
                    wtr.write_record(r).unwrap();
                }
                assert!(wtr.flush().is_ok());
            })
        }
    };
}

macro_rules! bench_write_bytes {
    ($name:ident, $data:ident) => {
        #[bench]
        fn $name(b: &mut Bencher) {
            let data = $data.as_bytes();
            b.bytes = data.len() as u64;
            let records = collect_records(data);

            b.iter(|| {
                let mut wtr = Writer::from_writer(vec![]);
                for r in &records {
                    wtr.write_byte_record(r).unwrap();
                }
                assert!(wtr.flush().is_ok());
            })
        }
    };
}

bench_write!(write_nfl_record, NFL);
bench_write_bytes!(write_nfl_bytes, NFL);

fn count_deserialize_owned_bytes<R, D>(rdr: &mut Reader<R>) -> u64
where
    R: io::Read,
    D: DeserializeOwned,
{
    let mut count = 0;
    let mut rec = ByteRecord::new();
    while rdr.read_byte_record(&mut rec).unwrap() {
        let _: D = rec.deserialize(None).unwrap();
        count += 1;
    }
    count
}

fn count_deserialize_owned_str<R, D>(rdr: &mut Reader<R>) -> u64
where
    R: io::Read,
    D: DeserializeOwned,
{
    let mut count = 0;
    for rec in rdr.deserialize::<D>() {
        let _ = rec.unwrap();
        count += 1;
    }
    count
}

fn count_iter_bytes<R: io::Read>(rdr: &mut Reader<R>) -> u64 {
    let mut count = 0;
    for rec in rdr.byte_records() {
        count += rec.unwrap().len() as u64;
    }
    count
}

fn count_iter_str<R: io::Read>(rdr: &mut Reader<R>) -> u64 {
    let mut count = 0;
    for rec in rdr.records() {
        count += rec.unwrap().len() as u64;
    }
    count
}

fn count_read_bytes<R: io::Read>(rdr: &mut Reader<R>) -> u64 {
    let mut count = 0;
    let mut rec = ByteRecord::new();
    while rdr.read_byte_record(&mut rec).unwrap() {
        count += rec.len() as u64;
    }
    count
}

fn count_read_str<R: io::Read>(rdr: &mut Reader<R>) -> u64 {
    let mut count = 0;
    let mut rec = StringRecord::new();
    while rdr.read_record(&mut rec).unwrap() {
        count += rec.len() as u64;
    }
    count
}

fn collect_records(data: &[u8]) -> Vec<ByteRecord> {
    let mut rdr = ReaderBuilder::new().has_headers(false).from_reader(data);
    rdr.byte_records().collect::<Result<Vec<_>, _>>().unwrap()
}
