///|
pub(all) struct Feature {
  domain : String
  value : String
} derive(Debug, Eq, Hash)

///|
pub fn feature(domain : String, value : String) -> Feature {
  { domain, value, }
}

///|
pub fn Feature::canonical(self : Feature) -> String {
  self.domain.length().to_string() +
  ":" +
  self.domain +
  "=" +
  self.value.length().to_string() +
  ":" +
  self.value
}

///|
pub(all) enum ExecutionDisposition {
  Accepted
  Rejected(String)
  Failed(String, String)
} derive(Debug, Eq)

///|
pub(all) struct TargetObservation {
  disposition : ExecutionDisposition
  features : Array[Feature]
  result_digest : String?
  cost_class : Int
} derive(Debug, Eq)

///|
pub fn observation(
  disposition : ExecutionDisposition,
  features? : Array[Feature] = [],
  result_digest? : String,
  cost_class? : Int = 0,
) -> TargetObservation {
  {
    disposition,
    features: features.copy(),
    result_digest,
    cost_class: cost_class.max(0),
  }
}

///|
pub fn accepted(
  features? : Array[Feature] = [],
  result_digest? : String,
  cost_class? : Int = 0,
) -> TargetObservation {
  {
    disposition: Accepted,
    features: features.copy(),
    result_digest,
    cost_class: cost_class.max(0),
  }
}

///|
pub fn rejected(
  reason : String,
  features? : Array[Feature] = [],
) -> TargetObservation {
  {
    disposition: Rejected(reason),
    features: features.copy(),
    result_digest: None,
    cost_class: 0,
  }
}

///|
pub fn failed(
  kind : String,
  detail : String,
  features? : Array[Feature] = [],
) -> TargetObservation {
  {
    disposition: Failed(kind, detail),
    features: features.copy(),
    result_digest: None,
    cost_class: 0,
  }
}

///|
fn compare_string(left : String, right : String) -> Int {
  let left_bytes = @utf8.encode(left)
  let right_bytes = @utf8.encode(right)
  let common = left_bytes.length().min(right_bytes.length())
  for index = 0; index < common; index = index + 1 {
    let a = left_bytes[index].to_int()
    let b = right_bytes[index].to_int()
    if a < b {
      return -1
    }
    if a > b {
      return 1
    }
  }
  if left_bytes.length() < right_bytes.length() {
    -1
  } else if left_bytes.length() > right_bytes.length() {
    1
  } else {
    0
  }
}

///|
fn sorted_unique_features(values : Array[Feature]) -> Array[Feature] {
  let result : Array[Feature] = []
  for value in values {
    let key = value.canonical()
    let mut inserted = false
    let mut duplicate = false
    for index = 0; index < result.length(); index = index + 1 {
      let comparison = compare_string(key, result[index].canonical())
      if comparison == 0 {
        duplicate = true
        inserted = true
        break
      }
      if comparison < 0 {
        result.insert(index, value)
        inserted = true
        break
      }
    }
    if !inserted && !duplicate {
      result.push(value)
    }
  }
  result
}

///|
pub(all) struct FeedbackSignature(String) derive(Debug, Eq, Hash)

///|
pub fn FeedbackSignature::value(self : FeedbackSignature) -> String {
  self.0
}

///|
pub fn feedback_signature(features : Array[Feature]) -> FeedbackSignature {
  let parts : Array[String] = []
  for value in sorted_unique_features(features) {
    parts.push(value.canonical())
  }
  FeedbackSignature(parts.join("|"))
}

///|
pub fn TargetObservation::signature(
  self : TargetObservation,
) -> FeedbackSignature {
  feedback_signature(self.features)
}

///|
pub(all) struct FailureFingerprint(String) derive(Debug, Eq, Hash)

///|
pub fn FailureFingerprint::value(self : FailureFingerprint) -> String {
  self.0
}

///|
fn fingerprint_field(name : String, value : String) -> String {
  name + "=" + value.length().to_string() + ":" + value + ";"
}

///|
pub fn TargetObservation::failure_fingerprint(
  self : TargetObservation,
) -> FailureFingerprint? {
  match self.disposition {
    Accepted | Rejected(_) => None
    Failed(kind, detail) =>
      Some(
        FailureFingerprint(
          fingerprint_field("kind", kind) + fingerprint_field("detail", detail),
        ),
      )
  }
}

///|
pub fn TargetObservation::is_failure(self : TargetObservation) -> Bool {
  self.disposition is Failed(_, _)
}

///|
pub fn TargetObservation::feature_count(self : TargetObservation) -> Int {
  sorted_unique_features(self.features).length()
}

///|
pub fn TargetObservation::canonical_features(
  self : TargetObservation,
) -> Array[Feature] {
  sorted_unique_features(self.features)
}

///|
pub struct Target {
  name : String
  evaluate : (Bytes) -> TargetObservation
}

///|
pub fn Target::new(
  name : String,
  evaluate : (Bytes) -> TargetObservation,
) -> Target {
  { name, evaluate, }
}

///|
pub fn Target::name(self : Target) -> String {
  self.name
}

///|
pub fn Target::run(self : Target, input : Bytes) -> TargetObservation {
  (self.evaluate)(input)
}

///|
pub(all) struct FeatureDelta {
  signature : FeedbackSignature
  novel_features : Array[Feature]
  all_features : Array[Feature]
} derive(Debug, Eq)

///|
pub struct FeatureRegistry {
  seen : @hashmap.HashMap[Feature, Int]
  signatures : @hashmap.HashMap[FeedbackSignature, Int]
  mut observations : Int
}

///|
pub fn FeatureRegistry::new() -> FeatureRegistry {
  {
    seen: @hashmap.HashMap([]),
    signatures: @hashmap.HashMap([]),
    observations: 0,
  }
}

///|
pub fn FeatureRegistry::observe(
  self : FeatureRegistry,
  observation : TargetObservation,
) -> FeatureDelta {
  self.observations += 1
  let all = observation.canonical_features()
  let novel : Array[Feature] = []
  for item in all {
    match self.seen.get(item) {
      None => {
        self.seen.set(item, 1)
        novel.push(item)
      }
      Some(count) => self.seen.set(item, count + 1)
    }
  }
  let signature = feedback_signature(all)
  let signature_count = self.signatures.get(signature).unwrap_or(0)
  self.signatures.set(signature, signature_count + 1)
  { signature, novel_features: novel, all_features: all, }
}

///|
pub fn FeatureRegistry::feature_frequency(
  self : FeatureRegistry,
  value : Feature,
) -> Int {
  self.seen.get(value).unwrap_or(0)
}

///|
pub fn FeatureRegistry::signature_frequency(
  self : FeatureRegistry,
  value : FeedbackSignature,
) -> Int {
  self.signatures.get(value).unwrap_or(0)
}

///|
pub fn FeatureRegistry::unique_feature_count(self : FeatureRegistry) -> Int {
  self.seen.length()
}

///|
pub fn FeatureRegistry::unique_signature_count(self : FeatureRegistry) -> Int {
  self.signatures.length()
}

///|
pub fn FeatureRegistry::observation_count(self : FeatureRegistry) -> Int {
  self.observations
}