# encoding: UTF-8
shared_examples_for "things that declare options" do
  it "should support options without arguments" do
    thing = add_options_to { option "--bar" }
    expect(thing).to be_option :bar
  end

  it "should support options with an empty block" do
    thing = add_options_to do
      option "--foo" do
        # this section deliberately left blank
      end
    end
    expect(thing).to be
    expect(thing).to be_option :foo
  end

  { "--foo=" => :foo }.each do |input, option|
    it "should accept #{name.inspect}" do
      thing = add_options_to { option input }
      expect(thing).to be_option option
    end
  end

  it "should support option documentation" do
    text = "Sturm und Drang (German pronunciation: [ˈʃtʊʁm ʊnt ˈdʁaŋ]) …"

    thing = add_options_to do
      option "--foo" do
        description text
        summary text
      end
    end

    expect(thing.get_option(:foo).description).to eq(text)
  end

  it "should list all the options" do
    thing = add_options_to do
      option "--foo"
      option "--bar", '-b'
      option "-q", "--quux"
      option "-f"
      option "--baz"
    end
    expect(thing.options).to eq([:foo, :bar, :quux, :f, :baz])
  end

  it "should detect conflicts in long options" do
    expect {
      add_options_to do
        option "--foo"
        option "--foo"
      end
    }.to raise_error ArgumentError, /Option foo conflicts with existing option foo/i
  end

  it "should detect conflicts in short options" do
    expect {
      add_options_to do
        option "-f"
        option "-f"
      end
    }.to raise_error ArgumentError, /Option f conflicts with existing option f/
  end

  ["-f", "--foo"].each do |option|
    ["", " FOO", "=FOO", " [FOO]", "=[FOO]"].each do |argument|
      input = option + argument
      it "should detect conflicts within a single option like #{input.inspect}" do
        expect {
          add_options_to do
            option input, input
          end
        }.to raise_error ArgumentError, /duplicates existing alias/
      end
    end
  end


  # Verify the range of interesting conflicts to check for ordering causing
  # the behaviour to change, or anything exciting like that.
  [ %w{--foo}, %w{-f}, %w{-f --foo}, %w{--baz -f},
    %w{-f --baz}, %w{-b --foo}, %w{--foo -b}
  ].each do |conflict|
    base = %w{--foo -f}
    it "should detect conflicts between #{base.inspect} and #{conflict.inspect}" do
      expect {
        add_options_to do
          option(*base)
          option(*conflict)
        end
      }.to raise_error ArgumentError, /conflicts with existing option/
    end
  end

  it "should fail if we are not consistent about taking an argument" do
    expect { add_options_to do option "--foo=bar", "--bar" end }.
      to raise_error ArgumentError, /inconsistent about taking an argument/
  end

  it "should not accept optional arguments" do
    expect do
      thing = add_options_to do option "--foo=[baz]", "--bar=[baz]" end
      [:foo, :bar].each do |name|
        expect(thing).to be_option name
      end
    end.to raise_error(ArgumentError, /optional arguments are not supported/)
  end

  describe "#takes_argument?" do
    it "should detect an argument being absent" do
      thing = add_options_to do option "--foo" end
      expect(thing.get_option(:foo)).not_to be_takes_argument
    end
    ["=FOO", " FOO"].each do |input|
      it "should detect an argument given #{input.inspect}" do
        thing = add_options_to do option "--foo#{input}" end
        expect(thing.get_option(:foo)).to be_takes_argument
      end
    end
  end

  describe "#optional_argument?" do
    it "should be false if no argument is present" do
      option = add_options_to do option "--foo" end.get_option(:foo)
      expect(option).not_to be_takes_argument
      expect(option).not_to be_optional_argument
    end

    ["=FOO", " FOO"].each do |input|
      it "should be false if the argument is mandatory (like #{input.inspect})" do
        option = add_options_to do option "--foo#{input}" end.get_option(:foo)
      expect(option).to be_takes_argument
      expect(option).not_to be_optional_argument
      end
    end

    ["=[FOO]", " [FOO]"].each do |input|
      it "should fail if the argument is optional (like #{input.inspect})" do
        expect do
          option = add_options_to do option "--foo#{input}" end.get_option(:foo)
          expect(option).to be_takes_argument
          expect(option).to be_optional_argument
        end.to raise_error(ArgumentError, /optional arguments are not supported/)
      end
    end
  end

  describe "#default_to" do
    it "should not have a default value by default" do
      option = add_options_to do option "--foo" end.get_option(:foo)
      expect(option).not_to be_has_default
    end

    it "should accept a block for the default value" do
      option = add_options_to do
        option "--foo" do
          default_to do
            12
          end
        end
      end.get_option(:foo)

      expect(option).to be_has_default
    end

    it "should invoke the block when asked for the default value" do
      invoked = false
      option = add_options_to do
        option "--foo" do
          default_to do
            invoked = true
          end
        end
      end.get_option(:foo)

      expect(option).to be_has_default
      expect(option.default).to be_truthy
      expect(invoked).to be_truthy
    end

    it "should return the value of the block when asked for the default" do
      option = add_options_to do
        option "--foo" do
          default_to do
            12
          end
        end
      end.get_option(:foo)

      expect(option).to be_has_default
      expect(option.default).to eq(12)
    end

    it "should invoke the block every time the default is requested" do
      option = add_options_to do
        option "--foo" do
          default_to do
            {}
          end
        end
      end.get_option(:foo)

      first  = option.default.object_id
      second = option.default.object_id
      third  = option.default.object_id

      expect(first).not_to eq(second)
      expect(first).not_to eq(third)
      expect(second).not_to eq(third)
    end

    it "should fail if the option has a default and is required" do
      expect {
        add_options_to do
          option "--foo" do
            required
            default_to do 12 end
          end
        end
      }.to raise_error ArgumentError, /can't be optional and have a default value/

      expect {
        add_options_to do
          option "--foo" do
            default_to do 12 end
            required
          end
        end
      }.to raise_error ArgumentError, /can't be optional and have a default value/
    end

    it "should fail if default_to has no block" do
      expect { add_options_to do option "--foo" do default_to end end }.
        to raise_error ArgumentError, /default_to requires a block/
    end

    it "should fail if default_to is invoked twice" do
      expect {
        add_options_to do
          option "--foo" do
            default_to do 12 end
            default_to do "fun" end
          end
        end
      }.to raise_error ArgumentError, /already has a default value/
    end

    [ "one", "one, two", "one, *two" ].each do |input|
      it "should fail if the block has the wrong arity (#{input})" do
        expect {
          add_options_to do
            option "--foo" do
              eval "default_to do |#{input}| 12 end"
            end
          end
        }.to raise_error ArgumentError, /should not take any arguments/
      end
    end
  end
end
