///|
pub fn DisplayProfile::legacy_xga() -> DisplayProfile {
  {
    name: "legacy-xga",
    min_width: 1024,
    min_height: 768,
    min_refresh_hz: 60,
    require_preferred_timing: false,
    allow_interlaced: true,
    max_extension_blocks: 8,
  }
}

///|
pub fn DisplayProfile::full_hd_video() -> DisplayProfile {
  {
    name: "full-hd-video",
    min_width: 1920,
    min_height: 1080,
    min_refresh_hz: 60,
    require_preferred_timing: true,
    allow_interlaced: false,
    max_extension_blocks: 4,
  }
}

///|
pub fn DisplayProfile::workstation() -> DisplayProfile {
  {
    name: "workstation",
    min_width: 2560,
    min_height: 1440,
    min_refresh_hz: 60,
    require_preferred_timing: true,
    allow_interlaced: false,
    max_extension_blocks: 4,
  }
}

///|
pub fn DisplayProfile::gaming() -> DisplayProfile {
  {
    name: "gaming",
    min_width: 1920,
    min_height: 1080,
    min_refresh_hz: 120,
    require_preferred_timing: true,
    allow_interlaced: false,
    max_extension_blocks: 4,
  }
}

///|
pub fn DisplayProfile::uhd_video() -> DisplayProfile {
  {
    name: "uhd-video",
    min_width: 3840,
    min_height: 2160,
    min_refresh_hz: 60,
    require_preferred_timing: true,
    allow_interlaced: false,
    max_extension_blocks: 6,
  }
}

///|
pub fn DisplayProfile::projector_room() -> DisplayProfile {
  {
    name: "projector-room",
    min_width: 1280,
    min_height: 800,
    min_refresh_hz: 60,
    require_preferred_timing: false,
    allow_interlaced: true,
    max_extension_blocks: 6,
  }
}

///|
pub fn standard_display_profiles() -> Array[DisplayProfile] {
  [
    DisplayProfile::legacy_xga(),
    DisplayProfile::baseline(),
    DisplayProfile::embedded_panel(),
    DisplayProfile::desktop(),
    DisplayProfile::full_hd_video(),
    DisplayProfile::projector_room(),
    DisplayProfile::workstation(),
    DisplayProfile::gaming(),
    DisplayProfile::large_format(),
    DisplayProfile::uhd_video(),
  ]
}

///|
pub fn profile_matrix(edid : Edid) -> Array[ProfileCheck] {
  let out : Array[ProfileCheck] = []
  for profile in standard_display_profiles() {
    out.push(check_profile(edid, profile))
  }
  out
}

///|
pub fn check_profile(edid : Edid, profile : DisplayProfile) -> ProfileCheck {
  let minimum_mode_met = supports_mode(
    edid,
    profile.min_width,
    profile.min_height,
    profile.min_refresh_hz,
  )
  let preferred_timing_met = !profile.require_preferred_timing ||
    preferred_mode(edid) is Some(_)
  let interlaced_policy_met = profile.allow_interlaced ||
    !has_interlaced_modes(edid)
  let extension_policy_met = edid.extension_count <=
    profile.max_extension_blocks
  let status = if minimum_mode_met &&
    preferred_timing_met &&
    interlaced_policy_met &&
    extension_policy_met {
    Accepted
  } else if minimum_mode_met && preferred_timing_met {
    NeedsAttention
  } else {
    Rejected
  }
  {
    profile_name: profile.name,
    status,
    score: profile_score(edid, profile),
    minimum_mode_met,
    preferred_timing_met,
    interlaced_policy_met,
    extension_policy_met,
  }
}

///|
pub fn best_profile_match(edid : Edid) -> ProfileCheck {
  let checks = profile_matrix(edid)
  let mut best = checks[0]
  for check in checks {
    if check.score > best.score {
      best = check
    }
  }
  best
}

///|
pub fn accepted_profiles(edid : Edid) -> Array[ProfileCheck] {
  let out : Array[ProfileCheck] = []
  for check in profile_matrix(edid) {
    if check.status == Accepted {
      out.push(check)
    }
  }
  out
}

///|
pub fn rejected_profiles(edid : Edid) -> Array[ProfileCheck] {
  let out : Array[ProfileCheck] = []
  for check in profile_matrix(edid) {
    if check.status == Rejected {
      out.push(check)
    }
  }
  out
}

///|
pub fn profile_matrix_to_text(checks : Array[ProfileCheck]) -> String {
  let buf = StringBuilder(size_hint=checks.length() * 64)
  let mut first = true
  for check in checks {
    if first {
      first = false
    } else {
      buf.write_char('\n')
    }
    buf.write_string(check.profile_name)
    buf.write_string(": ")
    buf.write_string(check.status.name())
    buf.write_string(" score=")
    buf.write_string(check.score.to_string())
  }
  buf.to_string()
}

///|
pub fn profile_matrix_to_markdown(checks : Array[ProfileCheck]) -> String {
  let buf = StringBuilder(size_hint=checks.length() * 96 + 96)
  buf.write_string(
    "| Profile | Status | Score | Mode | Preferred | Interlaced | Extensions |\n",
  )
  buf.write_string("| --- | --- | ---: | ---: | ---: | ---: | ---: |")
  for check in checks {
    buf.write_char('\n')
    buf.write_string("| ")
    buf.write_string(check.profile_name)
    buf.write_string(" | ")
    buf.write_string(check.status.name())
    buf.write_string(" | ")
    buf.write_string(check.score.to_string())
    buf.write_string(" | ")
    buf.write_string(yes_no(check.minimum_mode_met))
    buf.write_string(" | ")
    buf.write_string(yes_no(check.preferred_timing_met))
    buf.write_string(" | ")
    buf.write_string(yes_no(check.interlaced_policy_met))
    buf.write_string(" | ")
    buf.write_string(yes_no(check.extension_policy_met))
    buf.write_string(" |")
  }
  buf.to_string()
}

///|
pub fn ProfileCheck::is_ok(self : ProfileCheck) -> Bool {
  self.status == Accepted
}

///|
pub fn ProfileCheck::failed_requirements(self : ProfileCheck) -> Int {
  bool_failure(self.minimum_mode_met) +
  bool_failure(self.preferred_timing_met) +
  bool_failure(self.interlaced_policy_met) +
  bool_failure(self.extension_policy_met)
}

///|
fn profile_score(edid : Edid, profile : DisplayProfile) -> Int {
  let mut score = 0
  if supports_mode(
      edid,
      profile.min_width,
      profile.min_height,
      profile.min_refresh_hz,
    ) {
    score = score + 400
  }
  if !profile.require_preferred_timing || preferred_mode(edid) is Some(_) {
    score = score + 250
  }
  if profile.allow_interlaced || !has_interlaced_modes(edid) {
    score = score + 150
  }
  if edid.extension_count <= profile.max_extension_blocks {
    score = score + 100
  }
  match best_mode(edid) {
    Some(mode) => {
      if mode.width >= profile.min_width && mode.height >= profile.min_height {
        score = score + 80
      }
      if mode.refresh_hz_rounded() >= profile.min_refresh_hz {
        score = score + 20
      }
    }
    None => ()
  }
  score
}

///|
fn yes_no(value : Bool) -> String {
  if value {
    "yes"
  } else {
    "no"
  }
}

///|
fn bool_failure(value : Bool) -> Int {
  if value {
    0
  } else {
    1
  }
}