///|
const VCD_MAGIC_1 : Byte = b'\xd6'

///|
const VCD_MAGIC_2 : Byte = b'\xc3'

///|
const VCD_MAGIC_3 : Byte = b'\xc4'

///|
const VCD_VERSION : Byte = b'\x00'

///|
const VCD_DECOMPRESS : Int = 0x01

///|
const VCD_CODETABLE : Int = 0x02

///|
const VCD_SOURCE : Int = 0x01

///|
const VCD_TARGET : Int = 0x02

///|
priv struct ParsedFileHeader {
  length : Int
}

///|
priv struct ParsedWindow {
  offset : Int
  indicator : Int
  source_size : Int
  source_position : Int
  delta_length : Int
  target_size : Int
  data_offset : Int
  instruction_offset : Int
  address_offset : Int
  data : Bytes
  instructions : Bytes
  addresses : Bytes
}

///|
priv struct ParsedDelta {
  header : ParsedFileHeader
  windows : Array[ParsedWindow]
}

///|
fn checked_section_sum(
  offset : Int,
  first : Int,
  second : Int,
  third : Int,
) -> Int raise VcdiffError {
  if first > MAX_PORTABLE_INT - second {
    raise IntegerOverflow(offset~)
  }
  let partial = first + second
  if partial > MAX_PORTABLE_INT - third {
    raise IntegerOverflow(offset~)
  }
  partial + third
}

///|
fn parse_file_header(cursor : ByteCursor) -> ParsedFileHeader raise VcdiffError {
  let start = cursor.offset()
  let first = cursor.read_byte()
  let second = cursor.read_byte()
  let third = cursor.read_byte()
  if first != VCD_MAGIC_1 || second != VCD_MAGIC_2 || third != VCD_MAGIC_3 {
    raise InvalidMagic(offset=start)
  }
  let version_offset = cursor.offset()
  let version = cursor.read_byte()
  if version != VCD_VERSION {
    raise UnsupportedVersion(offset=version_offset, version=version.to_int())
  }
  let indicator_offset = cursor.offset()
  let indicator = cursor.read_byte().to_int()
  if indicator > VCD_DECOMPRESS + VCD_CODETABLE {
    raise InvalidHeader(
      offset=indicator_offset,
      reason="reserved header indicator bits are set",
    )
  }
  if (indicator & VCD_DECOMPRESS) != 0 {
    raise UnsupportedFeature(
      offset=indicator_offset,
      feature="secondary compressor",
    )
  }
  if (indicator & VCD_CODETABLE) != 0 {
    raise UnsupportedFeature(
      offset=indicator_offset,
      feature="custom code table",
    )
  }
  { length: cursor.offset() - start }
}

///|
fn parse_window(cursor : ByteCursor) -> ParsedWindow raise VcdiffError {
  let window_offset = cursor.offset()
  let indicator_offset = cursor.offset()
  let indicator = cursor.read_byte().to_int()
  if indicator > VCD_SOURCE + VCD_TARGET {
    raise InvalidWindow(
      offset=indicator_offset,
      reason="reserved window indicator bits are set",
    )
  }
  if (indicator & VCD_SOURCE) != 0 && (indicator & VCD_TARGET) != 0 {
    raise InvalidWindow(
      offset=indicator_offset,
      reason="VCD_SOURCE and VCD_TARGET cannot both be set",
    )
  }
  let mut source_size = 0
  let mut source_position = 0
  if indicator != 0 {
    source_size = read_varint(cursor)
    source_position = read_varint(cursor)
  }
  let delta_length_offset = cursor.offset()
  let delta_length = read_varint(cursor)
  let delta = cursor.read_subcursor(delta_length)
  let target_size = read_varint(delta)
  let delta_indicator_offset = delta.offset()
  let delta_indicator = delta.read_byte().to_int()
  if delta_indicator != 0 {
    if (delta_indicator & 0x07) != 0 {
      raise UnsupportedFeature(
        offset=delta_indicator_offset,
        feature="secondary-compressed window section",
      )
    }
    raise InvalidWindow(
      offset=delta_indicator_offset,
      reason="reserved delta indicator bits are set",
    )
  }
  let data_length = read_varint(delta)
  let instruction_length = read_varint(delta)
  let address_length = read_varint(delta)
  let section_length = checked_section_sum(
    delta.offset(),
    data_length,
    instruction_length,
    address_length,
  )
  if section_length != delta.remaining() {
    raise LengthMismatch(
      offset=delta.offset(),
      expected=section_length,
      actual=delta.remaining(),
    )
  }
  let data_offset = delta.offset()
  let data = delta.read_exact(data_length)
  let instruction_offset = delta.offset()
  let instructions = delta.read_exact(instruction_length)
  let address_offset = delta.offset()
  let addresses = delta.read_exact(address_length)
  if delta.remaining() != 0 {
    raise LengthMismatch(
      offset=delta.offset(),
      expected=delta_length,
      actual=delta_length - delta.remaining(),
    )
  }
  if delta_length < 5 {
    raise InvalidWindow(
      offset=delta_length_offset,
      reason="delta encoding is shorter than its mandatory fields",
    )
  }
  {
    offset: window_offset,
    indicator,
    source_size,
    source_position,
    delta_length,
    target_size,
    data_offset,
    instruction_offset,
    address_offset,
    data,
    instructions,
    addresses,
  }
}

///|
fn parse_delta(delta : Bytes) -> ParsedDelta raise VcdiffError {
  let cursor = ByteCursor::new(delta)
  let header = parse_file_header(cursor)
  let windows : Array[ParsedWindow] = []
  while cursor.remaining() > 0 {
    windows.push(parse_window(cursor))
  }
  { header, windows }
}

///|
/// Validates the RFC file and window framing without executing instructions.
///
/// The returned value is the number of target windows found.
pub fn validate_structure(delta : Bytes) -> Int raise VcdiffError {
  let parsed = parse_delta(delta)
  let mut declared_target_bytes = 0
  for window in parsed.windows {
    if declared_target_bytes > MAX_PORTABLE_INT - window.target_size {
      raise IntegerOverflow(offset=window.offset)
    }
    declared_target_bytes += window.target_size
    ignore(window.indicator)
    ignore(window.source_size)
    ignore(window.source_position)
    ignore(window.delta_length)
    ignore(window.data_offset)
    ignore(window.instruction_offset)
    ignore(window.address_offset)
    ignore(window.data.length())
    ignore(window.instructions.length())
    ignore(window.addresses.length())
  }
  ignore(parsed.header.length)
  parsed.windows.length()
}