///|
fn abs_int(x : Int) -> Int {
  if x < 0 {
    -x
  } else {
    x
  }
}

///|
pub fn melodic_interval(a : Note, b : Note) -> Int {
  b.midi - a.midi
}

///|
pub fn harmonic_interval(lower : Note, upper : Note) -> Int {
  upper.midi - lower.midi
}

///|
pub fn interval_class(semitones : Int) -> Int {
  let v = semitones % 12
  if v < 0 {
    v + 12
  } else {
    v
  }
}

///|
pub fn is_perfect_consonance(semitones : Int) -> Bool {
  let pc = interval_class(semitones)
  pc == 0 || pc == 7
}

///|
pub fn is_imperfect_consonance(semitones : Int) -> Bool {
  let pc = interval_class(semitones)
  pc == 3 || pc == 4 || pc == 8 || pc == 9
}

///|
pub fn is_consonance(semitones : Int) -> Bool {
  is_perfect_consonance(semitones) || is_imperfect_consonance(semitones)
}

///|
pub fn is_dissonance(semitones : Int) -> Bool {
  !is_consonance(semitones)
}

///|
pub fn direction(a : Note, b : Note) -> Direction {
  if b.midi > a.midi {
    Up
  } else if b.midi < a.midi {
    Down
  } else {
    Same
  }
}

///|
pub fn motion(
  cantus_a : Note,
  cantus_b : Note,
  counter_a : Note,
  counter_b : Note,
) -> MotionKind {
  let c = direction(cantus_a, cantus_b)
  let p = direction(counter_a, counter_b)
  if c == Same && p == Same {
    Static
  } else if c == Same || p == Same {
    Oblique
  } else if c == p {
    let old_interval = harmonic_interval(cantus_a, counter_a)
    let new_interval = harmonic_interval(cantus_b, counter_b)
    if interval_class(old_interval) == interval_class(new_interval) {
      Parallel
    } else {
      Similar
    }
  } else {
    Contrary
  }
}

///|
pub fn absolute_melodic_interval(a : Note, b : Note) -> Int {
  abs_int(melodic_interval(a, b))
}