require File.join(File.dirname(__FILE__), "..", "spec_helper" )

module GuiTest

  def should_be symbol, api
    case symbol
      when :find_window
        api.dll_name.should == 'user32' # The name of the DLL that exports the API function
        api.effective_function_name.should == 'FindWindowA' # Actual function returned by the constructor: 'GetUserName' ->'GetUserNameA' or 'GetUserNameW'
        api.function_name.should == 'FindWindow' # The name of the function passed to the constructor
        api.prototype.should == ['P', 'P'] # The prototype, returned as an array of characters
    end
  end

  def redefined_methods
    [:FindWindow, :IsWindow, :EnumWindows, :GetComputerName, :GetForegroundWindow]
  end

  def should_count_args(*methods, rights, wrongs)
    rights = [rights].flatten
    wrongs = [wrongs].flatten
    methods.each do |method|
      (0..8).each do |n|
        if n == rights.size
          expect {send method, *rights}.to_not raise_error
        else
        args = (1..n).map {wrongs[rand(wrongs.size)]}
        expect {send method, *args}.
                to raise_error "wrong number of parameters: expected #{rights.size}, got #{args.size}"
        end
      end
    end
  end

  describe WinGui::DefApi, 'defines wrappers for Win32::API functions' do
    before(:each) { hide_method *redefined_methods } # hide original methods if  defined
    after(:each) { restore_method *redefined_methods } # restore original methods if hidden

    context 'defining enhanced API function method' do
      spec{ use{ WinGui.def_api('FindWindow', 'PP', 'L', rename: nil, aliases: nil, boolean: nil, zeronil: nil, &any_block) }}

      it 'defines new instance methods with appropriate names' do
        WinGui.def_api 'FindWindow', 'PP', 'L'
        respond_to?(:find_window).should be_true
        respond_to?(:FindWindow).should be_true
      end

      it 'constructs argument prototype from uppercase string, enforces the args count' do
        expect { WinGui.def_api 'FindWindow', 'PP', 'L' }.to_not raise_error
        should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
      end

      it 'constructs argument prototype from lowercase string, enforces the args count' do
        expect { WinGui.def_api 'FindWindow', 'pp', 'l' }.to_not raise_error
        should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
      end

      it 'constructs argument prototype from (mixedcase) array, enforces the args count' do
        expect { WinGui.def_api 'FindWindow', ['p', 'P'], 'L' }.to_not raise_error
        should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
      end

      it 'with :rename option, overrides snake_case name for defined method but leaves CamelCase intact' do
        WinGui.def_api 'FindWindow', 'PP', 'L', :rename=> 'my_own_find'
        expect {find_window(nil, nil)}.to raise_error NoMethodError
        expect {FindWindow(nil, nil)}.to_not raise_error
        expect {my_own_find(nil, nil)}.to_not raise_error
      end

      it 'defined snake_case method returns expected value when called' do
        WinGui.def_api 'FindWindow', 'PP', 'L'
        find_window(nil, nil).should_not == 0
        find_window(nil, TEST_IMPOSSIBLE).should == 0
        find_window(TEST_IMPOSSIBLE, nil).should == 0
        find_window(TEST_IMPOSSIBLE, TEST_IMPOSSIBLE).should == 0
      end

      it 'defined CamelCase method returns expected value when called' do
        WinGui.def_api 'FindWindow', 'PP', 'L'
        FindWindow(nil, nil).should_not == 0
        FindWindow(nil, TEST_IMPOSSIBLE).should == 0
        FindWindow(TEST_IMPOSSIBLE, nil).should == 0
        FindWindow(TEST_IMPOSSIBLE, TEST_IMPOSSIBLE).should == 0
      end

      it 'returns underlying Win32::API object if defined method is called with (:api) argument ' do
        WinGui.def_api 'FindWindow', 'PP', 'L'
        expect {find_window(:api)}.to_not raise_error
        should_be :find_window, find_window(:api)
      end
    end

    context 'defining aliases' do
      it 'adds alias for defined method with :alias option' do
        WinGui.def_api 'FindWindow', 'PP', 'L', :alias => 'my_own_find'
        expect {find_window(nil, nil)}.to_not raise_error
        expect {my_own_find(nil, nil)}.to_not raise_error
      end

      it 'adds aliases for defined method with :aliases option' do
        WinGui.def_api 'FindWindow', 'PP', 'L', :aliases => ['my_own_find', 'my_own_find1']
        expect {find_window(nil, nil)}.to_not raise_error
        expect {my_own_find(nil, nil)}.to_not raise_error
        expect {my_own_find1(nil, nil)}.to_not raise_error
      end

      it 'adds Rubyesque alias to IsXxx API test function' do
        WinGui.def_api 'IsWindow', 'L', 'L'
        respond_to?(:window?).should be_true
        respond_to?(:is_window).should be_true
      end

      it 'adds Rubyesque alias to GetXxx API getter function' do
        WinGui.def_api 'GetComputerName', 'PP', 'I', :dll=> 'kernel32'
        respond_to?(:get_computer_name).should be_true
        respond_to?(:computer_name).should be_true
      end

    end

    context 'auto-defining Ruby-like boolean methods if API function name starts with "Is_"' do
      before(:each) {WinGui.def_api 'IsWindow', 'L', 'L'}

      it 'defines new instance method name dropping Is_ and adding ?' do
        respond_to?(:window?).should be_true
        respond_to?(:is_window).should be_true
        respond_to?(:IsWindow).should be_true
      end

      it 'defined CamelCase method returns zero/non-zero as expected' do
        IsWindow(any_handle).should_not == true
        IsWindow(any_handle).should_not == 0
        IsWindow(not_a_handle).should == 0
      end

      it 'defined snake_case method returns false/true instead of zero/non-zero' do
        window?(any_handle).should == true
        window?(not_a_handle).should == false
        is_window(any_handle).should == true
        is_window(not_a_handle).should == false
      end

      it 'defined methods enforce the argument count' do
        should_count_args :window?, :is_window, :IsWindow,  [not_a_handle], [nil, not_a_handle, any_handle]
      end
    end

    context 'defining API with :boolean option converts result to boolean' do
      before(:each) do
        WinGui.def_api 'FindWindow', 'PP', 'L', :boolean => true
      end

      it 'defines new instance method' do
        respond_to?(:find_window).should be_true
        respond_to?(:FindWindow).should be_true
      end

      it 'defined snake_case method returns false/true instead of zero/non-zero' do
        find_window(nil, nil).should == true
        find_window(nil, TEST_IMPOSSIBLE).should == false
      end

      it 'defined CamelCase method still returns zero/non-zero' do
        FindWindow(nil, nil).should_not == true
        FindWindow(nil, nil).should_not == 0
        FindWindow(nil, TEST_IMPOSSIBLE).should == 0
      end

      it 'defined methods enforce the argument count' do
        should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
      end
    end

    context 'defining API with :zeronil option converts zero result to nil' do
      before(:each) do
        WinGui.def_api 'FindWindow', 'PP', 'L', :zeronil => true
      end

      it 'defines new instance method' do
        respond_to?(:find_window).should be_true
        respond_to?(:FindWindow).should be_true
      end

      it 'defined CamelCase method still returns zero/non-zero' do
        FindWindow(nil, nil).should_not == true
        FindWindow(nil, nil).should_not == 0
        FindWindow(nil, TEST_IMPOSSIBLE).should == 0
      end

      it 'defined method returns nil (but NOT false) instead of zero' do
        find_window(nil, TEST_IMPOSSIBLE).should_not == false
        find_window(nil, TEST_IMPOSSIBLE).should == nil
      end

      it 'defined method does not return true when result is non-zero' do
        find_window(nil, nil).should_not == true
        find_window(nil, nil).should_not == 0
      end

      it 'defined methods enforce the argument count' do
        should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
      end
    end

    context 'using DLL other than default user32 with :dll option' do
      before(:each) {WinGui.def_api 'GetComputerName', 'PP', 'I', :dll=> 'kernel32'}

      it 'defines new instance method with appropriate name' do
        respond_to?(:GetComputerName).should be_true
        respond_to?(:get_computer_name).should be_true
        respond_to?(:computer_name).should be_true
      end

      it 'returns expected result' do
        WinGui.def_api 'GetComputerName', ['P', 'P'], 'I', :dll=> 'kernel32'
        hostname = `hostname`.strip.upcase
        name = " " * 128
        get_computer_name(name, "128")
        name.unpack("A*").first.should == hostname
      end
    end

    context 'trying to define an invalid API function' do
      it 'raises error when trying to define function with a wrong function name' do
        expect { WinGui.def_api 'FindWindowImpossible', 'PP', 'L' }.
                to raise_error( /Unable to load function 'FindWindowImpossible'/ )
      end
    end

    context 'defining API function using definition block' do
      it 'defines new instance method' do
        WinGui.def_api( 'FindWindow', 'PP', 'L' ){|api, *args|}
        respond_to?(:find_window).should be_true
        respond_to?(:FindWindow).should be_true
      end

      it 'does not enforce argument count outside of block' do
        WinGui.def_api( 'FindWindow', 'PP', 'L' ){|api, *args|}
        expect { find_window }.to_not raise_error
        expect { find_window(nil) }.to_not raise_error
        expect { find_window(nil, 'Str', 1) }.to_not raise_error
      end

      it 'returns block return value when defined method is called' do
        WinGui.def_api( 'FindWindow', 'PP', 'L' ){|api, *args| 'Value'}
        find_window(nil).should == 'Value'
      end

      it 'passes arguments and underlying Win32::API object to the block' do
        WinGui.def_api( 'FindWindow', 'PP', 'L' ) do |api, *args|
          @api = api
          @args = args
        end
        expect {find_window(1, 2, 3) }.to_not raise_error
        @args.should == [1, 2, 3]
        should_be :find_window, @api
      end

      it ':rename option overrides standard name for defined method' do
        WinGui.def_api( 'FindWindow', 'PP', 'L', :rename => 'my_own_find' ){|api, *args|}
        expect {find_window(nil, nil, nil)}.to raise_error
        expect {my_own_find(nil, nil)}.to_not raise_error
      end

      it 'adds alias for defined method with :alias option' do
        WinGui.def_api( 'FindWindow', 'PP', 'L', :alias => 'my_own_find' ){|api, *args|}
        expect {find_window(nil, nil)}.to_not raise_error
        expect {my_own_find(nil, nil)}.to_not raise_error
      end

      it 'adds aliases for defined method with :aliases option' do
        WinGui.def_api( 'FindWindow', 'PP', 'L', :aliases => ['my_own_find', 'my_own_find1'] ) {|api, *args|}
        expect {find_window(nil, nil)}.to_not raise_error
        expect {my_own_find(nil, nil)}.to_not raise_error
        expect {my_own_find1(nil, nil)}.to_not raise_error
      end

      it 'returns underlying Win32::API object if defined method is called with (:api) argument ' do
        WinGui.def_api( 'FindWindow', 'PP', 'L' ){|api, *args|}
        expect {find_window(:api)}.to_not raise_error
        should_be :find_window, find_window(:api)
      end
    end

    context 'calling defined methods with attached block to preprocess the API function results' do
      it 'defined method yields raw result to block attached to its invocation' do
        WinGui.def_api 'FindWindow', 'PP', 'L', zeronil: true
        find_window(nil, TEST_IMPOSSIBLE) {|result| result.should == 0 }
      end

      it 'defined method returns result of block attached to its invocation' do
        WinGui.def_api 'FindWindow', 'PP', 'L', zeronil: true
        return_value = find_window(nil, TEST_IMPOSSIBLE) {|result| 'Value'}
        return_value.should == 'Value'
      end

      it 'defined method transforms result of block before returning it' do
        WinGui.def_api 'FindWindow', 'PP', 'L', zeronil: true
        return_value = find_window(nil, TEST_IMPOSSIBLE) {|result| 0 }
        return_value.should_not == 0
        return_value.should == nil
      end
    end

    context 'defining API function without arguments - f(VOID)' do
      it 'should enforce argument count to 0, NOT 1 (enhanced methods)' do
        WinGui.def_api 'GetForegroundWindow', 'V', 'L', zeronil: true
        should_count_args :get_foreground_window, :foreground_window, [], [nil, 0, 123]
      end

      it 'should NOT enforce argument count (raw method)' do
        WinGui.def_api 'GetForegroundWindow', 'V', 'L', zeronil: true
        expect {GetForegroundWindow()}.to_not raise_error
        expect {GetForegroundWindow(nil)}.to_not raise_error
        expect {GetForegroundWindow(1,2)}.to_not raise_error
        expect {GetForegroundWindow(1,2,3)}.to_not raise_error
      end
    end

    context 'working with API function callbacks' do
      it '#callback method creates a valid callback object' do
        expect { @callback = WinGui.callback('LP', 'I') {|handle, message| true} }.to_not raise_error
        @callback.should be_a_kind_of(Win32::API::Callback)
      end

      it 'created callback object can be used as a valid arg of API function expecting callback' do
        WinGui.def_api 'EnumWindows', 'KP', 'L'
        @callback = WinGui.callback('LP', 'I'){|handle, message| true }
        expect { enum_windows(@callback, 'Message') }.to_not raise_error
      end

      it 'defined API functions expecting callback convert given block into callback' do
        pending ' What about prototype!? API is not exactly clear atm (.with_callback method?)'
        WinGui.def_api 'EnumWindows', 'KP', 'L'
        expect { enum_windows('Message'){|handle, message| true } }.to_not raise_error
      end
    end
  end
end