# frozen_string_literal: true

require "gir_ffi_test_helper"

describe GirFFI::Builders::ArgumentBuilder do
  let(:var_gen) { GirFFI::VariableNameGenerator.new }
  let(:builder) { GirFFI::Builders::ArgumentBuilder.new(var_gen, arg_info) }

  describe "for an argument with direction :in" do
    describe "for :c" do
      describe "with full ownership transfer" do
        let(:arg_info) do
          get_introspection_data("GIMarshallingTests", "array_struct_take_in").args[0]
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion)
            .must_equal ["_v1 = GirFFI::SizedArray.copy_from([:pointer, GIMarshallingTests::BoxedStruct], -1, structs)"]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion).must_equal []
        end
      end
    end

    describe "for :callback" do
      let(:arg_info) do
        get_introspection_data("Regress", "test_callback_destroy_notify").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = Regress::TestCallbackUserData.from(callback)"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal []
      end
    end

    describe "for :zero_terminated" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests",
                               "array_in_len_zero_terminated").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = GirFFI::ZeroTerminated.from(:gint32, ints)"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal []
      end
    end

    describe "for :void" do
      let(:arg_info) { get_introspection_data("Regress", "test_callback_user_data").args[1] }

      describe "when it is a regular argument" do
        it "has the correct value for method_argument_name" do
          _(builder.method_argument_name).must_equal arg_info.name
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal ["_v1 = user_data"]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion).must_equal []
        end
      end

      describe "when it is a closure" do
        before do
          callback = Object.new
          allow(callback).to receive(:call_argument_name).and_return "foo"
          builder.mark_as_user_data callback
        end

        it "has the correct value for method_argument_name" do
          _(builder.method_argument_name).must_be_nil
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal ["_v1 = GirFFI::ArgHelper.store(foo)"]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion).must_equal []
        end
      end
    end
  end

  describe "for an argument with direction :out" do
    describe "for :enum" do
      let(:arg_info) { get_introspection_data("GIMarshallingTests", "genum_out").args[0] }

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new GIMarshallingTests::GEnum"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = GIMarshallingTests::GEnum.get_value_from_pointer(_v1, 0)"]
      end
    end

    describe "for :flags" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "flags_out").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new GIMarshallingTests::Flags"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = GIMarshallingTests::Flags.get_value_from_pointer(_v1, 0)"]
      end
    end

    describe "for :object" do
      describe "with full ownership transfer" do
        let(:arg_info) do
          get_method_introspection_data("Regress", "TestObj", "null_out").args[0]
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion).must_equal ["_v2 = Regress::TestObj.wrap(_v1.get_pointer(0))"]
        end
      end

      describe "with no ownership transfer" do
        let(:arg_info) do
          get_method_introspection_data("GIMarshallingTests", "Object", "none_out").args[0]
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion)
            .must_equal ["_v2 = GIMarshallingTests::Object.wrap(_v1.get_pointer(0)).tap { |it| it && it.ref }"]
        end
      end
    end

    describe "for :struct" do
      describe "when not allocated by the caller" do
        let(:arg_info) do
          get_introspection_data("GIMarshallingTests", "boxed_struct_out").args[0]
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal [
            "_v1 = FFI::MemoryPointer.new :pointer"
          ]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion)
            .must_equal ["_v2 = GIMarshallingTests::BoxedStruct.wrap_copy(_v1.get_pointer(0))"]
        end
      end

      describe "when allocated by the caller" do
        let(:arg_info) do
          get_method_introspection_data("Regress", "TestStructA", "clone").args[0]
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal ["_v1 = Regress::TestStructA.new"]
        end

        it "has empty #post_conversion" do
          _(builder.post_conversion).must_equal []
        end
      end
    end

    describe "for :strv" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "gstrv_out").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = GLib::Strv.wrap(_v1.get_pointer(0))"]
      end
    end

    describe "for :array" do
      describe "when allocated by the callee" do
        let(:arg_info) do
          get_introspection_data("GIMarshallingTests", "garray_utf8_none_out").args[0]
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion).must_equal ["_v2 = GLib::Array.wrap(:utf8, _v1.get_pointer(0))"]
        end
      end

      describe "when allocated by the caller" do
        let(:function_info) do
          get_introspection_data("GIMarshallingTests", "garray_utf8_full_out_caller_allocated")
        end

        let(:arg_info) { function_info.args[0] }

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal ["_v1 = GLib::Array.new :utf8"]
        end

        it "has empty #post_conversion" do
          _(builder.post_conversion).must_equal []
        end
      end
    end

    describe "for :ptr_array" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "gptrarray_utf8_none_out").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = GLib::PtrArray.wrap(:utf8, _v1.get_pointer(0))"]
      end
    end

    describe "for :error" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "gerror_out").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = GLib::Error.wrap(_v1.get_pointer(0))"]
      end
    end

    describe "for :c" do
      describe "with fixed size" do
        let(:arg_info) do
          get_introspection_data("GIMarshallingTests", "array_fixed_out").args[0]
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion).must_equal ["_v2 = GirFFI::SizedArray.wrap(:gint32, 4, _v1.get_pointer(0))"]
        end
      end

      describe "with separate size parameter" do
        let(:arg_info) do
          get_introspection_data("GIMarshallingTests", "array_out").args[0]
        end

        let(:length_argument) { Object.new }
        before do
          allow(length_argument).to receive(:post_converted_name).and_return "bar"
          builder.length_arg = length_argument
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion).must_equal ["_v2 = GirFFI::SizedArray.wrap(:gint32, bar, _v1.get_pointer(0))"]
        end
      end
    end

    describe "for :glist" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "glist_utf8_none_out").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = GLib::List.wrap(:utf8, _v1.get_pointer(0))"]
      end
    end

    describe "for :gslist" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "gslist_utf8_none_out").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = GLib::SList.wrap(:utf8, _v1.get_pointer(0))"]
      end
    end

    describe "for :ghash" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "ghashtable_utf8_none_out").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = GLib::HashTable.wrap([:utf8, :utf8], _v1.get_pointer(0))"]
      end
    end

    describe "for :utf8" do
      describe "with full ownership transfer" do
        let(:arg_info) do
          get_introspection_data("GIMarshallingTests", "utf8_full_out").args[0]
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion).must_equal [
            "_v2 = GirFFI::AllocationHelper.free_after _v1.get_pointer(0), &:to_utf8"
          ]
        end

        it "has the correct value for #return_value_name" do
          builder.post_conversion
          _(builder.return_value_name).must_equal "_v2"
        end
      end

      describe "with no ownership transfer" do
        let(:arg_info) do
          get_introspection_data("GIMarshallingTests", "utf8_none_out").args[0]
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion).must_equal ["_v2 = _v1.get_pointer(0).to_utf8"]
        end
      end
    end

    describe "for :void" do
      let(:function_info) do
        get_introspection_data("Everything", "one_outparam_gpointer")
      end
      let(:arg_info) { function_info.args[0] }

      before { skip_below "1.47.1" }

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = _v1.get_pointer(0)"]
      end
    end
  end

  describe "for an argument with direction :inout" do
    describe "for :enum" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "enum_inout").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new GIMarshallingTests::Enum",
                                              "GIMarshallingTests::Enum.copy_value_to_pointer(#{arg_info.name}, _v1)"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = GIMarshallingTests::Enum.get_value_from_pointer(_v1, 0)"]
      end
    end

    describe "for :flags" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "no_type_flags_inout").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion)
          .must_equal ["_v1 = FFI::MemoryPointer.new GIMarshallingTests::NoTypeFlags",
                       "GIMarshallingTests::NoTypeFlags.copy_value_to_pointer(#{arg_info.name}, _v1)"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = GIMarshallingTests::NoTypeFlags.get_value_from_pointer(_v1, 0)"]
      end
    end

    describe "for :gint32" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "int32_inout_min_max").args[0]
      end

      it "has the correct value for method_argument_name" do
        _(builder.method_argument_name).must_equal arg_info.name
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :int32",
                                              "_v1.put_int32 0, #{arg_info.name}"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = _v1.get_int32(0)"]
      end
    end

    describe "for an array length" do
      let(:function_info) do
        get_introspection_data("Regress", "test_array_int_inout")
      end
      let(:arg_info) { function_info.args[0] }
      let(:array_arg_info) { function_info.args[1] }
      let(:array_arg_builder) do
        GirFFI::Builders::ArgumentBuilder.new(var_gen, array_arg_info)
      end

      before do
        builder.array_arg = array_arg_builder
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["n_ints = ints.nil? ? 0 : ints.length",
                                              "_v1 = FFI::MemoryPointer.new :int32",
                                              "_v1.put_int32 0, n_ints"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = _v1.get_int32(0)"]
      end
    end

    describe "for :strv" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "gstrv_inout").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer",
                                              "_v1.put_pointer 0, GLib::Strv.from(g_strv)"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = GLib::Strv.wrap(_v1.get_pointer(0))"]
      end
    end

    describe "for :ptr_array" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "gptrarray_utf8_none_inout").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer",
                                              "_v1.put_pointer 0, GLib::PtrArray.from(:utf8, parray_)"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = GLib::PtrArray.wrap(:utf8, _v1.get_pointer(0))"]
      end
    end

    describe "for :utf8" do
      let(:arg_info) do
        get_introspection_data("GIMarshallingTests", "utf8_none_inout").args[0]
      end

      it "has the correct value for #pre_conversion" do
        _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :pointer",
                                              "_v1.put_pointer 0, GirFFI::InPointer.from_utf8(utf8)"]
      end

      it "has the correct value for #post_conversion" do
        _(builder.post_conversion).must_equal ["_v2 = _v1.get_pointer(0).to_utf8"]
      end
    end

    describe "for :struct" do
      describe "with full ownership transfer" do
        let(:arg_info) do
          get_introspection_data("GIMarshallingTests", "boxed_struct_inout").args[0]
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal [
            "_v1 = FFI::MemoryPointer.new :pointer",
            "_v1.put_pointer 0, GIMarshallingTests::BoxedStruct.copy_from(struct_)"
          ]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion)
            .must_equal ["_v2 = GIMarshallingTests::BoxedStruct.wrap_own(_v1.get_pointer(0))"]
        end
      end
    end

    describe "for :c" do
      describe "with fixed size" do
        let(:arg_info) do
          get_introspection_data("GIMarshallingTests", "array_fixed_inout").args[0]
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal [
            'GirFFI::ArgHelper.check_fixed_array_size 4, ints, "ints"',
            "_v1 = FFI::MemoryPointer.new :pointer",
            "_v1.put_pointer 0, GirFFI::SizedArray.from(:gint32, 4, ints)"
          ]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion).must_equal ["_v2 = GirFFI::SizedArray.wrap(:gint32, 4, _v1.get_pointer(0))"]
        end
      end

      describe "with separate size parameter" do
        let(:function_info) do
          get_introspection_data("Regress", "test_array_int_inout")
        end
        let(:length_arg_info) { function_info.args[0] }
        let(:arg_info) { function_info.args[1] }
        let(:length_arg_builder) do
          GirFFI::Builders::ArgumentBuilder.new(var_gen, length_arg_info)
        end

        before do
          builder.length_arg = length_arg_builder
        end

        it "has the correct value for #pre_conversion" do
          _(builder.pre_conversion).must_equal [
            "_v1 = FFI::MemoryPointer.new :pointer",
            "_v1.put_pointer 0, GirFFI::SizedArray.copy_from(:gint32, -1, ints)"
          ]
        end

        it "has the correct value for #post_conversion" do
          _(builder.post_conversion).must_equal ["_v3 = GirFFI::SizedArray.wrap(:gint32, _v2, _v1.get_pointer(0))"]
        end
      end
    end
  end

  describe "for a skipped argument with direction :in" do
    let(:arg_info) do
      get_method_introspection_data("Regress", "TestObj", "skip_param").args[2]
    end

    it "has the correct value for method_argument_name" do
      _(builder.method_argument_name).must_be_nil
    end

    it "has the correct value for #pre_conversion" do
      _(builder.pre_conversion).must_equal ["_v1 = 0"]
    end

    it "has the correct value for #post_conversion" do
      _(builder.post_conversion).must_equal []
    end
  end

  describe "for a skipped argument with direction :inout" do
    let(:arg_info) do
      get_method_introspection_data("Regress", "TestObj", "skip_inout_param").args[3]
    end

    it "has the correct value for method_argument_name" do
      _(builder.method_argument_name).must_be_nil
    end

    it "has the correct value for #pre_conversion" do
      _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :int32",
                                            "_v1.put_int32 0, 0"]
    end

    it "has the correct value for #post_conversion" do
      _(builder.post_conversion).must_equal []
    end
  end

  describe "for a skipped argument with direction :out" do
    let(:arg_info) do
      get_method_introspection_data("Regress", "TestObj", "skip_out_param").args[1]
    end

    it "has the correct value for method_argument_name" do
      _(builder.method_argument_name).must_be_nil
    end

    it "has the correct value for #pre_conversion" do
      _(builder.pre_conversion).must_equal ["_v1 = FFI::MemoryPointer.new :int32"]
    end

    it "has the correct value for #post_conversion" do
      _(builder.post_conversion).must_equal []
    end
  end

  describe "for an argument with direction :error" do
    let(:arg_info) { GirFFI::ErrorArgumentInfo.new }

    it "has the correct value for #pre_conversion" do
      _(builder.pre_conversion).must_equal [
        "_v1 = FFI::MemoryPointer.new(:pointer).write_pointer nil"
      ]
    end

    it "has the correct value for #post_conversion" do
      _(builder.post_conversion).must_equal [
        "GirFFI::ArgHelper.check_error(_v1)"
      ]
    end
  end
end
