##
# Module ISO Test

def labeled_module(name, &block)
  Module.new do
    (class <<self; self end).class_eval do
      define_method(:to_s) { name }
      alias_method :inspect, :to_s
    end
    class_eval(&block) if block
  end
end

def labeled_class(name, supklass = Object, &block)
  Class.new(supklass) do
    (class <<self; self end).class_eval do
      define_method(:to_s) { name }
      alias_method :inspect, :to_s
    end
    class_eval(&block) if block
  end
end

def assert_uninitialized_const(&block)
  assert_raise_with_message_pattern(NameError, "uninitialized constant *", &block)
end

def assert_wrong_const_name(&block)
  assert_raise_with_message_pattern(NameError, "wrong constant name *", &block)
end

assert('Module', '15.2.2') do
  assert_equal Class, Module.class
end

assert('Module#alias_method', '15.2.2.4.8') do
  cls = Class.new do
    def foo
      "FOO"
    end
  end

  assert_same(cls, cls.alias_method(:bar, :foo))
  assert_equal("FOO", cls.new.bar)
end

# TODO not implemented ATM assert('Module.constants', '15.2.2.3.1') do

assert('Module#ancestors', '15.2.2.4.9') do
  class Test4ModuleAncestors
  end
  r = String.ancestors

  assert_equal Array, r.class
  assert_true r.include?(String)
  assert_true r.include?(Object)
end

assert('Module#append_features', '15.2.2.4.10') do
  module Test4AppendFeatures
    def self.append_features(mod)
      Test4AppendFeatures2.const_set(:Const4AppendFeatures2, mod)
    end
  end
  module Test4AppendFeatures2
    include Test4AppendFeatures
  end

  assert_equal Test4AppendFeatures2, Test4AppendFeatures2.const_get(:Const4AppendFeatures2)
  assert_raise(FrozenError) { Module.new.append_features Class.new.freeze }
end

assert('Module#attr NameError') do
  %w[
    foo?
    @foo
    @@foo
    $foo
  ].each do |name|
    module NameTest; end

    assert_raise(NameError) do
      NameTest.module_eval { attr_reader name.to_sym }
    end

    assert_raise(NameError) do
      NameTest.module_eval { attr_writer name.to_sym }
    end

    assert_raise(NameError) do
      NameTest.module_eval { attr name.to_sym }
    end

    assert_raise(NameError) do
      NameTest.module_eval { attr_accessor name.to_sym }
    end
  end

end

assert('Module#attr', '15.2.2.4.11') do
  class AttrTest
    class << self
      attr :cattr
      def cattr_val=(val)
        @cattr = val
      end
    end
    attr :iattr
    def iattr_val=(val)
      @iattr = val
    end
  end

  test = AttrTest.new
  assert_true AttrTest.respond_to?(:cattr)
  assert_true test.respond_to?(:iattr)

  assert_false AttrTest.respond_to?(:cattr=)
  assert_false test.respond_to?(:iattr=)

  test.iattr_val = 'test'
  assert_equal 'test', test.iattr

  AttrTest.cattr_val = 'test'
  assert_equal 'test', AttrTest.cattr
end

assert('Module#attr_accessor', '15.2.2.4.12') do
  class AttrTestAccessor
    class << self
      attr_accessor :cattr
    end
    attr_accessor :iattr, 'iattr2'
  end

  attr_instance = AttrTestAccessor.new
  assert_true AttrTestAccessor.respond_to?(:cattr=)
  assert_true attr_instance.respond_to?(:iattr=)
  assert_true attr_instance.respond_to?(:iattr2=)
  assert_true AttrTestAccessor.respond_to?(:cattr)
  assert_true attr_instance.respond_to?(:iattr)
  assert_true attr_instance.respond_to?(:iattr2)

  attr_instance.iattr = 'test'
  assert_equal 'test', attr_instance.iattr

  AttrTestAccessor.cattr = 'test'
  assert_equal 'test', AttrTestAccessor.cattr
end

assert('Module#attr_reader', '15.2.2.4.13') do
  class AttrTestReader
    class << self
      attr_reader :cattr
      def cattr_val=(val)
        @cattr = val
      end
    end
    attr_reader :iattr, 'iattr2'
    def iattr_val=(val)
      @iattr = val
    end
  end

  attr_instance = AttrTestReader.new
  assert_true AttrTestReader.respond_to?(:cattr)
  assert_true attr_instance.respond_to?(:iattr)
  assert_true attr_instance.respond_to?(:iattr2)

  assert_false AttrTestReader.respond_to?(:cattr=)
  assert_false attr_instance.respond_to?(:iattr=)
  assert_false attr_instance.respond_to?(:iattr2=)

  attr_instance.iattr_val = 'test'
  assert_equal 'test', attr_instance.iattr

  AttrTestReader.cattr_val = 'test'
  assert_equal 'test', AttrTestReader.cattr
end

assert('Module#attr_writer', '15.2.2.4.14') do
  class AttrTestWriter
    class << self
      attr_writer :cattr
      def cattr_val
        @cattr
      end
    end
    attr_writer :iattr, 'iattr2'
    def iattr_val
      @iattr
    end
  end

  attr_instance = AttrTestWriter.new
  assert_true AttrTestWriter.respond_to?(:cattr=)
  assert_true attr_instance.respond_to?(:iattr=)
  assert_true attr_instance.respond_to?(:iattr2=)

  assert_false AttrTestWriter.respond_to?(:cattr)
  assert_false attr_instance.respond_to?(:iattr)
  assert_false attr_instance.respond_to?(:iattr2)

  attr_instance.iattr = 'test'
  assert_equal 'test', attr_instance.iattr_val

  AttrTestWriter.cattr = 'test'
  assert_equal 'test', AttrTestWriter.cattr_val
end

assert('Module#class_eval', '15.2.2.4.15') do
  class Test4ClassEval
    @a = 11
    @b = 12
  end
  Test4ClassEval.class_eval do
    def method1
    end
  end
  assert_equal 11, Test4ClassEval.class_eval{ @a }
  assert_equal 12, Test4ClassEval.class_eval{ @b }
  assert_equal true, Test4ClassEval.new.respond_to?(:method1)
end

assert('Module#const_defined?', '15.2.2.4.20') do
  module Test4ConstDefined
    Const4Test4ConstDefined = true
  end

  assert_true Test4ConstDefined.const_defined?(:Const4Test4ConstDefined)
  assert_false Test4ConstDefined.const_defined?(:NotExisting)
  assert_wrong_const_name{ Test4ConstDefined.const_defined?(:wrong_name) }
end

assert('Module#const_get', '15.2.2.4.21') do
  module Test4ConstGet
    Const4Test4ConstGet = 42
  end

  assert_equal 42, Test4ConstGet.const_get(:Const4Test4ConstGet)
  assert_equal 42, Test4ConstGet.const_get("Const4Test4ConstGet")
  assert_equal 42, Object.const_get("Test4ConstGet::Const4Test4ConstGet")

  assert_raise(TypeError){ Test4ConstGet.const_get(123) }
  assert_uninitialized_const{ Test4ConstGet.const_get(:I_DO_NOT_EXIST) }
  assert_uninitialized_const{ Test4ConstGet.const_get("I_DO_NOT_EXIST::ME_NEITHER") }
  assert_wrong_const_name{ Test4ConstGet.const_get(:wrong_name) }
end

assert('Module#const_set', '15.2.2.4.23') do
  module Test4ConstSet
    Const4Test4ConstSet = 42
  end

  assert_equal 23, Test4ConstSet.const_set(:Const4Test4ConstSet, 23)
  assert_equal 23, Test4ConstSet.const_get(:Const4Test4ConstSet)
  ["", "wrongNAME", "Wrong-Name"].each do |n|
    assert_wrong_const_name { Test4ConstSet.const_set(n, 1) }
  end
end

assert('Module#remove_const', '15.2.2.4.40') do
  module Test4RemoveConst
    ExistingConst = 23
  end

  assert_equal 23, Test4RemoveConst.remove_const(:ExistingConst)
  assert_false Test4RemoveConst.const_defined?(:ExistingConst)
  assert_raise_with_message_pattern(NameError, "constant * not defined") do
    Test4RemoveConst.remove_const(:NonExistingConst)
  end
  %i[x X!].each do |n|
    assert_wrong_const_name { Test4RemoveConst.remove_const(n) }
  end
  assert_raise(FrozenError) { Test4RemoveConst.freeze.remove_const(:A) }
end

assert('Module#const_missing', '15.2.2.4.22') do
  module Test4ConstMissing
    def self.const_missing(sym)
      42 # the answer to everything
    end
  end

  assert_equal 42, Test4ConstMissing.const_get(:ConstDoesntExist)
end

assert('Module#extend_object', '15.2.2.4.25') do
  cls = Class.new
  mod = Module.new { def foo; end }
  a = cls.new
  b = cls.new
  mod.extend_object(b)
  assert_false a.respond_to?(:foo)
  assert_true b.respond_to?(:foo)
  assert_raise(FrozenError) { mod.extend_object(cls.new.freeze) }
  assert_raise(FrozenError, TypeError) { mod.extend_object(1) }
end

assert('Module#include', '15.2.2.4.27') do
  module Test4Include
    Const4Include = 42
  end
  module Test4Include2
    @include_result = include Test4Include
    class << self
      attr_reader :include_result
    end
  end

  assert_equal 42, Test4Include2.const_get(:Const4Include)
  assert_equal Test4Include2, Test4Include2.include_result
  assert_raise(FrozenError) { Module.new.freeze.include Test4Include }
end

assert('Module#include?', '15.2.2.4.28') do
  module Test4IncludeP
  end
  class Test4IncludeP2
    include Test4IncludeP
  end
  class Test4IncludeP3 < Test4IncludeP2
  end

  assert_true Test4IncludeP2.include?(Test4IncludeP)
  assert_true Test4IncludeP3.include?(Test4IncludeP)
  assert_false Test4IncludeP.include?(Test4IncludeP)
end

assert('Module#included', '15.2.2.4.29') do
  module Test4Included
    Const4Included = 42
    def self.included mod
      Test4Included.const_set(:Const4Included2, mod)
    end
  end
  module Test4Included2
    include Test4Included
  end

  assert_equal 42, Test4Included2.const_get(:Const4Included)
  assert_equal Test4Included2, Test4Included2.const_get(:Const4Included2)
end

assert('Module#initialize', '15.2.2.4.31') do
  assert_kind_of Module, Module.new
  mod = Module.new { def hello; "hello"; end }
  cls = Class.new{include mod}
  assert_true cls.new.respond_to?(:hello)
  a = nil
  mod = Module.new { |m| a = m }
  assert_equal mod, a
end

assert('Module#method_defined?', '15.2.2.4.34') do
  module Test4MethodDefined
    module A
      def method1() end
    end

    class B
      def method2() end
    end

    class C < B
      include A
      def method3() end
    end
  end

  assert_true Test4MethodDefined::A.method_defined? :method1
  assert_true Test4MethodDefined::C.method_defined? :method1
  assert_true Test4MethodDefined::C.method_defined? "method2"
  assert_true Test4MethodDefined::C.method_defined? "method3"
  assert_false Test4MethodDefined::C.method_defined? "method4"
end

assert('Module#module_eval', '15.2.2.4.35') do
  module Test4ModuleEval
    @a = 11
    @b = 12
  end

  assert_equal 11, Test4ModuleEval.module_eval{ @a }
  assert_equal 12, Test4ModuleEval.module_eval{ @b }
end

assert('Module#undef_method', '15.2.2.4.42') do
  module Test4UndefMethod
    class Parent
      def hello
      end
    end

    class Child < Parent
      def hello
      end
    end

    class GrandChild < Child
    end
  end
  Test4UndefMethod::Child.class_eval{ undef_method :hello }

  assert_true Test4UndefMethod::Parent.new.respond_to?(:hello)
  assert_false Test4UndefMethod::Child.new.respond_to?(:hello)
  assert_false Test4UndefMethod::GrandChild.new.respond_to?(:hello)
end

# Not ISO specified

assert('Module#dup') do
  module TestModuleDup
    @@cvar = :cvar
    class << self
      attr_accessor :cattr
      def cmeth; :cmeth end
    end
    def cvar; @@cvar end
    def imeth; :imeth end
    self.cattr = :cattr
  end

  m = TestModuleDup.dup
  o = Object.include(m).new
  assert_equal(:cattr, m.cattr)
  assert_equal(:cmeth, m.cmeth)
  assert_equal(:cvar, o.cvar)
  assert_equal(:imeth, o.imeth)
  assert_match("#<Module:0x*>", m.to_s)
  assert_not_predicate(m, :frozen?)
  assert_not_predicate(TestModuleDup.freeze.dup, :frozen?)
end

assert('Module#define_method') do
  c = Class.new {
    define_method(:m1) { :ok }
    define_method(:m2, Proc.new { :ok })
  }
  assert_equal c.new.m1, :ok
  assert_equal c.new.m2, :ok
  assert_raise(TypeError) do
    Class.new { define_method(:n1, nil) }
  end
end

assert 'Module#prepend_features' do
  mod = Module.new { def m; :mod end }
  cls = Class.new { def m; :cls end }
  assert_equal :cls, cls.new.m
  mod.prepend_features(cls)
  assert_equal :mod, cls.new.m
  assert_raise(FrozenError) { Module.new.prepend_features(Class.new.freeze) }
end

# @!group prepend
  assert('Module#prepend') do
    module M0
      def m1; [:M0] end
    end
    module M1
      def m1; [:M1, super, :M1] end
    end
    module M2
      def m1; [:M2, super, :M2] end
    end
    M3 = Module.new do
      def m1; [:M3, super, :M3] end
    end
    module M4
      def m1; [:M4, super, :M4] end
    end

    class P0
      include M0
      prepend M1
      def m1; [:C0, super, :C0] end
    end
    class P1 < P0
      prepend M2, M3
      include M4
      def m1; [:C1, super, :C1] end
    end

    obj = P1.new
    expected = [:M2,[:M3,[:C1,[:M4,[:M1,[:C0,[:M0],:C0],:M1],:M4],:C1],:M3],:M2]
    assert_equal(expected, obj.m1)
  end

  assert('Module#prepend result') do
    module TestPrepended; end
    module TestPrependResult
      @prepend_result = prepend TestPrepended
      class << self
        attr_reader :prepend_result
      end
    end

    assert_equal TestPrependResult, TestPrependResult.prepend_result
  end

  # mruby shouldn't be affected by this since there is
  # no visibility control (yet)
  assert('Module#prepend public') do
    assert_nothing_raised('ruby/ruby #8846') do
      Class.new.prepend(Module.new)
    end
  end

  assert('Module#prepend inheritance') do
    bug6654 = '[ruby-core:45914]'
    a = labeled_module('a')
    b = labeled_module('b') { include a }
    c = labeled_module('c') { prepend b }

    #assert bug6654 do
      # the Module#< operator should be used here instead, but we don't have it
      assert_include(c.ancestors, a)
      assert_include(c.ancestors, b)
    #end

    bug8357 = '[ruby-core:54736] [Bug #8357]'
    b = labeled_module('b') { prepend a }
    c = labeled_class('c') { include b }

    #assert bug8357 do
      # the Module#< operator should be used here instead, but we don't have it
      assert_include(c.ancestors, a)
      assert_include(c.ancestors, b)
    #end

    bug8357 = '[ruby-core:54742] [Bug #8357]'
    assert_kind_of(b, c.new, bug8357)
  end

  assert 'Module#prepend + Class#ancestors' do
    bug6658 = '[ruby-core:45919]'
    m = labeled_module("m")
    c = labeled_class("c") {prepend m}
    assert_equal([m, c], c.ancestors[0, 2], bug6658)

    bug6662 = '[ruby-dev:45868]'
    c2 = labeled_class("c2", c)
    anc = c2.ancestors
    assert_equal([c2, m, c, Object], anc[0..anc.index(Object)], bug6662)
  end

  assert 'Module#prepend + Module#ancestors' do
    bug6659 = '[ruby-dev:45861]'
    m0 = labeled_module("m0") { def x; [:m0, *super] end }
    m1 = labeled_module("m1") { def x; [:m1, *super] end; prepend m0 }
    m2 = labeled_module("m2") { def x; [:m2, *super] end; prepend m1 }
    c0 = labeled_class("c0") { def x; [:c0] end }
    c1 = labeled_class("c1") { def x; [:c1] end; prepend m2 }
    c2 = labeled_class("c2", c0) { def x; [:c2, *super] end; include m2 }
    #
    assert_equal([m0, m1], m1.ancestors, bug6659)
    #
    bug6662 = '[ruby-dev:45868]'
    assert_equal([m0, m1, m2], m2.ancestors, bug6662)
    assert_equal([m0, m1, m2, c1], c1.ancestors[0, 4], bug6662)
    assert_equal([:m0, :m1, :m2, :c1], c1.new.x)
    assert_equal([c2, m0, m1, m2, c0], c2.ancestors[0, 5], bug6662)
    assert_equal([:c2, :m0, :m1, :m2, :c0], c2.new.x)
    #
    m3 = labeled_module("m3") { include m1; prepend m1 }
    assert_equal([m3, m0, m1], m3.ancestors)
    m3 = labeled_module("m3") { prepend m1; include m1 }
    assert_equal([m0, m1, m3], m3.ancestors)
    m3 = labeled_module("m3") { prepend m1; prepend m1 }
    assert_equal([m0, m1, m3], m3.ancestors)
    m3 = labeled_module("m3") { include m1; include m1 }
    assert_equal([m3, m0, m1], m3.ancestors)
  end

  assert 'cyclic Module#prepend' do
    bug7841 = '[ruby-core:52205] [Bug #7841]'
    m1 = Module.new
    m2 = Module.new
    m1.instance_eval { prepend(m2) }
    assert_raise(ArgumentError, bug7841) do
      m2.instance_eval { prepend(m1) }
    end
  end

  # these assertions will not run without a #assert_separately method
  #assert 'test_prepend_optmethod' do
  #  bug7983 = '[ruby-dev:47124] [Bug #7983]'
  #  assert_separately [], %{
  #    module M
  #      def /(other)
  #        to_f / other
  #      end
  #    end
  #    Integer.send(:prepend, M)
  #    assert_equal(0.5, 1 / 2, "#{bug7983}")
  #  }
  #  assert_equal(0, 1 / 2)
  #end

  # mruby has no visibility control
  # assert 'Module#prepend visibility' do
  #   bug8005 = '[ruby-core:53106] [Bug #8005]'
  #   c = Class.new do
  #     prepend Module.new {}
  #     def foo() end
  #     protected :foo
  #   end
  #   a = c.new
  #   assert_true a.respond_to?(:foo), bug8005
  #   assert_nothing_raised(bug8005) {a.send :foo}
  # end

  # mruby has no visibility control
  # assert 'Module#prepend inherited visibility' do
  #   bug8238 = '[ruby-core:54105] [Bug #8238]'
  #   module Test4PrependVisibilityInherited
  #     class A
  #       def foo() A; end
  #       private :foo
  #     end
  #     class B < A
  #       public :foo
  #       prepend Module.new
  #     end
  #   end
  #   assert_equal(Test4PrependVisibilityInherited::A, Test4PrependVisibilityInherited::B.new.foo, "#{bug8238}")
  # end

  # assert 'Module#prepend super in alias' do
  #   skip "super does not currently work in aliased methods"
  #   bug7842 = '[Bug #7842]'

  #   p = labeled_module("P") do
  #     def m; "P"+super; end
  #   end

  #   a = labeled_class("A") do
  #     def m; "A"; end
  #   end

  #   b = labeled_class("B", a) do
  #     def m; "B"+super; end
  #     alias m2 m
  #     prepend p
  #     alias m3 m
  #   end

  #   assert_nothing_raised do
  #     assert_equal("BA", b.new.m2, bug7842)
  #   end

  #   assert_nothing_raised do
  #     assert_equal("PBA", b.new.m3, bug7842)
  #   end
  # end

  assert 'Module#prepend each class' do
    m = labeled_module("M")
    c1 = labeled_class("C1") {prepend m}
    c2 = labeled_class("C2", c1) {prepend m}
    assert_equal([m, c2, m, c1], c2.ancestors[0, 4], "should be able to prepend each class")
  end

  assert 'Module#prepend no duplication' do
    m = labeled_module("M")
    c = labeled_class("C") {prepend m; prepend m}
    assert_equal([m, c], c.ancestors[0, 2], "should never duplicate")
  end

  assert 'Module#prepend in superclass' do
    m = labeled_module("M")
    c1 = labeled_class("C1")
    c2 = labeled_class("C2", c1) {prepend m}
    c1.class_eval {prepend m}
    assert_equal([m, c2, m, c1], c2.ancestors[0, 4], "should accessible prepended module in superclass")
  end

  # requires #assert_separately
  #assert 'Module#prepend call super' do
  #  assert_separately([], <<-'end;') #do
  #    bug10847 = '[ruby-core:68093] [Bug #10847]'
  #    module M; end
  #    Float.prepend M
  #    assert_nothing_raised(SystemStackError, bug10847) do
  #      0.3.numerator
  #    end
  #  end
  #end

  assert 'Module#prepend to frozen class' do
    assert_raise(FrozenError) { Class.new.freeze.prepend Module.new }
  end
# @!endgroup prepend

assert('Module#to_s') do
  module Outer
    class Inner; end
    const_set :SetInner, Class.new
  end

  assert_equal 'Outer', Outer.to_s
  assert_equal 'Outer::Inner', Outer::Inner.to_s
  assert_equal 'Outer::SetInner', Outer::SetInner.to_s

  outer = Module.new do
    const_set :SetInner, Class.new
  end
  Object.const_set :SetOuter, outer

  assert_equal 'SetOuter', SetOuter.to_s
  assert_equal 'SetOuter::SetInner', SetOuter::SetInner.to_s

  assert_match "#<Module:0x*>", Module.new.to_s
  assert_match "#<Class:0x*>", Class.new.to_s

  assert_equal "FrozenClassToS", (FrozenClassToS = Class.new.freeze).to_s
  assert_equal "Outer::A", (Outer::A = Module.new.freeze).to_s
  assert_match "#<Module:0x*>::A", (Module.new::A = Class.new.freeze).to_s
end

assert('Module#inspect') do
  module Test4to_sModules
  end

  assert_equal 'Test4to_sModules', Test4to_sModules.inspect
end

assert('Issue 1467') do
  module M1
    def initialize
      super()
    end
  end

  class C1
    include M1
      def initialize
        super()
      end
  end

  class C2
    include M1
  end

  assert_kind_of(M1, C1.new)
  assert_kind_of(M1, C2.new)
end

assert('clone Module') do
  module M1
    def foo
      true
    end
  end

  class B
    include M1.clone
  end

  assert_true(B.new.foo)
end

assert('Module#module_function') do
  module M
    def modfunc; end
    module_function :modfunc
  end

  assert_true M.respond_to?(:modfunc)
end

assert('module with non-class/module outer raises TypeError') do
  assert_raise(TypeError) { module 0::M1 end }
  assert_raise(TypeError) { module []::M2 end }
end

assert('module to return the last value') do
  m = module M; :m end
  assert_equal(m, :m)
end

assert('module to return nil if body is empty') do
  assert_nil(module M end)
end

assert('get constant of parent module in singleton class; issue #3568') do
  actual = module GetConstantInSingletonTest
    EXPECTED = "value"
    class << self
      EXPECTED
    end
  end

  assert_equal("value", actual)
end
