# frozen_string_literal: true

require 'gir_ffi_test_helper'

GirFFI.setup :Regress
GirFFI.setup :GIMarshallingTests

describe GirFFI::Builder do
  let(:gir) { GObjectIntrospection::IRepository.default }

  describe '.build_class' do
    it 'does not replace existing classes' do
      oldclass = GObject::Object
      GirFFI::Builder.build_class get_introspection_data('GObject', 'Object')
      GObject::Object.must_equal oldclass
    end
  end

  describe '.build_module' do
    it 'refuses to build existing modules defined elsewhere' do
      result = -> { GirFFI::Builder.build_module('Array') }.must_raise
      result.message.must_equal 'The module Array was already defined elsewhere'
    end

    describe 'building a module for the first time' do
      before do
        save_module :Regress
        GirFFI::Builder.build_module 'Regress'
      end

      it 'creates a Lib module ready to attach functions from the shared library' do
        gir = GObjectIntrospection::IRepository.default
        expected = [gir.shared_library('Regress')]
        assert_equal expected, Regress::Lib.ffi_libraries.map(&:name)
      end

      after do
        restore_module :Regress
      end
    end

    describe 'building a module that already exists' do
      it 'does not replace the existing module' do
        oldmodule = Regress
        GirFFI::Builder.build_module 'Regress'
        assert_equal oldmodule, Regress
      end

      it 'does not replace the existing Lib module' do
        oldmodule = Regress::Lib
        GirFFI::Builder.build_module 'Regress'
        assert_equal oldmodule, Regress::Lib
      end
    end

    it 'passes the version on to ModuleBuilder' do
      builder = double(generate: nil)
      expect(GirFFI::Builders::ModuleBuilder).to receive(:new).
        with('Foo', namespace: 'Foo', version: '1.0').
        and_return builder

      GirFFI::Builder.build_module 'Foo', '1.0'
    end
  end

  describe '.build_by_gtype' do
    it 'returns the class types known to the GIR' do
      result = GirFFI::Builder.build_by_gtype GObject::Object.gtype
      result.must_equal GObject::Object
    end

    it 'returns the class for user-defined types' do
      klass = Class.new GIMarshallingTests::OverridesObject
      Object.const_set "Derived#{Sequence.next}", klass
      gtype = GirFFI.define_type klass

      found_klass = GirFFI::Builder.build_by_gtype gtype
      found_klass.must_equal klass
    end

    it 'returns a valid class for boxed classes unknown to GIR' do
      object_class = GIMarshallingTests::PropertiesObject.object_class
      property = object_class.find_property 'some-boxed-glist'
      gtype = property.value_type

      gtype.wont_equal GObject::TYPE_NONE

      found_klass = GirFFI::Builder.build_by_gtype gtype
      found_klass.name.must_be_nil
      found_klass.ancestors.must_include GirFFI::BoxedBase
    end
  end

  describe '.attach_ffi_function' do
    let(:lib) { Module.new }

    it 'calls attach_function with the correct types for Regress.test_callback_destroy_notify' do
      function_info = get_introspection_data 'Regress', 'test_callback_destroy_notify'

      expect(lib).
        to receive(:attach_function).
        with('regress_test_callback_destroy_notify',
             [Regress::TestCallbackUserData, :pointer, GLib::DestroyNotify],
             :int32).
        and_return true

      GirFFI::Builder.attach_ffi_function(lib, function_info)
    end

    it 'calls attach_function with the correct types for Regress::TestObj#torture_signature_0' do
      info = get_method_introspection_data 'Regress', 'TestObj', 'torture_signature_0'

      expect(lib).
        to receive(:attach_function).
        with('regress_test_obj_torture_signature_0',
             [:pointer, :int32, :pointer, :pointer, :pointer, :pointer, :uint32],
             :void).
        and_return true

      GirFFI::Builder.attach_ffi_function(lib, info)
    end

    it 'calls attach_function with the correct types for Regress::TestObj#instance_method' do
      info = get_method_introspection_data 'Regress', 'TestObj', 'instance_method'
      expect(lib).to receive(:attach_function).
        with('regress_test_obj_instance_method', [:pointer], :int32).
        and_return true
      GirFFI::Builder.attach_ffi_function(lib, info)
    end

    it 'calls attach_function with the correct types for Regress.test_array_gint32_in' do
      info = get_introspection_data 'Regress', 'test_array_gint32_in'
      expect(lib).to receive(:attach_function).
        with('regress_test_array_gint32_in', [:int32, :pointer], :int32).
        and_return true
      GirFFI::Builder.attach_ffi_function(lib, info)
    end

    it 'does not attach the function if it is already defined' do
      info = get_introspection_data 'Regress', 'test_array_gint32_in'
      allow(lib).to receive(:method_defined?).and_return true
      expect(lib).not_to receive(:attach_function)
      GirFFI::Builder.attach_ffi_function(lib, info)
    end
  end

  #
  # NOTE: Legacy tests below.
  #

  describe 'looking at Regress.test_callback_destroy_notify' do
    before do
      save_module :GObject
      save_module :Regress
      GirFFI::Builder.build_module 'GObject'
      GirFFI::Builder.build_module 'Regress'
      @go = get_introspection_data 'Regress', 'test_callback_destroy_notify'
    end

    it 'defines ffi callback types :Callback and :ClosureNotify' do
      Regress.setup_method 'test_callback_destroy_notify'
      tcud = Regress::Lib.find_type :TestCallbackUserData
      dn = GLib::Lib.find_type :DestroyNotify

      assert_equal FFI.find_type(:int32), tcud.result_type
      assert_equal FFI.find_type(:void), dn.result_type
      assert_equal [FFI.find_type(:pointer)], tcud.param_types
      assert_equal [FFI.find_type(:pointer)], dn.param_types
    end

    after do
      restore_module :Regress
      restore_module :GObject
    end
  end

  describe 'building Regress::TestBoxed' do
    before do
      GirFFI::Builder.build_class get_introspection_data('Regress', 'TestBoxed')
    end

    it 'sets up #wrap' do
      assert Regress::TestBoxed.respond_to? 'wrap'
    end

    it 'sets up #allocate' do
      assert Regress::TestBoxed.respond_to? 'allocate'
    end
  end

  describe 'built Regress module' do
    before do
      save_module :Regress
      GirFFI::Builder.build_module 'Regress'
    end

    it 'autocreates singleton methods' do
      refute_defines_singleton_method Regress, :test_uint
      Regress.test_uint 31
      assert_defines_singleton_method Regress, :test_uint
    end

    it 'autocreates the TestObj class on first access' do
      assert !Regress.const_defined?(:TestObj)
      Regress::TestObj.must_be_instance_of Class
      assert Regress.const_defined? :TestObj
    end

    it 'knows its own module builder' do
      Regress.gir_ffi_builder.must_be_instance_of GirFFI::Builders::ModuleBuilder
    end

    after do
      restore_module :Regress
    end
  end

  describe 'having built Regress::TestObj' do
    before do
      GirFFI::Builder.build_class get_introspection_data('Regress', 'TestObj')
    end

    it 'C functions for called instance methods get attached to Regress::Lib' do
      o = Regress::TestObj.new_from_file('foo')
      o.instance_method
      Regress::Lib.must_respond_to :regress_test_obj_instance_method
    end

    it 'the built class knows its own GIR info' do
      Regress::TestObj.gir_info.name.must_equal 'TestObj'
    end

    it 'the built class knows its own class builder' do
      Regress::TestObj.gir_ffi_builder.must_be_instance_of GirFFI::Builders::ObjectBuilder
    end
  end

  describe 'built Regress::TestSubObj' do
    it 'inherits #set_bare from its superclass' do
      o1 = Regress::TestSubObj.new
      o1.set_bare(nil)
      pass
    end

    it 'overrides #instance_method' do
      obj = Regress::TestObj.new_from_file('foo')
      subobj = Regress::TestSubObj.new

      obj.instance_method.must_equal(-1)
      subobj.instance_method.must_equal 0
    end
  end

  describe 'building Regress::TestSubObj' do
    before do
      save_module :Regress
      GirFFI::Builder.build_class get_introspection_data('Regress', 'TestSubObj')
    end

    it 'sets up the Regress namespace' do
      assert Regress.const_defined? :Lib
      assert Regress.respond_to? :gir_ffi_builder
      assert Regress.const_defined? :GIR_FFI_BUILDER
    end

    it 'creates the Regress::Lib module ready to attach functions from the shared library' do
      expected = [gir.shared_library('Regress')]
      assert_equal expected, Regress::Lib.ffi_libraries.map(&:name)
    end

    it 'builds parent classes also' do
      assert Regress.const_defined? :TestObj
      assert Object.const_defined? :GObject
      assert GObject.const_defined? :Object
    end

    it 'sets up the inheritance chain' do
      ancestors = Regress::TestSubObj.ancestors
      assert_equal [
        Regress::TestSubObj,
        Regress::TestObj,
        GObject::Object
      ], ancestors[0..2]
    end

    it 'creates a Regress::TestSubObj#to_ptr method' do
      assert Regress::TestSubObj.public_method_defined? :to_ptr
    end

    after do
      restore_module :Regress
    end
  end
end
