#![allow(dead_code)]

#[macro_use]
extern crate nom;

use nom::{
  IResult, Needed, Err,
  error::ErrorKind,
  number::streaming::{be_u16, be_u32, be_u64, be_f32}
};

use std::str;

fn mp4_box(input: &[u8]) -> IResult<&[u8], &[u8]> {
  match be_u32(input) {
    Ok((i, offset)) => {
      let sz: usize = offset as usize;
      if i.len() >= sz - 4 {
        Ok((&i[(sz - 4)..], &i[0..(sz - 4)]))
      } else {
        Err(Err::Incomplete(Needed::Size(offset as usize + 4)))
      }
    }
    Err(e) => Err(e),
  }
}

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

#[cfg_attr(rustfmt, rustfmt_skip)]
#[allow(non_snake_case)]
#[derive(Debug,Clone)]
pub struct Mvhd32 {
  version_flags: u32, // actually:
  // version: u8,
  // flags: u24       // 3 bytes
  created_date:  u32,
  modified_date: u32,
  scale:         u32,
  duration:      u32,
  speed:         f32,
  volume:        u16, // actually a 2 bytes decimal
  /* 10 bytes reserved */
  scaleA:        f32,
  rotateB:       f32,
  angleU:        f32,
  rotateC:       f32,
  scaleD:        f32,
  angleV:        f32,
  positionX:     f32,
  positionY:     f32,
  scaleW:        f32,
  preview:       u64,
  poster:        u32,
  selection:     u64,
  current_time:  u32,
  track_id:      u32
}

#[cfg_attr(rustfmt, rustfmt_skip)]
#[allow(non_snake_case)]
#[derive(Debug,Clone)]
pub struct Mvhd64 {
  version_flags: u32, // actually:
  // version: u8,
  // flags: u24       // 3 bytes
  created_date:  u64,
  modified_date: u64,
  scale:         u32,
  duration:      u64,
  speed:         f32,
  volume:        u16, // actually a 2 bytes decimal
  /* 10 bytes reserved */
  scaleA:        f32,
  rotateB:       f32,
  angleU:        f32,
  rotateC:       f32,
  scaleD:        f32,
  angleV:        f32,
  positionX:     f32,
  positionY:     f32,
  scaleW:        f32,
  preview:       u64,
  poster:        u32,
  selection:     u64,
  current_time:  u32,
  track_id:      u32
}

#[allow(non_snake_case)]
named!(mvhd32 <&[u8], MvhdBox>,
  do_parse!(
  version_flags: be_u32 >>
  created_date:  be_u32 >>
  modified_date: be_u32 >>
  scale:         be_u32 >>
  duration:      be_u32 >>
  speed:         be_f32 >>
  volume:        be_u16 >> // actually a 2 bytes decimal
              take!(10) >>
  scale_a:       be_f32 >>
  rotate_b:      be_f32 >>
  angle_u:       be_f32 >>
  rotate_c:      be_f32 >>
  scale_d:       be_f32 >>
  angle_v:       be_f32 >>
  position_x:    be_f32 >>
  position_y:    be_f32 >>
  scale_w:       be_f32 >>
  preview:       be_u64 >>
  poster:        be_u32 >>
  selection:     be_u64 >>
  current_time:  be_u32 >>
  track_id:      be_u32 >>
  (
    MvhdBox::M32(Mvhd32 {
      version_flags: version_flags,
      created_date:  created_date,
      modified_date: modified_date,
      scale:         scale,
      duration:      duration,
      speed:         speed,
      volume:        volume,
      scaleA:        scale_a,
      rotateB:       rotate_b,
      angleU:        angle_u,
      rotateC:       rotate_c,
      scaleD:        scale_d,
      angleV:        angle_v,
      positionX:     position_x,
      positionY:     position_y,
      scaleW:        scale_w,
      preview:       preview,
      poster:        poster,
      selection:     selection,
      current_time:  current_time,
      track_id:      track_id
    })
  ))
);

#[allow(non_snake_case)]
named!(mvhd64 <&[u8], MvhdBox>,
  do_parse!(
  version_flags: be_u32 >>
  created_date:  be_u64 >>
  modified_date: be_u64 >>
  scale:         be_u32 >>
  duration:      be_u64 >>
  speed:         be_f32 >>
  volume:        be_u16 >> // actually a 2 bytes decimal
              take!(10) >>
  scale_a:       be_f32 >>
  rotate_b:      be_f32 >>
  angle_u:       be_f32 >>
  rotate_c:      be_f32 >>
  scale_d:       be_f32 >>
  angle_v:       be_f32 >>
  position_x:    be_f32 >>
  position_y:    be_f32 >>
  scale_w:       be_f32 >>
  preview:       be_u64 >>
  poster:        be_u32 >>
  selection:     be_u64 >>
  current_time:  be_u32 >>
  track_id:      be_u32 >>
  (
    MvhdBox::M64(Mvhd64 {
      version_flags: version_flags,
      created_date:  created_date,
      modified_date: modified_date,
      scale:         scale,
      duration:      duration,
      speed:         speed,
      volume:        volume,
      scaleA:        scale_a,
      rotateB:       rotate_b,
      angleU:        angle_u,
      rotateC:       rotate_c,
      scaleD:        scale_d,
      angleV:        angle_v,
      positionX:     position_x,
      positionY:     position_y,
      scaleW:        scale_w,
      preview:       preview,
      poster:        poster,
      selection:     selection,
      current_time:  current_time,
      track_id:      track_id
    })
  ))
);

#[derive(Debug, Clone)]
pub enum MvhdBox {
  M32(Mvhd32),
  M64(Mvhd64),
}

#[derive(Debug, Clone)]
pub enum MoovBox {
  Mdra,
  Dref,
  Cmov,
  Rmra,
  Iods,
  Mvhd(MvhdBox),
  Clip,
  Trak,
  Udta,
}

#[derive(Debug)]
enum MP4BoxType {
  Ftyp,
  Moov,
  Mdat,
  Free,
  Skip,
  Wide,
  Mdra,
  Dref,
  Cmov,
  Rmra,
  Iods,
  Mvhd,
  Clip,
  Trak,
  Udta,
  Unknown,
}

#[derive(Debug)]
struct MP4BoxHeader {
  length: u32,
  tag: MP4BoxType,
}

named!(brand_name<&[u8],&str>, map_res!(take!(4), str::from_utf8));

named!(filetype_parser<&[u8], FileType>,
  do_parse!(
    m: brand_name          >>
    v: take!(4)            >>
    c: many0!(brand_name)  >>
    (FileType{ major_brand: m, major_brand_version:v, compatible_brands: c })
  )
);

fn mvhd_box(input: &[u8]) -> IResult<&[u8], MvhdBox> {
  let res = if input.len() < 100 {
    Err(Err::Incomplete(Needed::Size(100)))
  } else if input.len() == 100 {
    mvhd32(input)
  } else if input.len() == 112 {
    mvhd64(input)
  } else {
    Err(Err::Error(error_position!(input, ErrorKind::TooLarge)))
  };
  println!("res: {:?}", res);
  res
}

fn unknown_box_type(input: &[u8]) -> IResult<&[u8], MP4BoxType> {
  Ok((input, MP4BoxType::Unknown))
}

//named!(box_type<&[u8], MP4BoxType>,
fn box_type(input: &[u8]) -> IResult<&[u8], MP4BoxType> {
  alt!(input,
    tag!("ftyp") => { |_| MP4BoxType::Ftyp } |
    tag!("moov") => { |_| MP4BoxType::Moov } |
    tag!("mdat") => { |_| MP4BoxType::Mdat } |
    tag!("free") => { |_| MP4BoxType::Free } |
    tag!("skip") => { |_| MP4BoxType::Skip } |
    tag!("wide") => { |_| MP4BoxType::Wide } |
    unknown_box_type
  )
}

// warning, an alt combinator with 9 branches containing a tag combinator
// can make the compilation very slow. Use functions as sub parsers,
// or split into multiple alt! parsers if it gets slow
named!(moov_type<&[u8], MP4BoxType>,
  alt!(
    tag!("mdra") => { |_| MP4BoxType::Mdra } |
    tag!("dref") => { |_| MP4BoxType::Dref } |
    tag!("cmov") => { |_| MP4BoxType::Cmov } |
    tag!("rmra") => { |_| MP4BoxType::Rmra } |
    tag!("iods") => { |_| MP4BoxType::Iods } |
    tag!("mvhd") => { |_| MP4BoxType::Mvhd } |
    tag!("clip") => { |_| MP4BoxType::Clip } |
    tag!("trak") => { |_| MP4BoxType::Trak } |
    tag!("udta") => { |_| MP4BoxType::Udta }
  )
);

named!(box_header<&[u8],MP4BoxHeader>,
  do_parse!(
    length: be_u32 >>
    tag: box_type  >>
    (MP4BoxHeader{ length: length, tag: tag})
  )
);

named!(moov_header<&[u8],MP4BoxHeader>,
  do_parse!(
    length: be_u32 >>
    tag: moov_type >>
    (MP4BoxHeader{ length: length, tag: tag})
  )
);

