///|
/// Range(, )
pub struct Range(Int, Int)

///|
pub impl Show for Range with fn output(self, logger) {
  let mut beginning = self.0 + 1 // from array index to line number
  let len = self.1 - self.0
  if len == 1 {
    logger.write_string(beginning.to_string())
  } else {
    if len == 0 {
      // empty ranges begin at line just before the range
      beginning -= 1
    }
    logger.write_string(beginning.to_string())
    logger.write_char(',')
    logger.write_string(len.to_string())
  }
}

///|
pub struct HunkHeader {
  old : Range
  new : Range
}

///|
/// Creates a hunk header from a (non empty) array of edit
fn HunkHeader::HunkHeader(edits : ArrayView[Edit]) -> Self {
  let (first_old_start, first_old_end) = edits[0].old_range()
  let (first_new_start, first_new_end) = edits[0].new_range()
  let mut old_end = first_old_end
  let mut new_end = first_new_end
  let mut found_old_end = false
  let mut found_new_end = false
  let mut i = edits.length()
  while i > 0 {
    i -= 1
    let edit = edits[i]
    let (edit_old_start, edit_old_end) = edit.old_range()
    let (edit_new_start, edit_new_end) = edit.new_range()
    if !found_old_end && edit_old_start != edit_old_end {
      old_end = edit_old_end
      found_old_end = true
    }
    if !found_new_end && edit_new_start != edit_new_end {
      new_end = edit_new_end
      found_new_end = true
    }
    if found_old_end && found_new_end {
      break
    }
  }
  HunkHeader::{
    old: Range(first_old_start, old_end),
    new: Range(first_new_start, new_end),
  }
}

///|
pub impl Show for HunkHeader with fn output(self, logger) {
  logger.write_string("@@ -\{self.old} +\{self.new} @@")
}

///|
pub struct Hunk[T] {
  edits : ArrayView[Edit]
  old : ArrayView[T]
  new : ArrayView[T]
}

///|
pub fn[T] Hunk::header(self : Hunk[T]) -> HunkHeader {
  HunkHeader(self.edits)
}

///|
pub impl[T : Show] Show for Hunk[T] with fn output(self, logger) {
  let header = HunkHeader(self.edits).to_string()
  logger.write_string(header)
  logger.write_char('\n')
  for edit in self.edits {
    let (prefix, slice) = match edit {
      Insert(..) => ("+", edit.view_from(old=self.old, new=self.new))
      Delete(..) => ("-", edit.view_from(old=self.old, new=self.new))
      Equal(..) => (" ", edit.view_from(old=self.old, new=self.new))
    }
    for s in slice {
      logger.write_string("\{prefix}\{s}\n")
    }
  }
}