///|
pub struct ReviewRequest {
  label_value : String
  source_value : String
  profile_value : ValidationProfile
  rewrite_plan_value : DocumentRewritePlan
} derive(Eq, Debug)

///|
pub fn ReviewRequest::new(label : String, source : String) -> ReviewRequest {
  {
    label_value: label,
    source_value: source,
    profile_value: ValidationProfile::generic_cnc(),
    rewrite_plan_value: DocumentRewritePlan::canonical(),
  }
}

///|
pub fn ReviewRequest::label(self : ReviewRequest) -> String {
  self.label_value
}

///|
pub fn ReviewRequest::source(self : ReviewRequest) -> String {
  self.source_value
}

///|
pub fn ReviewRequest::profile(self : ReviewRequest) -> ValidationProfile {
  self.profile_value
}

///|
pub fn ReviewRequest::rewrite_plan(self : ReviewRequest) -> DocumentRewritePlan {
  self.rewrite_plan_value
}

///|
pub fn ReviewRequest::with_profile(
  self : ReviewRequest,
  profile : ValidationProfile,
) -> ReviewRequest {
  { ..self, profile_value: profile }
}

///|
pub fn ReviewRequest::with_rewrite_plan(
  self : ReviewRequest,
  plan : DocumentRewritePlan,
) -> ReviewRequest {
  { ..self, rewrite_plan_value: plan }
}

///|
pub struct ReviewOutcome {
  label_value : String
  document_value : GCodeDocument?
  index_value : ProgramIndex?
  validation_value : DocumentValidation?
  rewrite_value : DocumentRewriteResult?
  parse_error_value : GCodeError?
} derive(Eq, Debug)

///|
pub fn ReviewOutcome::label(self : ReviewOutcome) -> String {
  self.label_value
}

///|
pub fn ReviewOutcome::document(self : ReviewOutcome) -> GCodeDocument? {
  self.document_value
}

///|
pub fn ReviewOutcome::index(self : ReviewOutcome) -> ProgramIndex? {
  self.index_value
}

///|
pub fn ReviewOutcome::validation(self : ReviewOutcome) -> DocumentValidation? {
  self.validation_value
}

///|
pub fn ReviewOutcome::rewrite(self : ReviewOutcome) -> DocumentRewriteResult? {
  self.rewrite_value
}

///|
pub fn ReviewOutcome::parse_error(self : ReviewOutcome) -> GCodeError? {
  self.parse_error_value
}

///|
pub fn ReviewOutcome::parsed(self : ReviewOutcome) -> Bool {
  self.document_value is Some(_)
}

///|
pub fn ReviewOutcome::accepted(self : ReviewOutcome) -> Bool {
  match self.validation_value {
    Some(validation) => validation.accepted() && self.rewrite_value is Some(_)
    None => false
  }
}

///|
pub fn ReviewOutcome::error_count(self : ReviewOutcome) -> Int {
  match self.validation_value {
    Some(validation) => validation.summary().error_count()
    None => if self.parse_error_value is Some(_) { 1 } else { 0 }
  }
}

///|
pub fn ReviewOutcome::warning_count(self : ReviewOutcome) -> Int {
  match self.validation_value {
    Some(validation) => validation.summary().warning_count()
    None => 0
  }
}

///|
pub fn ReviewOutcome::changed_lines(self : ReviewOutcome) -> Int {
  match self.rewrite_value {
    Some(rewrite) => rewrite.changed_lines()
    None => 0
  }
}

///|
pub fn review_source(
  request : ReviewRequest,
  max_lines? : Int = 100000,
) -> ReviewOutcome {
  let document = match parse_gcode_document(request.source(), max_lines~) {
    Ok(value) => value
    Err(error) =>
      return {
        label_value: request.label(),
        document_value: None,
        index_value: None,
        validation_value: None,
        rewrite_value: None,
        parse_error_value: Some(error),
      }
  }
  let index = index_document(document)
  let validation = validate_document(document, profile=request.profile())
  let rewrite = match rewrite_document(document, plan=request.rewrite_plan()) {
    Ok(value) => Some(value)
    Err(_) => None
  }
  {
    label_value: request.label(),
    document_value: Some(document),
    index_value: Some(index),
    validation_value: Some(validation),
    rewrite_value: rewrite,
    parse_error_value: None,
  }
}

///|
pub struct ReviewBatchSummary {
  item_count_value : Int
  parsed_count_value : Int
  accepted_count_value : Int
  rejected_count_value : Int
  parse_error_count_value : Int
  diagnostic_error_count_value : Int
  warning_count_value : Int
  changed_line_count_value : Int
  command_count_value : Int
} derive(Eq, Debug)

///|
pub fn ReviewBatchSummary::item_count(self : ReviewBatchSummary) -> Int {
  self.item_count_value
}

///|
pub fn ReviewBatchSummary::parsed_count(self : ReviewBatchSummary) -> Int {
  self.parsed_count_value
}

///|
pub fn ReviewBatchSummary::accepted_count(self : ReviewBatchSummary) -> Int {
  self.accepted_count_value
}

///|
pub fn ReviewBatchSummary::rejected_count(self : ReviewBatchSummary) -> Int {
  self.rejected_count_value
}

///|
pub fn ReviewBatchSummary::parse_error_count(self : ReviewBatchSummary) -> Int {
  self.parse_error_count_value
}

///|
pub fn ReviewBatchSummary::diagnostic_error_count(
  self : ReviewBatchSummary,
) -> Int {
  self.diagnostic_error_count_value
}

///|
pub fn ReviewBatchSummary::warning_count(self : ReviewBatchSummary) -> Int {
  self.warning_count_value
}

///|
pub fn ReviewBatchSummary::changed_line_count(self : ReviewBatchSummary) -> Int {
  self.changed_line_count_value
}

///|
pub fn ReviewBatchSummary::command_count(self : ReviewBatchSummary) -> Int {
  self.command_count_value
}

///|
pub struct ReviewBatch {
  outcomes_value : Array[ReviewOutcome]
  summary_value : ReviewBatchSummary
} derive(Eq, Debug)

///|
pub fn ReviewBatch::outcomes(self : ReviewBatch) -> Array[ReviewOutcome] {
  self.outcomes_value.copy()
}

///|
pub fn ReviewBatch::summary(self : ReviewBatch) -> ReviewBatchSummary {
  self.summary_value
}

///|
pub fn ReviewBatch::accepted(self : ReviewBatch) -> Bool {
  self.summary_value.rejected_count() == 0
}

///|
pub fn ReviewBatch::find(self : ReviewBatch, label : String) -> ReviewOutcome? {
  for outcome in self.outcomes_value {
    if outcome.label() == label {
      return Some(outcome)
    }
  }
  None
}

///|
pub fn ReviewBatch::accepted_labels(self : ReviewBatch) -> Array[String] {
  let labels : Array[String] = []
  for outcome in self.outcomes_value {
    if outcome.accepted() {
      labels.push(outcome.label())
    }
  }
  labels
}

///|
pub fn ReviewBatch::rejected_labels(self : ReviewBatch) -> Array[String] {
  let labels : Array[String] = []
  for outcome in self.outcomes_value {
    if !outcome.accepted() {
      labels.push(outcome.label())
    }
  }
  labels
}

///|
pub fn ReviewBatch::parse_error_labels(self : ReviewBatch) -> Array[String] {
  let labels : Array[String] = []
  for outcome in self.outcomes_value {
    if outcome.parse_error() is Some(_) {
      labels.push(outcome.label())
    }
  }
  labels
}

///|
pub struct ReviewManifestItem {
  label_value : String
  parsed_value : Bool
  accepted_value : Bool
  physical_lines_value : Int
  executable_blocks_value : Int
  checked_words_value : Int
  error_count_value : Int
  warning_count_value : Int
  changed_lines_value : Int
} derive(Eq, Debug)

///|
pub fn ReviewManifestItem::label(self : ReviewManifestItem) -> String {
  self.label_value
}

///|
pub fn ReviewManifestItem::parsed(self : ReviewManifestItem) -> Bool {
  self.parsed_value
}

///|
pub fn ReviewManifestItem::accepted(self : ReviewManifestItem) -> Bool {
  self.accepted_value
}

///|
pub fn ReviewManifestItem::physical_lines(self : ReviewManifestItem) -> Int {
  self.physical_lines_value
}

///|
pub fn ReviewManifestItem::executable_blocks(self : ReviewManifestItem) -> Int {
  self.executable_blocks_value
}

///|
pub fn ReviewManifestItem::checked_words(self : ReviewManifestItem) -> Int {
  self.checked_words_value
}

///|
pub fn ReviewManifestItem::error_count(self : ReviewManifestItem) -> Int {
  self.error_count_value
}

///|
pub fn ReviewManifestItem::warning_count(self : ReviewManifestItem) -> Int {
  self.warning_count_value
}

///|
pub fn ReviewManifestItem::changed_lines(self : ReviewManifestItem) -> Int {
  self.changed_lines_value
}

///|
pub fn ReviewBatch::manifest(self : ReviewBatch) -> Array[ReviewManifestItem] {
  let items : Array[ReviewManifestItem] = []
  for outcome in self.outcomes_value {
    let lines = match outcome.document() {
      Some(document) => document.physical_lines()
      None => 0
    }
    let blocks = match outcome.validation() {
      Some(validation) => validation.summary().executable_blocks()
      None => 0
    }
    let words = match outcome.validation() {
      Some(validation) => validation.summary().checked_words()
      None => 0
    }
    items.push({
      label_value: outcome.label(),
      parsed_value: outcome.parsed(),
      accepted_value: outcome.accepted(),
      physical_lines_value: lines,
      executable_blocks_value: blocks,
      checked_words_value: words,
      error_count_value: outcome.error_count(),
      warning_count_value: outcome.warning_count(),
      changed_lines_value: outcome.changed_lines(),
    })
  }
  items
}

///|
pub fn render_review_manifest(batch : ReviewBatch) -> String {
  let mut output = "label,parsed,accepted,lines,blocks,words,errors,warnings,changed\n"
  for item in batch.manifest() {
    output = output + item.label()
    output = output + "," + item.parsed().to_string()
    output = output + "," + item.accepted().to_string()
    output = output + "," + item.physical_lines().to_string()
    output = output + "," + item.executable_blocks().to_string()
    output = output + "," + item.checked_words().to_string()
    output = output + "," + item.error_count().to_string()
    output = output + "," + item.warning_count().to_string()
    output = output + "," + item.changed_lines().to_string() + "\n"
  }
  output
}

///|
pub fn review_batch(
  requests : Array[ReviewRequest],
  max_items? : Int = 1000,
  max_lines_per_item? : Int = 100000,
) -> Result[ReviewBatch, GCodeError] {
  if max_items <= 0 || max_lines_per_item <= 0 {
    return Err(make_error(InvalidNumber, "batch limits must be positive", 0))
  }
  if requests.length() > max_items {
    return Err(make_error(InvalidWord, "review batch item limit exceeded", 0))
  }
  let outcomes : Array[ReviewOutcome] = []
  let mut parsed = 0
  let mut accepted = 0
  let mut parse_errors = 0
  let mut diagnostic_errors = 0
  let mut warnings = 0
  let mut changed_lines = 0
  let mut commands = 0
  for request in requests {
    let outcome = review_source(request, max_lines=max_lines_per_item)
    if outcome.parsed() {
      parsed += 1
    } else {
      parse_errors += 1
    }
    if outcome.accepted() {
      accepted += 1
    }
    diagnostic_errors += outcome.error_count()
    warnings += outcome.warning_count()
    changed_lines += outcome.changed_lines()
    match outcome.index() {
      Some(index) => commands += index.command_count()
      None => ()
    }
    outcomes.push(outcome)
  }
  Ok({
    outcomes_value: outcomes,
    summary_value: {
      item_count_value: requests.length(),
      parsed_count_value: parsed,
      accepted_count_value: accepted,
      rejected_count_value: requests.length() - accepted,
      parse_error_count_value: parse_errors,
      diagnostic_error_count_value: diagnostic_errors,
      warning_count_value: warnings,
      changed_line_count_value: changed_lines,
      command_count_value: commands,
    },
  })
}

///|
pub fn render_review_outcome(outcome : ReviewOutcome) -> String {
  let mut output = "label=" + outcome.label()
  output = output + ", parsed=" + outcome.parsed().to_string()
  output = output + ", accepted=" + outcome.accepted().to_string()
  output = output + ", errors=" + outcome.error_count().to_string()
  output = output + ", warnings=" + outcome.warning_count().to_string()
  output = output +
    ", changed_lines=" +
    outcome.changed_lines().to_string() +
    "\n"
  match outcome.parse_error() {
    Some(error) => output = output + error.format() + "\n"
    None =>
      match outcome.validation() {
        Some(validation) => output = output + render_validation(validation)
        None => ()
      }
  }
  output
}

///|
pub fn render_review_batch(batch : ReviewBatch) -> String {
  let summary = batch.summary()
  let mut output = "items=" + summary.item_count().to_string()
  output = output + ", parsed=" + summary.parsed_count().to_string()
  output = output + ", accepted=" + summary.accepted_count().to_string()
  output = output + ", rejected=" + summary.rejected_count().to_string()
  output = output + ", parse_errors=" + summary.parse_error_count().to_string()
  output = output +
    ", diagnostic_errors=" +
    summary.diagnostic_error_count().to_string()
  output = output + ", warnings=" + summary.warning_count().to_string()
  output = output +
    ", changed_lines=" +
    summary.changed_line_count().to_string()
  output = output + ", commands=" + summary.command_count().to_string() + "\n"
  for outcome in batch.outcomes() {
    output = output + render_review_outcome(outcome)
  }
  output
}