// Record segmentation (ISO 28500:2017 clauses 5.20-5.22 and 7).
//
// A logical record that is too large to store in one physical record
// may be split into a sequence of physical records that share a
// WARC-Segment-Origin-ID: the first record has WARC-Segment-Number 1,
// later records increment the number, and the final record may carry
// WARC-Segment-Total-Length for the whole logical record.
//
// This module parses the three segmentation fields of one record
// (`segment_info`) and reports syntax violations; the sequence-level
// consistency checks (contiguity, increasing numbers, placement of
// the total length) live in `validate_archive` (validator.mbt).

///|
/// The parsed segmentation fields of a record. Absent fields are
/// `None`.
pub struct SegmentInfo {
  number : Int64?
  origin_id : String?
  total_length : Int64?
}

///|
/// Construct segment info from optional parts.
pub fn SegmentInfo::new(
  number : Int64?,
  origin_id : String?,
  total_length : Int64?,
) -> SegmentInfo {
  { number, origin_id, total_length }
}

///|
/// The WARC-Segment-Number value, when present.
pub fn SegmentInfo::number(self : SegmentInfo) -> Int64? {
  self.number
}

///|
/// The interior URI of WARC-Segment-Origin-ID, when present.
pub fn SegmentInfo::origin_id(self : SegmentInfo) -> String? {
  self.origin_id
}

///|
/// The WARC-Segment-Total-Length value, when present.
pub fn SegmentInfo::total_length(self : SegmentInfo) -> Int64? {
  self.total_length
}

///|
/// A structured segmentation error.
fn seg_err(index : Int64, kind : WarcErrorKind, context : String) -> WarcError {
  WarcError::new(WarcErrorStage::Segment, kind, 0L, index, context)
}

///|
/// Parse the segmentation fields of a record.
///
/// Fails when a field is present but malformed: a non-decimal or
/// non-positive WARC-Segment-Number, a WARC-Segment-Origin-ID that is
/// not a ``, or a non-decimal WARC-Segment-Total-Length.
pub fn WarcRecord::segment_info(
  self : WarcRecord,
  index : Int64,
) -> Result[SegmentInfo, WarcError] {
  let number = match field_first_of(self.fields, "WARC-Segment-Number") {
    Some(v) => {
      let vb = @utf8.encode(v)
      let parsed = parse_decimal(vb, 0, vb.length(), 19, "WARC-Segment-Number")
      match parsed {
        Ok(n) => {
          if n < 1 {
            return Err(
              seg_err(
                index,
                WarcErrorKind::InvalidFieldValue,
                "WARC-Segment-Number must be at least 1",
              ),
            )
          }
          Some(n)
        }
        Err(_) =>
          return Err(
            seg_err(
              index,
              WarcErrorKind::InvalidFieldValue,
              "WARC-Segment-Number is not a decimal integer",
            ),
          )
      }
    }
    None => (None : Int64?)
  }
  let origin_id = match field_first_of(self.fields, "WARC-Segment-Origin-ID") {
    Some(v) => {
      let parsed = parse_uri_ref(v, index)
      match parsed {
        Ok(interior) => Some(interior)
        Err(_) =>
          return Err(
            seg_err(
              index,
              WarcErrorKind::InvalidUri,
              "WARC-Segment-Origin-ID must be written as ",
            ),
          )
      }
    }
    None => (None : String?)
  }
  let total_length = match
    field_first_of(self.fields, "WARC-Segment-Total-Length") {
    Some(v) => {
      let vb = @utf8.encode(v)
      let parsed = parse_decimal(
        vb,
        0,
        vb.length(),
        19,
        "WARC-Segment-Total-Length",
      )
      match parsed {
        Ok(n) => Some(n)
        Err(_) =>
          return Err(
            seg_err(
              index,
              WarcErrorKind::InvalidFieldValue,
              "WARC-Segment-Total-Length is not a decimal integer",
            ),
          )
      }
    }
    None => (None : Int64?)
  }
  Ok(SegmentInfo::new(number, origin_id, total_length))
}

///|
/// Segmentation field syntax errors of one record, if any.
pub fn segment_errors(rec : WarcRecord, index : Int64) -> Array[WarcError] {
  let errs : Array[WarcError] = []
  let parsed = rec.segment_info(index)
  match parsed {
    Ok(_) => ()
    Err(e) => errs.push(e)
  }
  errs
}