///|
/// A conservative metadata anonymization policy.
pub(all) struct AnonymizationPolicy {
  remove : Array[Tag]
  replacements : Array[(Tag, String)]
  remove_private : Bool
} derive(Eq, Debug)

///|
/// One change made during anonymization.
pub(all) enum AnonymizationAction {
  Removed
  Replaced
} derive(Eq, Debug)

///|
/// An audit record for one anonymized tag.
pub(all) struct AnonymizationAudit {
  tag : Tag
  action : AnonymizationAction
} derive(Eq, Debug)

///|
/// The result of applying an anonymization policy.
pub(all) struct AnonymizationResult {
  dataset : DataSet
  audit : Array[AnonymizationAudit]
} derive(Eq, Debug)

///|
/// Create an empty policy.
pub fn anonymization_policy() -> AnonymizationPolicy {
  { remove: [], replacements: [], remove_private: false }
}

///|
/// Return a conservative policy for common identifying metadata.
pub fn default_anonymization_policy() -> AnonymizationPolicy {
  {
    remove: [
      patient_name(),
      patient_id(),
      patient_birth_date(),
      referring_physician_name(),
      accession_number(),
    ],
    replacements: [
      (institution_name(), "REDACTED"),
      (study_description(), "REDACTED"),
      (series_description(), "REDACTED"),
    ],
    remove_private: true,
  }
}

///|
/// Return a policy that removes the given tag.
pub fn AnonymizationPolicy::with_remove(
  self : AnonymizationPolicy,
  tag : Tag,
) -> AnonymizationPolicy {
  self.remove.push(tag)
  self
}

///|
/// Return a policy that replaces a text value for the given tag.
pub fn AnonymizationPolicy::with_replacement(
  self : AnonymizationPolicy,
  tag : Tag,
  value : String,
) -> AnonymizationPolicy {
  self.replacements.push((tag, value))
  self
}

///|
/// Return a policy with private-tag removal enabled.
pub fn AnonymizationPolicy::with_private_removal(
  self : AnonymizationPolicy,
) -> AnonymizationPolicy {
  { remove: self.remove, replacements: self.replacements, remove_private: true }
}

///|
/// Apply a policy without modifying the source data set.
pub fn anonymize(
  dataset : DataSet,
  policy : AnonymizationPolicy,
) -> AnonymizationResult {
  let result = data_set()
  let audit : Array[AnonymizationAudit] = []
  for element in dataset.elements() {
    if policy.remove_private && element.tag.is_private() {
      audit.push({ tag: element.tag, action: Removed })
    } else if policy.remove.contains(element.tag) {
      audit.push({ tag: element.tag, action: Removed })
    } else {
      match replacement_for(policy.replacements, element.tag) {
        Some(value) => {
          result.set(data_element(element.tag, element.vr, Text(value)))
          audit.push({ tag: element.tag, action: Replaced })
        }
        None => result.set(element)
      }
    }
  }
  { dataset: result, audit }
}

///|
/// Return the anonymized data set.
pub fn AnonymizationResult::dataset(self : AnonymizationResult) -> DataSet {
  self.dataset
}

///|
/// Return the audit entries in source order.
pub fn AnonymizationResult::audit(
  self : AnonymizationResult,
) -> Array[AnonymizationAudit] {
  self.audit.copy()
}

///|
/// Return the tag changed by this audit entry.
pub fn AnonymizationAudit::tag(self : AnonymizationAudit) -> Tag {
  self.tag
}

///|
/// Return the action recorded for this audit entry.
pub fn AnonymizationAudit::action(
  self : AnonymizationAudit,
) -> AnonymizationAction {
  self.action
}

///|
fn replacement_for(replacements : Array[(Tag, String)], tag : Tag) -> String? {
  for pair in replacements {
    let replacement_tag = pair.0
    let value = pair.1
    if replacement_tag == tag {
      return Some(value)
    }
  }
  None
}