///|
pub(all) struct Analysis {
  species : Species
  findings : Array[Finding]
} derive(Debug, Eq, ToJson)

///|
pub fn Analysis::is_clean(self : Analysis) -> Bool {
  for finding in self.findings {
    if finding.severity == Error {
      return false
    }
  }
  true
}

///|
fn severity_name(severity : Severity) -> String {
  match severity {
    Error => "Error"
    Warning => "Warning"
    Info => "Info"
  }
}

///|
fn rule_name(rule : RuleKind) -> String {
  match rule {
    ParallelPerfect => "ParallelPerfect"
    HiddenPerfect => "HiddenPerfect"
    VoiceCrossing => "VoiceCrossing"
    DissonancePlacement => "DissonancePlacement"
    MelodyRange => "MelodyRange"
    LargeLeap => "LargeLeap"
    LeapRecovery => "LeapRecovery"
    SpeciesRhythm => "SpeciesRhythm"
    CadenceShape => "CadenceShape"
  }
}

///|
pub fn analyze_counterpoint(
  cantus : Array[Note],
  counter : Array[Note],
  options? : AnalysisOptions = AnalysisOptions::default(species=First),
) -> Analysis {
  let findings : Array[Finding] = []
  for f in analyze_species_shape(cantus, counter, options) {
    findings.push(f)
  }
  for f in analyze_melody(counter, options) {
    findings.push(f)
  }
  for f in analyze_harmony(cantus, counter, options) {
    findings.push(f)
  }
  { species: options.species, findings }
}

///|
pub fn format_finding(finding : Finding) -> String {
  "[\{severity_name(finding.severity)}] \{rule_name(finding.rule)} at \{finding.index}: \{finding.message}"
}

///|
pub fn format_analysis(analysis : Analysis) -> String {
  if analysis.findings.length() == 0 {
    "No counterpoint findings."
  } else {
    let lines : Array[String] = []
    for f in analysis.findings {
      lines.push(format_finding(f))
    }
    lines.join("\n")
  }
}