class MethodMixin {
  """
  Mixin class with common methods included by @Method@ and @UnboundMethod@.
  """

  def documentation {
    """
    @return Docstring for @self.
    """

    Fancy Documentation for: executable
  }

  def documentation: docstring {
    """
    @docstring New docstring for @self.
    """

    Fancy Documentation for: executable is: docstring
  }

  def visibility {
    """
    @return The visibility (@'private, @'protected, @'public) of a @Method@ in its defined context, if any.
    """
    entry = @defined_in method_table() lookup(@name)
    { entry visibility() } if: entry
  }

  def public? {
    """
    @return @true, if the @Method@ is public in its defined context.
    """
    visibility == 'public
  }

  def protected? {
    """
    @return @true, if the @Method@ is protected in its defined context.
    """
    visibility == 'protected
  }

  def private? {
    """
    @return @true, if the @Method@ is private in its defined context.
    """
    visibility == 'private
  }

  def tests {
    """
    Returns an Array of all the FancySpec SpecTests defined for a
    Method.
    """

    @__method_tests__ =  @__method_tests__ || []
    @__method_tests__
  }

  def test: test_block {
    it = FancySpec new: self
    test_block call: [it]
    tests << it
  }
}

class Method {
  """
  An instance of Method represents a method on a Class.
  Every method in Fancy is an instance of the Method class.
  """

  ruby_alias: 'arity
  ruby_alias: 'executable
  include: MethodMixin
  forwards_unary_ruby_methods

  def call: args ([]) {
    call(*args)
  }
}

class UnboundMethod {
  """
  An instance UnboundMethod represents a Method object not bound to a specific @Class@ or @Object@.
  """

  ruby_alias: 'arity
  ruby_alias: 'executable
  include: MethodMixin
  forwards_unary_ruby_methods

  alias_method: 'bind: for_ruby: 'bind

  def call: args ([]) {
    call(*args)
  }

  def selector_with_args {
    name = name to_s
    match name {
      case ":[]" -> return "[arg_0]"
      case "[]:" -> return "[arg_0]: arg_1"
    }

    match arity {
      case 0 -> name rest
      case _ ->
        selectors = name split: ":"
        (0..arity - 1) map: |i| {
          "#{selectors[i]}: arg_#{i}"
        } . join: " "
    }
  }
}