#![cfg(feature = "mp4")]
#![feature(test)]
extern crate test;

extern crate combine;
extern crate byteorder;

use test::{Bencher, black_box};

use std::str::from_utf8;

use byteorder::{BigEndian, ByteOrder};

use combine::*;
use combine::range::{range, take};

#[derive(Clone, PartialEq, Eq, Debug)]
struct FileType<'a> {
    major_brand: &'a str,
    major_brand_version: &'a [u8],
    compatible_brands: Vec<&'a str>,
}

#[derive(Clone, Debug)]
enum MP4Box<'a> {
    Ftyp(FileType<'a>),
    Moov,
    Mdat,
    Free,
    Skip,
    Wide,
    Unknown,
}

fn parse_mp4(data: &[u8]) -> Result<(Vec<MP4Box>, &[u8]), ParseError<&[u8]>> {
    let brand_name = || take(4).and_then(from_utf8);
    let filetype_box = (range(&b"ftyp"[..]), brand_name(), take(4), many(brand_name()))
        .map(|(_, m, v, c)| {
            MP4Box::Ftyp(FileType {
                major_brand: m,
                major_brand_version: v,
                compatible_brands: c,
            })
        });

    let mp4_box = take(4).map(BigEndian::read_u32).then(|offset| take(offset as usize - 4));
    let mut box_parser = filetype_box.or(range(&b"moov"[..]).map(|_| MP4Box::Moov))
        .or(range(&b"mdat"[..]).map(|_| MP4Box::Mdat))
        .or(range(&b"free"[..]).map(|_| MP4Box::Free))
        .or(range(&b"skip"[..]).map(|_| MP4Box::Skip))
        .or(range(&b"wide"[..]).map(|_| MP4Box::Wide))
        .or(value(MP4Box::Unknown));
    let data_interpreter = mp4_box.flat_map(|box_data| box_parser.parse(box_data).map(|t| t.0));

    many(data_interpreter).parse(data)
}

static MP4_SMALL: &'static [u8] = include_bytes!("small.mp4");

fn run_test(b: &mut Bencher, data: &[u8]) {
    b.iter(|| {
        match parse_mp4(data) {
            Ok(x) => black_box(x),
            Err(err) => panic!("{:?}", err),
        }
    });
}

#[bench]
fn mp4_small_test(b: &mut Bencher) {
    run_test(b, MP4_SMALL)
}
