use nom::{IResult as NomResult, Needed};
use nom::{le_u16 as parse_le_u16, le_u8 as parse_u8};
use crate::parsers::basic_data_types::{
  parse_bool_bits,
  parse_i32_bits,
  parse_le_fixed8_p8,
  parse_matrix,
  parse_s_rgb8,
  parse_straight_s_rgba8,
  parse_u16_bits,
  parse_u32_bits,
};
use crate::parsers::gradient::parse_gradient;
use swf_tree as ast;
#[derive(PartialEq, Eq, Clone, Copy, Ord, PartialOrd)]
pub enum ShapeVersion {
  Shape1,
  Shape2,
  Shape3,
  Shape4,
}
pub fn parse_glyph(input: &[u8]) -> NomResult<&[u8], ast::Glyph> {
  bits!(input, parse_glyph_bits)
}
pub fn parse_glyph_bits(input: (&[u8], usize)) -> NomResult<(&[u8], usize), ast::Glyph> {
  do_parse!(
    input,
    fill_bits: map!(apply!(parse_u32_bits, 4), |x| x as usize) >>
    line_bits: map!(apply!(parse_u32_bits, 4), |x| x as usize) >>
    
    records: apply!(parse_shape_record_string_bits, fill_bits, line_bits, ShapeVersion::Shape1) >>
    (ast::Glyph {
      records: records,
    })
  )
}
pub fn parse_shape(input: &[u8], version: ShapeVersion) -> NomResult<&[u8], ast::Shape> {
  bits!(input, apply!(parse_shape_bits, version))
}
pub fn parse_shape_bits(input: (&[u8], usize), version: ShapeVersion) -> NomResult<(&[u8], usize), ast::Shape> {
  do_parse!(
    input,
    styles: apply!(parse_shape_styles_bits, version) >>
    records: apply!(parse_shape_record_string_bits, styles.fill_bits, styles.line_bits, version) >>
    (ast::Shape {
      initial_styles: ast::ShapeStyles {
        fill: styles.fill,
        line: styles.line,
      },
      records: records,
    })
  )
}
pub struct ShapeStyles {
  pub fill: Vec<ast::FillStyle>,
  pub line: Vec<ast::LineStyle>,
  pub fill_bits: usize,
  pub line_bits: usize,
}
pub fn parse_shape_styles_bits(input: (&[u8], usize), version: ShapeVersion) -> NomResult<(&[u8], usize), ShapeStyles> {
  do_parse!(
    input,
    fill: bytes!(apply!(parse_fill_style_list, version)) >>
    line: bytes!(apply!(parse_line_style_list, version)) >>
    fill_bits: map!(apply!(parse_u32_bits, 4), |x| x as usize) >>
    line_bits: map!(apply!(parse_u32_bits, 4), |x| x as usize) >>
    (ShapeStyles {
      fill: fill,
      line: line,
      fill_bits: fill_bits,
      line_bits: line_bits,
    })
  )
}
pub fn parse_shape_record_string_bits(input: (&[u8], usize), mut fill_bits: usize, mut line_bits: usize, version: ShapeVersion) -> NomResult<(&[u8], usize), Vec<ast::ShapeRecord>> {
  let mut result: Vec<ast::ShapeRecord> = Vec::new();
  let mut current_input = input;
  loop {
    match parse_u16_bits(current_input, 6) {
      Ok((next_input, record_head)) => if record_head == 0 {
        current_input = next_input;
        break;
      },
      Err(::nom::Err::Incomplete(_)) => return Err(::nom::Err::Incomplete(Needed::Unknown)),
      Err(e) => return Err(e),
    };
    let is_edge = match parse_bool_bits(current_input) {
      Ok((next_input, is_edge)) => {
        current_input = next_input;
        is_edge
      }
      Err(::nom::Err::Incomplete(_)) => return Err(::nom::Err::Incomplete(Needed::Unknown)),
      Err(e) => return Err(e),
    };
    if is_edge {
      let is_straight_edge = match parse_bool_bits(current_input) {
        Ok((next_input, is_straight_edge)) => {
          current_input = next_input;
          is_straight_edge
        }
        Err(::nom::Err::Incomplete(_)) => return Err(::nom::Err::Incomplete(Needed::Unknown)),
        Err(e) => return Err(e),
      };
      let (next_input, edge) = if is_straight_edge {
        parse_straight_edge_bits(current_input)?
      } else {
         parse_curved_edge_bits(current_input)?
      };
      current_input = next_input;
      result.push(ast::ShapeRecord::Edge(edge));
    } else {
      let (next_input, (style_change, style_bits)) = parse_style_change_bits(current_input, fill_bits, line_bits, version)?;
      fill_bits = style_bits.0;
      line_bits = style_bits.1;
      result.push(ast::ShapeRecord::StyleChange(style_change));
      current_input = next_input;
    }
  }
  Ok((current_input, result))
}
pub fn parse_curved_edge_bits(input: (&[u8], usize)) -> NomResult<(&[u8], usize), ast::shape_records::Edge> {
  do_parse!(
    input,
    n_bits: map!(apply!(parse_u16_bits, 4), |x| (x as usize) + 2) >>
    control_x: apply!(parse_i32_bits, n_bits) >>
    control_y: apply!(parse_i32_bits, n_bits) >>
    anchor_x: apply!(parse_i32_bits, n_bits) >>
    anchor_y: apply!(parse_i32_bits, n_bits) >>
    (ast::shape_records::Edge {
      delta: ast::Vector2D { x: control_x + anchor_x, y: control_y + anchor_y },
      control_delta: Option::Some(ast::Vector2D {x: control_x, y: control_y}),
    })
  )
}
pub fn parse_straight_edge_bits(input: (&[u8], usize)) -> NomResult<(&[u8], usize), ast::shape_records::Edge> {
  do_parse!(
    input,
    n_bits: map!(apply!(parse_u16_bits, 4), |x| (x as usize) + 2) >>
    is_diagonal: call!(parse_bool_bits) >>
    is_vertical: map!(cond!(!is_diagonal, call!(parse_bool_bits)), |opt: Option<bool>| opt.unwrap_or_default()) >>
    delta_x: cond!(is_diagonal || !is_vertical, apply!(parse_i32_bits, n_bits)) >>
    delta_y: cond!(is_diagonal || is_vertical, apply!(parse_i32_bits, n_bits)) >>
    (ast::shape_records::Edge {
      delta: ast::Vector2D { x: delta_x.unwrap_or_default(), y: delta_y.unwrap_or_default() },
      control_delta: Option::None,
    })
  )
}
pub fn parse_style_change_bits(input: (&[u8], usize), fill_bits: usize, line_bits: usize, version: ShapeVersion) -> NomResult<(&[u8], usize), (ast::shape_records::StyleChange, (usize, usize))> {
  do_parse!(
    input,
    has_new_styles: parse_bool_bits >>
    change_line_style: parse_bool_bits >>
    change_right_fill: parse_bool_bits >>
    change_left_fill: parse_bool_bits >>
    has_move_to: parse_bool_bits >>
    move_to: cond!(has_move_to,
      do_parse!(
        move_to_bits: apply!(parse_u16_bits, 5) >>
        x: apply!(parse_i32_bits, move_to_bits as usize) >>
        y: apply!(parse_i32_bits, move_to_bits as usize) >>
        (ast::Vector2D {x: x, y: y})
      )
    ) >>
    left_fill: cond!(change_left_fill, apply!(parse_u16_bits, fill_bits)) >>
    right_fill: cond!(change_right_fill, apply!(parse_u16_bits, fill_bits)) >>
    line_style: cond!(change_line_style, apply!(parse_u16_bits, line_bits)) >>
    styles: map!(
      cond!(has_new_styles, apply!(parse_shape_styles_bits, version)),
      |styles| match styles {
        Option::Some(styles) => (
          Option::Some(ast::ShapeStyles {fill: styles.fill, line: styles.line}),
          styles.fill_bits,
          styles.line_bits,
        ),
        Option::None => (Option::None, fill_bits, line_bits),
      }
    ) >>
    ((
      ast::shape_records::StyleChange {
          move_to: move_to,
          left_fill: left_fill.map(|x| x as usize),
          right_fill: right_fill.map(|x| x as usize),
          line_style: line_style.map(|x| x as usize),
          new_styles: styles.0,
      },
      (styles.1, styles.2),
    ))
  )
}
pub fn parse_list_length(input: &[u8], allow_extended: bool) -> NomResult<&[u8], usize> {
  let (remaining_input, u8_len) = parse_u8(input)?;
  if u8_len == 0xff && allow_extended {
    parse_le_u16(remaining_input).map(|(i, x)| (i, x as usize))
  } else {
    Ok((remaining_input, u8_len as usize))
  }
}
pub fn parse_fill_style_list(input: &[u8], version: ShapeVersion) -> NomResult<&[u8], Vec<ast::FillStyle>> {
  length_count!(input, apply!(parse_list_length, version >= ShapeVersion::Shape2), apply!(parse_fill_style, version >= ShapeVersion::Shape3))
}
pub fn parse_fill_style(input: &[u8], with_alpha: bool) -> NomResult<&[u8], ast::FillStyle> {
  switch!(input, parse_u8,
    0x00 => map!(apply!(parse_solid_fill, with_alpha), |fill| ast::FillStyle::Solid(fill)) |
    0x10 => map!(apply!(parse_linear_gradient_fill, with_alpha), |fill| ast::FillStyle::LinearGradient(fill)) |
    0x12 => map!(apply!(parse_radial_gradient_fill, with_alpha), |fill| ast::FillStyle::RadialGradient(fill)) |
    0x13 => map!(apply!(parse_focal_gradient_fill, with_alpha), |fill| ast::FillStyle::FocalGradient(fill)) |
    0x40 => map!(apply!(parse_bitmap_fill, true, true), |fill| ast::FillStyle::Bitmap(fill)) |
    0x41 => map!(apply!(parse_bitmap_fill, false, true), |fill| ast::FillStyle::Bitmap(fill)) |
    0x42 => map!(apply!(parse_bitmap_fill, true, false), |fill| ast::FillStyle::Bitmap(fill)) |
    0x43 => map!(apply!(parse_bitmap_fill, false, false), |fill| ast::FillStyle::Bitmap(fill))
    
  )
}
pub fn parse_bitmap_fill(input: &[u8], repeating: bool, smoothed: bool) -> NomResult<&[u8], ast::fill_styles::Bitmap> {
  do_parse!(
    input,
    bitmap_id: parse_le_u16 >>
    matrix: parse_matrix >>
    (ast::fill_styles::Bitmap {
      bitmap_id: bitmap_id,
      matrix: matrix,
      repeating: repeating,
      smoothed: smoothed
    })
  )
}
pub fn parse_focal_gradient_fill(input: &[u8], with_alpha: bool) -> NomResult<&[u8], ast::fill_styles::FocalGradient> {
  do_parse!(
    input,
    matrix: parse_matrix >>
    gradient: apply!(parse_gradient, with_alpha) >>
    focal_point: parse_le_fixed8_p8 >>
    (ast::fill_styles::FocalGradient {
      matrix: matrix,
      gradient: gradient,
      focal_point: focal_point
    })
  )
}
pub fn parse_linear_gradient_fill(input: &[u8], with_alpha: bool) -> NomResult<&[u8], ast::fill_styles::LinearGradient> {
  do_parse!(
    input,
    matrix: parse_matrix >>
    gradient: apply!(parse_gradient, with_alpha) >>
    (ast::fill_styles::LinearGradient {
      matrix: matrix,
      gradient: gradient
    })
  )
}
pub fn parse_radial_gradient_fill(input: &[u8], with_alpha: bool) -> NomResult<&[u8], ast::fill_styles::RadialGradient> {
  do_parse!(
    input,
    matrix: parse_matrix >>
    gradient: apply!(parse_gradient, with_alpha) >>
    (ast::fill_styles::RadialGradient {
      matrix: matrix,
      gradient: gradient
    })
  )
}
pub fn parse_solid_fill(input: &[u8], with_alpha: bool) -> NomResult<&[u8], ast::fill_styles::Solid> {
  do_parse!(
    input,
    color: switch!(value!(with_alpha),
      true => call!(parse_straight_s_rgba8) |
      false => map!(parse_s_rgb8, |c| ast::StraightSRgba8 {r: c.r, g: c.g, b: c.b, a: 255})
    ) >>
    (ast::fill_styles::Solid {color: color})
  )
}
pub fn parse_line_style_list(input: &[u8], version: ShapeVersion) -> NomResult<&[u8], Vec<ast::LineStyle>> {
  length_count!(
    input,
    apply!(parse_list_length, version >= ShapeVersion::Shape2),
    switch!(value!(version >= ShapeVersion::Shape4),
      true => call!(parse_line_style2) |
      false => apply!(parse_line_style, version >= ShapeVersion::Shape3)
    )
  )
}
pub fn parse_line_style(input: &[u8], with_alpha: bool) -> NomResult<&[u8], ast::LineStyle> {
  do_parse!(
    input,
    width: parse_le_u16 >>
    color: switch!(value!(with_alpha),
      true => call!(parse_straight_s_rgba8) |
      false => map!(parse_s_rgb8, |c| ast::StraightSRgba8 {r: c.r, g: c.g, b: c.b, a: 255})
    ) >>
    (ast::LineStyle {
      width: width,
      start_cap: ast::CapStyle::Round,
      end_cap: ast::CapStyle::Round,
      join: ast::JoinStyle::Round,
      no_h_scale: false,
      no_v_scale: false,
      no_close: false,
      pixel_hinting: false,
      fill: ast::FillStyle::Solid(ast::fill_styles::Solid { color }),
    })
  )
}
pub fn parse_line_style2(input: &[u8]) -> NomResult<&[u8], ast::LineStyle> {
  fn cap_style_from_id(cap_style_id: u16) -> ast::CapStyle {
    match cap_style_id {
      0 => ast::CapStyle::Round,
      1 => ast::CapStyle::None,
      2 => ast::CapStyle::Square,
      _ => panic!("Unexpected cap style id"),
    }
  }
  do_parse!(
    input,
    width: parse_le_u16 >>
    flags: parse_le_u16 >>
    pixel_hinting: value!((flags & (1 << 0)) != 0) >>
    no_v_scale: value!((flags & (1 << 1)) != 0) >>
    no_h_scale: value!((flags & (1 << 2)) != 0) >>
    has_fill: value!((flags & (1 << 3)) != 0) >>
    join_style_id: value!((flags >> 4) & 0b11) >>
    start_cap_style_id: value!((flags >> 6) & 0b11) >>
    end_cap_style_id: value!((flags >> 8) & 0b11) >>
    no_close: value!((flags & (1 << 10)) != 0) >>
    
    start_cap: map!(value!(start_cap_style_id), cap_style_from_id) >>
    end_cap: map!(value!(end_cap_style_id), cap_style_from_id) >>
    join: switch!(value!(join_style_id),
      0 => value!(ast::JoinStyle::Round) |
      1 => value!(ast::JoinStyle::Bevel) |
      2 => do_parse!(
        limit: parse_le_u16 >>
        (ast::JoinStyle::Miter(ast::join_styles::Miter{limit}))
      )
    ) >>
    fill: switch!(value!(has_fill),
      true => apply!(parse_fill_style, true) |
      false => map!(parse_straight_s_rgba8, |color| ast::FillStyle::Solid(ast::fill_styles::Solid { color }))
    ) >>
    (ast::LineStyle {
      width: width,
      fill,
      pixel_hinting,
      no_v_scale,
      no_h_scale,
      no_close,
      join,
      start_cap,
      end_cap,
    })
  )
}