///|
pub(all) struct PitchStatistics {
  count : Int
  minimum : Int
  maximum : Int
  mean : Int
  median : Int
  range : Int
  distinct : Int
} derive(Debug, Eq, ToJson)

///|
pub(all) struct IntervalStatistics {
  count : Int
  minimum : Int
  maximum : Int
  mean : Int
  steps : Int
  leaps : Int
  repeated : Int
} derive(Debug, Eq, ToJson)

///|
pub(all) struct PairStatistics {
  aligned : Int
  contrary : Int
  parallel : Int
  similar : Int
  oblique : Int
  static_motion : Int
  average_vertical : Int
} derive(Debug, Eq, ToJson)

///|
pub(all) struct RegisterBand {
  low : Int
  high : Int
  count : Int
  percentage : Int
} derive(Debug, Eq, ToJson)

///|
fn insertion_index(values : Array[Int], value : Int) -> Int {
  let mut result = 0
  for current in values {
    if current <= value {
      result += 1
    }
  }
  result
}

///|
fn sorted_copy(values : Array[Int]) -> Array[Int] {
  let result : Array[Int] = []
  for value in values {
    let index = insertion_index(result, value)
    result.push(value)
    let mut i = result.length() - 1
    while i > index {
      result[i] = result[i - 1]
      i -= 1
    }
    result[index] = value
  }
  result
}

///|
pub fn pitch_statistics(notes : Array[Note]) -> PitchStatistics {
  if notes.length() == 0 {
    return {
      count: 0,
      minimum: 0,
      maximum: 0,
      mean: 0,
      median: 0,
      range: 0,
      distinct: 0,
    }
  }
  let values = pitches(notes)
  let sorted = sorted_copy(values)
  let mut low = sorted[0]
  let mut high = sorted[sorted.length() - 1]
  let mut sum = 0
  let mut distinct = 0
  for i, value in sorted {
    if value < low {
      low = value
    }
    if value > high {
      high = value
    }
    sum += value
    if i == 0 || value != sorted[i - 1] {
      distinct += 1
    }
  }
  let middle = sorted.length() / 2
  {
    count: sorted.length(),
    minimum: low,
    maximum: high,
    mean: sum / sorted.length(),
    median: sorted[middle],
    range: high - low,
    distinct,
  }
}

///|
pub fn interval_statistics(notes : Array[Note]) -> IntervalStatistics {
  let histogram = melodic_interval_histogram(notes)
  let count = if notes.length() < 2 { 0 } else { notes.length() - 1 }
  if count == 0 {
    return {
      count: 0,
      minimum: 0,
      maximum: 0,
      mean: 0,
      steps: 0,
      leaps: 0,
      repeated: 0,
    }
  }
  let mut total = 0
  let mut minimum = 100000
  let mut maximum = 0
  for i in 0.. 0 {
      total += i * frequency
      if i < minimum {
        minimum = i
      }
      if i > maximum {
        maximum = i
      }
    }
  }
  {
    count,
    minimum,
    maximum,
    mean: total / count,
    steps: if histogram.length() > 2 {
      histogram[1] + histogram[2]
    } else {
      0
    },
    leaps: if histogram.length() > 3 {
      count - histogram[0] - histogram[1] - histogram[2]
    } else {
      0
    },
    repeated: histogram[0],
  }
}

///|
pub fn pair_statistics(
  cantus : Array[Note],
  counter : Array[Note],
) -> PairStatistics {
  let n = if cantus.length() < counter.length() {
    cantus.length()
  } else {
    counter.length()
  }
  let mut aligned = 0
  let mut contrary = 0
  let mut parallel = 0
  let mut similar = 0
  let mut oblique = 0
  let mut static_motion = 0
  let mut vertical_total = 0
  for i in 0.. 1 {
    for i in 1.. contrary += 1
        Parallel => parallel += 1
        Similar => similar += 1
        Oblique => oblique += 1
        Static => static_motion += 1
      }
    }
  }
  {
    aligned,
    contrary,
    parallel,
    similar,
    oblique,
    static_motion,
    average_vertical: if n == 0 {
      0
    } else {
      vertical_total / n
    },
  }
}

///|
pub fn register_bands(notes : Array[Note], width : Int) -> Array[RegisterBand] {
  let result : Array[RegisterBand] = []
  if width <= 0 {
    return result
  }
  let statistics = pitch_statistics(notes)
  let mut start = statistics.minimum
  while start <= statistics.maximum {
    let high = start + width - 1
    let mut count = 0
    for note in notes {
      if start <= note.midi && note.midi <= high {
        count += 1
      }
    }
    result.push({
      low: start,
      high,
      count,
      percentage: if notes.length() == 0 {
        0
      } else {
        count * 100 / notes.length()
      },
    })
    start += width
  }
  result
}

///|
pub fn weighted_pitch_center(notes : Array[Note]) -> Int {
  if notes.length() == 0 {
    return 0
  }
  let mut weighted = 0
  let mut weight = 0
  for note in notes {
    let duration = if note.duration <= 0 { 1 } else { note.duration }
    weighted += note.midi * duration
    weight += duration
  }
  weighted / weight
}

///|
pub fn pitch_variance_proxy(notes : Array[Note]) -> Int {
  if notes.length() == 0 {
    return 0
  }
  let center = average_pitch(notes)
  let mut total = 0
  for note in notes {
    let difference = note.midi - center
    total += difference * difference
  }
  total / notes.length()
}

///|
pub fn density_by_measure(meter : Meter, notes : Array[Note]) -> Array[Int] {
  let measures = summarize_rhythm(meter, notes).measures
  let result = Array::make(if measures <= 0 { 1 } else { measures }, 0)
  for note in notes {
    let measure = measure_of(meter, note.onset)
    if measure >= 0 && measure < result.length() {
      result[measure] += 1
    }
  }
  result
}

///|
pub fn maximum_density(meter : Meter, notes : Array[Note]) -> Int {
  let mut result = 0
  for value in density_by_measure(meter, notes) {
    if value > result {
      result = value
    }
  }
  result
}

///|
pub fn compare_pitch_statistics(left : Array[Note], right : Array[Note]) -> Int {
  pitch_statistics(left).mean - pitch_statistics(right).mean
}

///|
pub fn compare_contours(left : Array[Note], right : Array[Note]) -> Int {
  contour_balance(left) - contour_balance(right)
}

///|
pub fn cadence_distance(notes : Array[Note], target : Int) -> Int {
  if notes.length() == 0 {
    return 0
  }
  let last = notes[notes.length() - 1].midi
  if last > target {
    last - target
  } else {
    target - last
  }
}