///|
/// Assignment and deletion for statement targets.

///|
fn replace_array_values(values : Array[Value], next : Array[Value]) -> Unit {
  let mut i = values.length()
  while i > 0 {
    i = i - 1
    let _ = values.remove(i)
  }
  for item in next {
    values.push(item)
  }
}

///|
fn collect_unpack_items(
  assigned : Value,
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Array[Value], RuntimeError] {
  match assigned {
    Value::Tuple(values) => Ok(values)
    Value::List(values) => Ok(values)
    _ => {
      let iterator = match
        iter_value_to_iterator(assigned, globals, builtins, io) {
        Ok(value) => value
        Err(err) =>
          match err.kind {
            RuntimeErrorKind::Type =>
              if err.message == "object is not iterable" {
                return Err(
                  make_runtime_error(
                    RuntimeErrorKind::Type,
                    "cannot unpack non-iterable".to_string(),
                  ),
                )
              } else {
                return Err(err)
              }
            _ => return Err(err)
          }
      }
      let items : Array[Value] = []
      while true {
        match iterator_next(iterator, None, globals, builtins, io) {
          Ok(value) => items.push(value)
          Err(err) =>
            if err.exc_type == "StopIteration" {
              break
            } else {
              return Err(err)
            }
        }
      }
      Ok(items)
    }
  }
}

///|
fn assign_target_with_env(
  target : Target,
  assigned : Value,
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Unit, RuntimeError] {
  match target {
    Target::Name(name) => {
      set_scoped_value(locals, globals, name, assigned)
      Ok(())
    }
    Target::Starred(_) =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "SyntaxError: starred assignment target must be in a list or tuple".to_string(),
        ),
      )
    Target::Tuple(targets) | Target::List(targets) => {
      let items = match collect_unpack_items(assigned, globals, builtins, io) {
        Ok(values) => values
        Err(err) => return Err(err)
      }
      // Extended iterable unpacking (PEP 3132): a, *rest, b = iterable
      let mut star_index : Int32? = None
      for i = 0; i < targets.length(); i = i + 1 {
        match targets[i] {
          Target::Starred(_) =>
            match star_index {
              Some(_) =>
                return Err(
                  make_runtime_error(
                    RuntimeErrorKind::Runtime,
                    "SyntaxError: multiple starred expressions in assignment".to_string(),
                  ),
                )
              None => star_index = Some(i)
            }
          _ => ()
        }
      }
      match star_index {
        None => {
          if items.length() != targets.length() {
            let expected = targets.length()
            if items.length() < expected {
              return Err(
                make_runtime_error(
                  RuntimeErrorKind::Runtime,
                  "ValueError: not enough values to unpack (expected " +
                  expected.to_string() +
                  ")",
                ),
              )
            }
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Runtime,
                "ValueError: too many values to unpack (expected " +
                expected.to_string() +
                ")",
              ),
            )
          }
          for i = 0; i < targets.length(); i = i + 1 {
            let _ = match
              assign_target_with_env(
                targets[i],
                items[i],
                locals,
                globals,
                builtins,
                io,
              ) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
          }
          Ok(())
        }
        Some(k) => {
          let left_count = k
          let right_count = targets.length() - k - 1
          let expected_min = targets.length() - 1
          if items.length() < expected_min {
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Runtime,
                "ValueError: not enough values to unpack (expected at least " +
                expected_min.to_string() +
                ")",
              ),
            )
          }
          for i = 0; i < left_count; i = i + 1 {
            let _ = match
              assign_target_with_env(
                targets[i],
                items[i],
                locals,
                globals,
                builtins,
                io,
              ) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
          }
          for i = 0; i < right_count; i = i + 1 {
            let src = items.length() - right_count + i
            let dst = k + 1 + i
            let _ = match
              assign_target_with_env(
                targets[dst],
                items[src],
                locals,
                globals,
                builtins,
                io,
              ) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
          }
          let mid : Array[Value] = []
          let end_mid = items.length() - right_count
          for i = left_count; i < end_mid; i = i + 1 {
            mid.push(items[i])
          }
          match targets[k] {
            Target::Starred(inner) =>
              assign_target_with_env(
                inner,
                Value::List(mid),
                locals,
                globals,
                builtins,
                io,
              )
            _ => Ok(()) // unreachable
          }
        }
      }
    }
    Target::Attribute(value~, attr~) => {
      let target_value = match
        eval_expr_with_env(value, locals, globals, builtins, io) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      match
        set_attr_on_value(target_value, attr, assigned, globals, builtins, io) {
        Ok(_) => Ok(())
        Err(err) => Err(err)
      }
    }
    Target::Subscript(value~, index~) => {
      let container = match
        eval_expr_with_env(value, locals, globals, builtins, io) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      fn assign_list_subscript(
        values : Array[Value],
      ) -> Result[Unit, RuntimeError] {
        match index {
          Expr::Slice(start~, end~, step~) => {
            let start_value = match start {
              Some(expr) =>
                match eval_expr_with_env(expr, locals, globals, builtins, io) {
                  Ok(value) => value
                  Err(err) => return Err(err)
                }
              None => Value::None
            }
            let end_value = match end {
              Some(expr) =>
                match eval_expr_with_env(expr, locals, globals, builtins, io) {
                  Ok(value) => value
                  Err(err) => return Err(err)
                }
              None => Value::None
            }
            let step_value = match step {
              Some(expr) =>
                match eval_expr_with_env(expr, locals, globals, builtins, io) {
                  Ok(value) => value
                  Err(err) => return Err(err)
                }
              None => Value::None
            }
            let new_items = match iterable_values(assigned) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
            let (start, end, step) = match
              slice_params_from_values(
                values.length(),
                start_value,
                end_value,
                step_value,
              ) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
            if step == 1 {
              let merged : Array[Value] = []
              for i = 0; i < start; i = i + 1 {
                merged.push(values[i])
              }
              for item in new_items {
                merged.push(item)
              }
              for i = end; i < values.length(); i = i + 1 {
                merged.push(values[i])
              }
              replace_array_values(values, merged)
              Ok(())
            } else {
              let indices = match
                slice_indices_from_values(
                  values.length(),
                  start_value,
                  end_value,
                  step_value,
                ) {
                Ok(v) => v
                Err(err) => return Err(err)
              }
              if new_items.length() != indices.length() {
                return Err(
                  make_runtime_error(
                    RuntimeErrorKind::Runtime,
                    "ValueError: attempt to assign sequence of size " +
                    new_items.length().to_string() +
                    " to extended slice of size " +
                    indices.length().to_string(),
                  ),
                )
              }
              for i = 0; i < indices.length(); i = i + 1 {
                values[indices[i]] = new_items[i]
              }
              Ok(())
            }
          }
          _ => {
            let index_value = match
              eval_expr_with_env(index, locals, globals, builtins, io) {
              Ok(value) => value
              Err(err) => return Err(err)
            }
            let idx = match index_from_value(index_value, 0) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
            let norm = normalize_index(idx, values.length())
            if norm < 0 || norm >= values.length() {
              return Err(
                make_runtime_error(
                  RuntimeErrorKind::Index,
                  "index out of range".to_string(),
                ),
              )
            }
            values[norm] = assigned
            Ok(())
          }
        }
      }

      match container {
        Value::List(values) => assign_list_subscript(values)
        Value::Instance(inst) =>
          match get_named_value(inst.dict, list_storage_name) {
            Some(Value::List(values)) => assign_list_subscript(values)
            _ => {
              let index_arg = match index {
                Expr::Slice(start~, end~, step~) => {
                  let start_value = match start {
                    Some(expr) =>
                      match
                        eval_expr_with_env(expr, locals, globals, builtins, io) {
                        Ok(value) => value
                        Err(err) => return Err(err)
                      }
                    None => Value::None
                  }
                  let end_value = match end {
                    Some(expr) =>
                      match
                        eval_expr_with_env(expr, locals, globals, builtins, io) {
                        Ok(value) => value
                        Err(err) => return Err(err)
                      }
                    None => Value::None
                  }
                  let step_value = match step {
                    Some(expr) =>
                      match
                        eval_expr_with_env(expr, locals, globals, builtins, io) {
                        Ok(value) => value
                        Err(err) => return Err(err)
                      }
                    None => Value::None
                  }
                  let dict : Array[(String, Value)] = []
                  dict.push(("start", start_value))
                  dict.push(("stop", end_value))
                  dict.push(("step", step_value))
                  let klass = builtin_class_from_name("slice", builtins)
                  Value::Instance(InstanceValue::{ class: klass, dict, })
                }
                _ =>
                  match
                    eval_expr_with_env(index, locals, globals, builtins, io) {
                    Ok(value) => value
                    Err(err) => return Err(err)
                  }
              }
              match
                get_attr_from_value(
                  container, "__setitem__", globals, builtins, io,
                ) {
                Ok(setitem) => {
                  let _ = match
                    call_callable_with_env(
                      setitem,
                      [index_arg, assigned],
                      [],
                      globals,
                      builtins,
                      io,
                    ) {
                    Ok(v) => v
                    Err(err) => return Err(err)
                  }
                  Ok(())
                }
                Err(err) =>
                  match err.kind {
                    RuntimeErrorKind::Attribute =>
                      Err(
                        make_runtime_error(
                          RuntimeErrorKind::Type,
                          "object does not support item assignment".to_string(),
                        ),
                      )
                    _ => Err(err)
                  }
              }
            }
          }
        Value::ByteArray(_) => {
          let index_arg = match index {
            Expr::Slice(start~, end~, step~) => {
              let start_value = match start {
                Some(expr) =>
                  match
                    eval_expr_with_env(expr, locals, globals, builtins, io) {
                    Ok(value) => value
                    Err(err) => return Err(err)
                  }
                None => Value::None
              }
              let end_value = match end {
                Some(expr) =>
                  match
                    eval_expr_with_env(expr, locals, globals, builtins, io) {
                    Ok(value) => value
                    Err(err) => return Err(err)
                  }
                None => Value::None
              }
              let step_value = match step {
                Some(expr) =>
                  match
                    eval_expr_with_env(expr, locals, globals, builtins, io) {
                    Ok(value) => value
                    Err(err) => return Err(err)
                  }
                None => Value::None
              }
              let dict : Array[(String, Value)] = []
              dict.push(("start", start_value))
              dict.push(("stop", end_value))
              dict.push(("step", step_value))
              let klass = builtin_class_from_name("slice", builtins)
              Value::Instance(InstanceValue::{ class: klass, dict, })
            }
            _ =>
              match eval_expr_with_env(index, locals, globals, builtins, io) {
                Ok(value) => value
                Err(err) => return Err(err)
              }
          }
          match
            get_attr_from_value(container, "__setitem__", globals, builtins, io) {
            Ok(setitem) => {
              let _ = match
                call_callable_with_env(
                  setitem,
                  [index_arg, assigned],
                  [],
                  globals,
                  builtins,
                  io,
                ) {
                Ok(v) => v
                Err(err) => return Err(err)
              }
              Ok(())
            }
            Err(err) =>
              match err.kind {
                RuntimeErrorKind::Attribute =>
                  Err(
                    make_runtime_error(
                      RuntimeErrorKind::Type,
                      "object does not support item assignment".to_string(),
                    ),
                  )
                _ => Err(err)
              }
          }
        }
        Value::Dict(pairs) => {
          let index_value = match
            eval_expr_with_env(index, locals, globals, builtins, io) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
          let _ = match dict_set_item(pairs, index_value, assigned) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          Ok(())
        }
        _ =>
          Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "object does not support item assignment".to_string(),
            ),
          )
      }
    }
  }
}

///|
fn delete_target_with_env(
  target : Target,
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Unit, RuntimeError] {
  match target {
    Target::Name(name) => {
      if scope_name_is_nonlocal(name) {
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Runtime,
            "SyntaxError: can't delete nonlocal".to_string(),
          ),
        )
      }
      match delete_scoped_value(locals, globals, name) {
        Ok(true) => Ok(())
        Ok(false) =>
          Err(
            make_runtime_error(
              RuntimeErrorKind::Name,
              "name '" + name + "' is not defined",
            ),
          )
        Err(err) => Err(err)
      }
    }
    Target::Starred(_) =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "SyntaxError: can't use starred expression here".to_string(),
        ),
      )
    Target::Tuple(items) | Target::List(items) => {
      for item in items {
        let _ = match
          delete_target_with_env(item, locals, globals, builtins, io) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
      }
      Ok(())
    }
    Target::Attribute(value~, attr~) => {
      let target_value = match
        eval_expr_with_env(value, locals, globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      delete_attr_on_value(target_value, attr, globals, builtins, io)
    }
    Target::Subscript(value~, index~) => {
      let container = match
        eval_expr_with_env(value, locals, globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      match container {
        Value::List(values) =>
          match index {
            Expr::Slice(start~, end~, step~) => {
              let start_value = match start {
                Some(expr) =>
                  match
                    eval_expr_with_env(expr, locals, globals, builtins, io) {
                    Ok(value) => value
                    Err(err) => return Err(err)
                  }
                None => Value::None
              }
              let end_value = match end {
                Some(expr) =>
                  match
                    eval_expr_with_env(expr, locals, globals, builtins, io) {
                    Ok(value) => value
                    Err(err) => return Err(err)
                  }
                None => Value::None
              }
              let step_value = match step {
                Some(expr) =>
                  match
                    eval_expr_with_env(expr, locals, globals, builtins, io) {
                    Ok(value) => value
                    Err(err) => return Err(err)
                  }
                None => Value::None
              }
              let indices = match
                slice_indices_from_values(
                  values.length(),
                  start_value,
                  end_value,
                  step_value,
                ) {
                Ok(v) => v
                Err(err) => return Err(err)
              }
              if indices.length() == 0 {
                return Ok(())
              }
              let remove_mask : Array[Bool] = []
              for i = 0; i < values.length(); i = i + 1 {
                remove_mask.push(false)
              }
              for idx in indices {
                if idx >= 0 && idx < remove_mask.length() {
                  remove_mask[idx] = true
                }
              }
              let next : Array[Value] = []
              for i = 0; i < values.length(); i = i + 1 {
                if !remove_mask[i] {
                  next.push(values[i])
                }
              }
              replace_array_values(values, next)
              Ok(())
            }
            _ => {
              let index_value = match
                eval_expr_with_env(index, locals, globals, builtins, io) {
                Ok(v) => v
                Err(err) => return Err(err)
              }
              let idx = match index_from_value(index_value, 0) {
                Ok(v) => v
                Err(err) => return Err(err)
              }
              let norm = normalize_index(idx, values.length())
              if norm < 0 || norm >= values.length() {
                return Err(
                  make_runtime_error(
                    RuntimeErrorKind::Index,
                    "index out of range".to_string(),
                  ),
                )
              }
              let _ = values.remove(norm)
              Ok(())
            }
          }
        Value::Instance(_) | Value::ByteArray(_) => {
          let index_arg = match index {
            Expr::Slice(start~, end~, step~) => {
              let start_value = match start {
                Some(expr) =>
                  match
                    eval_expr_with_env(expr, locals, globals, builtins, io) {
                    Ok(value) => value
                    Err(err) => return Err(err)
                  }
                None => Value::None
              }
              let end_value = match end {
                Some(expr) =>
                  match
                    eval_expr_with_env(expr, locals, globals, builtins, io) {
                    Ok(value) => value
                    Err(err) => return Err(err)
                  }
                None => Value::None
              }
              let step_value = match step {
                Some(expr) =>
                  match
                    eval_expr_with_env(expr, locals, globals, builtins, io) {
                    Ok(value) => value
                    Err(err) => return Err(err)
                  }
                None => Value::None
              }
              let dict : Array[(String, Value)] = []
              dict.push(("start", start_value))
              dict.push(("stop", end_value))
              dict.push(("step", step_value))
              let klass = builtin_class_from_name("slice", builtins)
              Value::Instance(InstanceValue::{ class: klass, dict, })
            }
            _ =>
              match eval_expr_with_env(index, locals, globals, builtins, io) {
                Ok(value) => value
                Err(err) => return Err(err)
              }
          }
          match
            get_attr_from_value(container, "__delitem__", globals, builtins, io) {
            Ok(delitem) => {
              let _ = match
                call_callable_with_env(
                  delitem,
                  [index_arg],
                  [],
                  globals,
                  builtins,
                  io,
                ) {
                Ok(v) => v
                Err(err) => return Err(err)
              }
              Ok(())
            }
            Err(err) =>
              match err.kind {
                RuntimeErrorKind::Attribute =>
                  Err(
                    make_runtime_error(
                      RuntimeErrorKind::Type,
                      "object does not support item deletion".to_string(),
                    ),
                  )
                _ => Err(err)
              }
          }
        }
        Value::Dict(pairs) => {
          let index_value = match
            eval_expr_with_env(index, locals, globals, builtins, io) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          let removed = match dict_delete_key(pairs, index_value) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          if removed {
            Ok(())
          } else {
            Err(
              make_runtime_error(
                RuntimeErrorKind::Key,
                "key not found".to_string(),
              ),
            )
          }
        }
        _ =>
          Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "object does not support item deletion".to_string(),
            ),
          )
      }
    }
  }
}