CodeTools::AST << {

ParameterAssembly < Node {
  node_type params
  field required:   []
  field optional:   []
  field rest:      null
  field post:       []
  field kwrequired: []
  field kwoptional: []
  field kwrest:    null
  field block:     null

  keywords_any?:
    self.kwrequired.any? || self.kwoptional.any? || self.kwrest

  # Called on a new method or block generator
  # to set up various parameter-related properties.
  generator_setup: |g| {
    g.required_args = self.required.size + self.post.size

    g.post_args = self.post.size

    g.total_args =
      self.required.size + self.optional.size + self.post.size
      + (self.keywords_any? &? 1 ?? 0)

    g.splat_index =
      self.rest &? (self.required.size + self.optional.size) ?? null

    g.block_index = self.block && (
      self.required.size
      + self.optional.size
      + (self.rest &? 1 ?? 0)
      + self.post.size
      + (self.keywords_any? &? 1 ?? 0)
      + 1 # for self.block
    )

    g.arity         = self.arity

    g.keywords      = self.keyword_entries
  }

  # TODO: remove/refactor out
  keyword_entries: {
    entries = \
      kwrequired.map |x| { [x.name, true] }
    + kwoptional.map |x| { [x.name, false] }
    entries = entries.flatten(1)
    entries.empty? &? null ?? entries
  }

  # TODO: remove/refactor out
  arity: {
    arity = self.required.size + self.post.size

    kwrequired.any? && (arity = arity + 1)

    (self.rest || self.optional.any? || kwoptional.any?) && (
      arity = arity + 1
      arity = arity * -1
    )

    arity
  }

  all_params: [
    *self.required,
    *self.optional,
    *(self.rest &? [self.rest] ?? []),
    *self.post,
    *self.kwrequired,
    *self.kwoptional,
    *(self.kwrest &? [self.kwrest] ?? []),
    *(self.block &? [self.block] ?? []),
  ]

  names: all_params.map(&:name).compact

  # TODO: simplify
  bytecode: |g| {
    g.state.check_for_locals = false
    all_params.each |param| { param.map_local(g.state.scope) }

    self.required.each |param| { param.bytecode(g) }
    self.optional.each |param| { param.bytecode(g) }
    self.rest && self.rest.bytecode(g)

    self.keywords_any? && (
      kw_done = g.new_label
      assignments_label = g.new_label
      missing_value_label = g.new_label
      defaults_label = g.new_label

      g.state.scope.search_local(:__myco_keywords_value__).get_bytecode(g)

      g.dup_top; g.goto_if_not_nil(assignments_label) # TODO: trivially remove this line?

      g.pop
      g.push_cpath_top
      g.find_const(:Hash)
      g.send(:allocate, 0, true)

      assignments_label.set!

      self.kwrequired.each |param| {
        g.dup_top

        g.push_literal(param.name)
        g.send(:find_item, 1, true)

        g.dup_top
        g.goto_if_false(missing_value_label)

        g.send(:value, 0, true)

        param_var = g.state.scope.search_local(param.name)
        param_var.set_bytecode(g)
        g.pop
      }

      g.goto(defaults_label)

      missing_value_label.set!
      g.pop
      g.push_rubinius
      g.find_const(:Runtime)
      g.swap
      g.send(:keywords_missing, 1, true)
      g.goto(kw_done)

      defaults_label.set!

      extra_keys_label = g.new_label

      self.kwoptional.empty? &? (
        g.dup_top
        g.send(:size, 0, true)
        g.push(self.kwrequired.size)
        g.goto_if_not_equal(extra_keys_label)

        self.kwrest && (
          g.push_cpath_top
          g.find_const(:Hash)
          g.send(:allocate, 0, true)
          kwrest_asgn = g.state.scope.search_local(self.kwrest.name)
          kwrest_asgn.set_bytecode(g)
          g.pop
        )

        g.goto(kw_done)
      ) ?? (
        self.kwoptional.each |param| {
          next_value_label = g.new_label
          default_value_label = g.new_label

          g.dup_top
          g.push_literal(param.name)
          g.send(:find_item, 1, true)

          g.dup_top
          g.goto_if_false(default_value_label)

          g.send(:value, 0, true)
          g.state.scope.search_local(param.name).set_bytecode(g)
          g.goto(next_value_label)

          default_value_label.set!
          g.pop
          param.value.bytecode(g)
          g.state.scope.search_local(param.name).set_bytecode(g)

          next_value_label.set!
          g.pop
        }
      )

      extra_keys_label.set!

      g.dup_top
      g.push_rubinius
      g.find_const(:Runtime)
      g.swap

      self.kwrest &? g.push(:true) ?? g.push(:false)

      g.send(:keywords_extra, 2, true)
      self.kwrest && (
        kwrest_asgn = g.state.scope.search_local(self.kwrest.name)
        kwrest_asgn.set_bytecode(g)
      )
      g.pop

      kw_done.set!
    )

    self.block && self.block.bytecode(g)

    g.state.check_for_locals = true
  }
}

}