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

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

///|
pub fn DisplayProfile::embedded_panel() -> DisplayProfile {
  {
    name: "embedded-panel",
    min_width: 1280,
    min_height: 720,
    min_refresh_hz: 60,
    require_preferred_timing: true,
    allow_interlaced: false,
    max_extension_blocks: 1,
  }
}

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

///|
pub fn audit_edid(edid : Edid) -> EdidAudit {
  audit_edid_with_profile(edid, DisplayProfile::baseline())
}

///|
pub fn audit_edid_with_profile(
  edid : Edid,
  profile : DisplayProfile,
) -> EdidAudit {
  let diagnostics : Array[EdidDiagnostic] = []
  append_parse_diagnostics(diagnostics, edid.diagnostics)
  append_basic_diagnostics(diagnostics, edid)
  append_timing_diagnostics(diagnostics, edid)
  append_descriptor_diagnostics(diagnostics, edid)
  append_range_diagnostics(diagnostics, edid)
  append_profile_diagnostics(diagnostics, edid, profile)
  let summary = summarize_audit(edid, diagnostics)
  {
    status: summary.status,
    profile_name: profile.name,
    diagnostics,
    summary,
    preferred_mode: preferred_mode(edid),
    display_class: edid.display_class(),
  }
}

///|
pub fn summarize_audit(
  edid : Edid,
  diagnostics : Array[EdidDiagnostic],
) -> AuditSummary {
  let mut notes = 0
  let mut warnings = 0
  let mut errors = 0
  for diagnostic in diagnostics {
    match diagnostic.severity {
      Note => notes = notes + 1
      Warning => warnings = warnings + 1
      Error => errors = errors + 1
    }
  }
  let status = if errors > 0 {
    Rejected
  } else if warnings > 0 {
    NeedsAttention
  } else {
    Accepted
  }
  {
    status,
    diagnostics: diagnostics.length(),
    notes,
    warnings,
    errors,
    detailed_timings: edid.detailed_timing_count(),
    standard_timings: edid.valid_standard_timing_count(),
    established_timings: edid.enabled_established_timing_count(),
  }
}

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

///|
pub fn EdidAudit::has_errors(self : EdidAudit) -> Bool {
  self.summary.errors > 0
}

///|
pub fn EdidAudit::has_warnings(self : EdidAudit) -> Bool {
  self.summary.warnings > 0
}

///|
pub fn EdidAudit::diagnostics_by_severity(
  self : EdidAudit,
  severity : Severity,
) -> Array[EdidDiagnostic] {
  let out : Array[EdidDiagnostic] = []
  for diagnostic in self.diagnostics {
    if diagnostic.severity == severity {
      out.push(diagnostic)
    }
  }
  out
}

///|
pub fn EdidAudit::has_diagnostic(
  self : EdidAudit,
  code : DiagnosticCode,
) -> Bool {
  for diagnostic in self.diagnostics {
    if diagnostic.code == code {
      return true
    }
  }
  false
}

///|
fn append_parse_diagnostics(
  out : Array[EdidDiagnostic],
  diagnostics : Array[EdidDiagnostic],
) -> Unit {
  for diagnostic in diagnostics {
    out.push(diagnostic)
  }
}

///|
fn append_basic_diagnostics(out : Array[EdidDiagnostic], edid : Edid) -> Unit {
  if !edid.checksum_valid() {
    out.push(
      EdidDiagnostic::make(
        InvalidChecksum,
        Error,
        "Base block checksum is not valid",
        Some(127),
      ),
    )
  }
  if edid.version_major != 1 {
    out.push(
      EdidDiagnostic::make(
        UnsupportedVersion,
        Warning,
        "Only EDID 1.x base blocks are fully supported",
        Some(18),
      ),
    )
  }
  if edid.manufacture.week > 53 && !edid.manufacture.model_year {
    out.push(
      EdidDiagnostic::make(
        SuspiciousManufactureDate,
        Warning,
        "Manufacture week is outside the common 1..53 range",
        Some(16),
      ),
    )
  }
  if edid.manufacture.year < 1990 || edid.manufacture.year > 2050 {
    out.push(
      EdidDiagnostic::make(
        SuspiciousManufactureDate,
        Warning,
        "Manufacture year is outside expected EDID range",
        Some(17),
      ),
    )
  }
  if !edid.display.has_physical_size() {
    out.push(
      EdidDiagnostic::make(
        InvalidDisplaySize,
        Warning,
        "Physical display size is missing",
        Some(21),
      ),
    )
  }
  match edid.display.gamma_x100 {
    Some(gamma) =>
      if gamma < 120 || gamma > 350 {
        out.push(
          EdidDiagnostic::make(
            UnusualGamma,
            Note,
            "Gamma value is outside the common display range",
            Some(23),
          ),
        )
      }
    None => ()
  }
}

///|
fn append_timing_diagnostics(out : Array[EdidDiagnostic], edid : Edid) -> Unit {
  if edid.detailed_timing_count() == 0 {
    out.push(
      EdidDiagnostic::make(
        MissingDetailedTiming,
        Warning,
        "No detailed timing descriptor is present",
        Some(54),
      ),
    )
  }
  if edid.display.preferred_timing && preferred_mode(edid) is None {
    out.push(
      EdidDiagnostic::make(
        MissingPreferredTiming,
        Warning,
        "EDID marks preferred timing support but no preferred mode was decoded",
        Some(24),
      ),
    )
  }
}

///|
fn append_descriptor_diagnostics(
  out : Array[EdidDiagnostic],
  edid : Edid,
) -> Unit {
  match edid.monitor_name() {
    Some(name) =>
      if name.is_empty() {
        out.push(
          EdidDiagnostic::make(
            EmptyMonitorName,
            Note,
            "Monitor name descriptor is empty",
            None,
          ),
        )
      }
    None =>
      out.push(
        EdidDiagnostic::make(
          MissingMonitorName,
          Note,
          "Monitor name descriptor is not present",
          None,
        ),
      )
  }
}

///|
fn append_range_diagnostics(out : Array[EdidDiagnostic], edid : Edid) -> Unit {
  for descriptor in edid.descriptors {
    match descriptor.range_limits {
      Some(range) =>
        if range.min_vertical_hz > range.max_vertical_hz ||
          range.min_horizontal_khz > range.max_horizontal_khz {
          out.push(
            EdidDiagnostic::make(
              RangeLimitInconsistent,
              Warning,
              "Range limits descriptor has inverted min/max values",
              None,
            ),
          )
        }
      None => ()
    }
  }
}

///|
fn append_profile_diagnostics(
  out : Array[EdidDiagnostic],
  edid : Edid,
  profile : DisplayProfile,
) -> Unit {
  if profile.require_preferred_timing && preferred_mode(edid) is None {
    out.push(
      EdidDiagnostic::make(
        ProfileRequirementNotMet,
        Error,
        "Profile requires a preferred timing",
        None,
      ),
    )
  }
  if !supports_mode(
      edid,
      profile.min_width,
      profile.min_height,
      profile.min_refresh_hz,
    ) {
    out.push(
      EdidDiagnostic::make(
        ProfileRequirementNotMet,
        Error,
        "Display does not meet the profile minimum resolution and refresh rate",
        None,
      ),
    )
  }
  if !profile.allow_interlaced && has_interlaced_modes(edid) {
    out.push(
      EdidDiagnostic::make(
        InterlacedModeUnsupported,
        Warning,
        "Profile does not allow interlaced modes",
        None,
      ),
    )
  }
  if edid.extension_count > profile.max_extension_blocks {
    out.push(
      EdidDiagnostic::make(
        ExtensionBlockIgnored,
        Warning,
        "EDID declares more extension blocks than the profile accepts",
        Some(126),
      ),
    )
  }
}