///|
pub(all) struct GenomicInterval {
  start : Int
  end : Int
} derive(Debug, Eq)

///|
pub fn make_interval(start : Int, end : Int) -> GenomicInterval? {
  if start <= end && start > 0 {
    Some({ start, end })
  } else {
    None
  }
}

///|
pub fn interval_contains(interval : GenomicInterval?, position : Int) -> Bool {
  match interval {
    Some(value) => position >= value.start && position <= value.end
    None => false
  }
}

///|
pub fn interval_width(interval : GenomicInterval?) -> Int {
  match interval {
    Some(value) => value.end - value.start + 1
    None => 0
  }
}

///|
pub fn interval_overlaps(
  left : GenomicInterval?,
  right : GenomicInterval?,
) -> Bool {
  match (left, right) {
    (Some(a), Some(b)) => a.start <= b.end && b.start <= a.end
    _ => false
  }
}

///|
pub fn interval_intersection(
  left : GenomicInterval?,
  right : GenomicInterval?,
) -> GenomicInterval? {
  match (left, right) {
    (Some(a), Some(b)) =>
      make_interval(max_int(a.start, b.start), min_int(a.end, b.end))
    _ => None
  }
}

///|
pub fn interval_merge(
  intervals : Array[GenomicInterval?],
) -> Array[GenomicInterval?] {
  let valid : Array[GenomicInterval] = []
  for interval in intervals {
    match interval {
      Some(value) => valid.push(value)
      None => ()
    }
  }
  let result : Array[GenomicInterval?] = []
  for current in valid {
    if result.length() == 0 {
      result.push(Some(current))
    } else {
      match result[result.length() - 1] {
        Some(previous) if current.start <= previous.end + 1 => {
          ignore(result.pop())
          result.push(
            Some({
              start: previous.start,
              end: max_int(previous.end, current.end),
            }),
          )
        }
        _ => result.push(Some(current))
      }
    }
  }
  result
}

///|
pub fn interval_subtract(
  source : GenomicInterval?,
  removed : GenomicInterval?,
) -> Array[GenomicInterval?] {
  match (source, removed) {
    (Some(a), Some(b)) if interval_overlaps(Some(a), Some(b)) => {
      let pieces : Array[GenomicInterval?] = []
      if a.start < b.start {
        pieces.push(make_interval(a.start, b.start - 1))
      }
      if b.end < a.end {
        pieces.push(make_interval(b.end + 1, a.end))
      }
      pieces
    }
    (Some(a), _) => [Some(a)]
    _ => []
  }
}

///|
pub fn interval_shift(
  interval : GenomicInterval?,
  offset : Int,
) -> GenomicInterval? {
  match interval {
    Some(value) => make_interval(value.start + offset, value.end + offset)
    None => None
  }
}

///|
pub fn interval_expand(
  interval : GenomicInterval?,
  padding : Int,
) -> GenomicInterval? {
  match interval {
    Some(value) =>
      make_interval(max_int(1, value.start - padding), value.end + padding)
    None => None
  }
}

///|
pub fn interval_clip(
  interval : GenomicInterval?,
  lower : Int,
  upper : Int,
) -> GenomicInterval? {
  interval_intersection(interval, make_interval(lower, upper))
}

///|
pub fn interval_distance(
  left : GenomicInterval?,
  right : GenomicInterval?,
) -> Int {
  match (left, right) {
    (Some(a), Some(b)) if a.end < b.start => b.start - a.end - 1
    (Some(a), Some(b)) if b.end < a.start => a.start - b.end - 1
    _ => 0
  }
}

///|
pub fn format_interval(interval : GenomicInterval?) -> String {
  match interval {
    Some(value) => "\{value.start}-\{value.end}"
    None => "."
  }
}

///|
fn min_int(left : Int, right : Int) -> Int {
  if left < right {
    left
  } else {
    right
  }
}

///|
fn max_int(left : Int, right : Int) -> Int {
  if left > right {
    left
  } else {
    right
  }
}