///|
/// Object core helpers for name lookup and class MRO.

///|
fn get_named_value(dict : Array[(String, Value)], name : String) -> Value? {
  for pair in dict {
    if pair.0 == name {
      return Some(pair.1)
    }
  }
  None
}

///|
fn set_named_value(
  dict : Array[(String, Value)],
  name : String,
  value : Value,
) -> Unit {
  for i = 0; i < dict.length(); i = i + 1 {
    if dict[i].0 == name {
      dict[i] = (name, value)
      return
    }
  }
  dict.push((name, value))
}

///|
let function_dict_name = "$__mpython_func_dict__"

///|
fn function_attr_dict(func : FunctionValue) -> Array[(Value, Value)] {
  for pair in func.closure {
    if pair.0 == function_dict_name {
      match pair.1 {
        Value::Dict(pairs) => return pairs
        _ => break
      }
    }
  }
  let pairs : Array[(Value, Value)] = []
  func.closure.push((function_dict_name, Value::Dict(pairs)))
  pairs
}

///|
fn clone_class_list(list : Array[ClassValue]) -> Array[ClassValue] {
  let result : Array[ClassValue] = []
  for item in list {
    result.push(item)
  }
  result
}

///|
fn class_equal(a : ClassValue, b : ClassValue) -> Bool {
  if a.name != b.name {
    return false
  }
  if a.bases.length() != b.bases.length() {
    return false
  }
  if a.dict.length() != b.dict.length() {
    return false
  }
  for i = 0; i < a.bases.length(); i = i + 1 {
    match (a.bases[i], b.bases[i]) {
      (Value::Class(left), Value::Class(right)) =>
        if left.name != right.name {
          return false
        }
      (Value::Function(left), Value::Function(right)) =>
        if left.name != right.name {
          return false
        }
      _ => return false
    }
  }
  for i = 0; i < a.dict.length(); i = i + 1 {
    if a.dict[i].0 != b.dict[i].0 {
      return false
    }
  }
  true
}

///|
fn class_in_list(
  list : Array[ClassValue],
  klass : ClassValue,
  start : Int32,
) -> Bool {
  for i = start; i < list.length(); i = i + 1 {
    if class_equal(list[i], klass) {
      return true
    }
  }
  false
}

///|
fn class_mro_merge(
  seqs_in : Array[Array[ClassValue]],
) -> Result[Array[ClassValue], RuntimeError] {
  let seqs : Array[Array[ClassValue]] = []
  for seq in seqs_in {
    seqs.push(clone_class_list(seq))
  }
  let out : Array[ClassValue] = []
  while true {
    let mut all_empty = true
    for seq in seqs {
      if seq.length() > 0 {
        all_empty = false
        break
      }
    }
    if all_empty {
      return Ok(out)
    }
    let mut candidate : ClassValue? = None
    for seq in seqs {
      if seq.length() == 0 {
        continue
      }
      let head = seq[0]
      let mut blocked = false
      for other in seqs {
        if class_in_list(other, head, 1) {
          blocked = true
          break
        }
      }
      if !blocked {
        candidate = Some(head)
        break
      }
    }
    if candidate is None {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "cannot create a consistent method resolution order".to_string(),
        ),
      )
    }
    let pick = candidate.unwrap()
    out.push(pick)
    for seq in seqs {
      if seq.length() > 0 && class_equal(seq[0], pick) {
        let _ = seq.remove(0)
      }
    }
  }
  Ok(out)
}

///|
fn class_mro_with_seen(
  klass : ClassValue,
  seen : Array[ClassValue],
) -> Result[Array[ClassValue], RuntimeError] {
  for item in seen {
    if class_equal(item, klass) {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "cannot create a consistent method resolution order".to_string(),
        ),
      )
    }
  }
  let next_seen : Array[ClassValue] = []
  for item in seen {
    next_seen.push(item)
  }
  next_seen.push(klass)
  let seqs : Array[Array[ClassValue]] = []
  let direct_bases : Array[ClassValue] = []
  for base in klass.bases {
    match base {
      Value::Class(base_class) => {
        direct_bases.push(base_class)
        let base_mro = match class_mro_with_seen(base_class, next_seen) {
          Ok(value) => value
          Err(err) => return Err(err)
        }
        seqs.push(base_mro)
      }
      _ => ()
    }
  }
  seqs.push(direct_bases)
  let tail = match class_mro_merge(seqs) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let mro : Array[ClassValue] = [klass]
  for item in tail {
    mro.push(item)
  }
  Ok(mro)
}

///|
fn class_mro(klass : ClassValue) -> Result[Array[ClassValue], RuntimeError] {
  class_mro_with_seen(klass, [])
}

///|
fn class_includes_list(klass : ClassValue) -> Bool {
  let mro = match class_mro(klass) {
    Ok(value) => value
    Err(_) => return false
  }
  for cls in mro {
    if cls.name == "list" {
      return true
    }
  }
  false
}

///|
fn class_includes_dict(klass : ClassValue) -> Bool {
  let mro = match class_mro(klass) {
    Ok(value) => value
    Err(_) => return false
  }
  for cls in mro {
    if cls.name == "dict" {
      return true
    }
  }
  false
}

///|
fn class_includes_tuple(klass : ClassValue) -> Bool {
  let mro = match class_mro(klass) {
    Ok(value) => value
    Err(_) => return false
  }
  for cls in mro {
    if cls.name == "tuple" {
      return true
    }
  }
  false
}

///|
fn class_includes_set(klass : ClassValue) -> Bool {
  let mro = match class_mro(klass) {
    Ok(value) => value
    Err(_) => return false
  }
  for cls in mro {
    if cls.name == "set" {
      return true
    }
  }
  false
}

///|
fn class_includes_frozenset(klass : ClassValue) -> Bool {
  let mro = match class_mro(klass) {
    Ok(value) => value
    Err(_) => return false
  }
  for cls in mro {
    if cls.name == "frozenset" {
      return true
    }
  }
  false
}