require 'spec_helper'
require 'command_mapper/argument'
require 'command_mapper/types/list'
require 'command_mapper/types/key_value'

describe CommandMapper::Argument do
  let(:name) { :foo }

  describe "#initialize" do
    subject { described_class.new(name) }

    it "must set #name" do
      expect(subject.name).to eq(name)
    end

    it "must default #required? to true" do
      expect(subject.required?).to be(true)
    end

    it "must default #type to a Types::Str object" do
      expect(subject.type).to be_kind_of(Types::Str)
    end

    it "must default #repeats? to false" do
      expect(subject.repeats?).to be(false)
    end

    context "when given the required: false keyword argument" do
      subject { described_class.new(name, required: false) }

      it "must set #required? to false" do
        expect(subject.required?).to be(false)
      end
    end

    context "when given the type: keyword argument" do
      context "and it's a Types::Type object" do
        let(:type) { Types::KeyValue.new }

        subject { described_class.new(name, type: type) }

        it "must set #type" do
          expect(subject.type).to eq(type)
        end
      end
    end

    context "when given the repeats: true keyword argument" do
      subject { described_class.new(name, repeats: true) }

      it "#repeats? must be true" do
        expect(subject.repeats?).to be(true)
      end
    end
  end

  let(:required) { true  }
  let(:repeats)  { false }
  let(:value_allows_empty) { false }
  let(:value_allows_blank) { false }
  let(:type) do
    {
      allow_empty: value_allows_empty,
      allow_blank: value_allows_blank
    }
  end

  subject do
    described_class.new(name, required: required,
                              type:     type,
                              repeats:  repeats)
  end

  describe "#validate" do
    context "when the argument requires a value" do
      let(:required) { true }

      context "when the argument can be specified multiple times" do
        let(:repeats) { true }

        context "and it's given an Array" do
          context "and all elements of the Array are Strings" do
            let(:value) { %w[foo bar baz] }

            it "must return true" do
              expect(subject.validate(value)).to be(true)
            end
          end

          context "but one of the Array's elements is nil" do
            let(:values) { ["foo", nil, "bar"] }

            it "must return [false, \"cannot be nil\"]" do
              expect(subject.validate(values)).to eq(
                [false, "cannot be nil"]
              )
            end
          end

          context "but the Array contains Hashes" do
            let(:values) do
              [{"foo" => 1}, {"bar" => 2 }]
            end

            it "must return [false, \"cannot convert a Hash into a String (...)\"]" do
              expect(subject.validate(values)).to eq(
                [false, "cannot convert a Hash into a String (#{values[0].inspect})"]
              )
            end

            context "but #type is a Types::KeyValue object" do
              let(:type) { Types::KeyValue.new }

              let(:values) do
                [{"foo" => 1}, {"bar" => 2 }]
              end

              it "must return true" do
                expect(subject.validate(values)).to be(true)
              end

              context "but one of the Hashes is empty" do
                let(:values) do
                  [{"foo" => 1}, {}]
                end

                it "must return [false, \"requires at least one value\"]" do
                  expect(subject.validate(values)).to eq(
                    [false, "cannot be empty"]
                  )
                end
              end
            end
          end

          context "but it's empty" do
            let(:value) { [] }

            it "must return [false, \"requires at least one value\"]" do
              expect(subject.validate(value)).to eq(
                [false, "requires at least one value"]
              )
            end
          end
        end

        context "is given a String" do
          let(:value) { "foo" }

          it "must return true" do
            expect(subject.validate(value)).to be(true)
          end
        end

        context "and it's only given one value" do
          context "and it's a String" do
            let(:value) { "foo" }

            it "must return true" do
              expect(subject.validate(value)).to be(true)
            end
          end

          context "and it's a Hash" do
            let(:value) do
              {"foo" => "bar"}
            end

            it "must return [false, \"cannot convert a Hash into a String (...)\"]" do
              expect(subject.validate(value)).to eq(
                [false, "cannot convert a Hash into a String (#{value.inspect})"]
              )
            end

            context "but #type is a Types::KeyValue object" do
              let(:type) { Types::KeyValue.new }

              it "must return true" do
                expect(subject.validate(value)).to be(true)
              end

              context "but it's empty" do
                let(:value) { {} }

                it "must return [false, \"cannot be empty\"]" do
                  expect(subject.validate(value)).to eq(
                    [false, "cannot be empty"]
                  )
                end
              end
            end
          end
        end
      end

      context "but the argument can only be specified once" do
        let(:repeats) { false }

        context "and is given a String" do
          let(:value) { "foo" }

          it "must return true" do
            expect(subject.validate(value)).to be(true)
          end
        end

        context "and is given an Array" do
          let(:value) { [1,2,3,4] }

          it "must return [false, \"cannot convert a Array into a String (...)\"]" do
            expect(subject.validate(value)).to eq(
              [false, "cannot convert a Array into a String (#{value.inspect})"]
            )
          end

          context "when #type is a Types::List object" do
            let(:type) { Types::List.new }

            it "must return true" do
              expect(subject.validate(value)).to be(true)
            end

            context "but one of the Array elements is nil" do
              let(:value) { [1,2,nil,4] }

              it "must return [false, \"element cannot be nil\"]" do
                expect(subject.validate(value)).to eq(
                  [false, "element cannot be nil"]
                )
              end
            end

            context "but it's empty" do
              let(:value) { [] }

              it "must return [false, \"cannot be empty\"]" do
                expect(subject.validate(value)).to eq(
                  [false, "cannot be empty"]
                )
              end
            end
          end
        end

        context "and it's a Hash" do
          let(:value) do
            {"foo" => "bar"}
          end

          it "must return [false, \"cannot convert a Hash into a String (...)\"]" do
            expect(subject.validate(value)).to eq(
              [false, "cannot convert a Hash into a String (#{value.inspect})"]
            )
          end

          context "but #type is a Types::KeyValue object" do
            let(:type) { Types::KeyValue.new }

            it "must return true" do
              expect(subject.validate(value)).to be(true)
            end

            context "but it's empty" do
              let(:value) { {} }

              it "must return [false, \"cannot be empty\"]" do
                expect(subject.validate(value)).to eq(
                  [false, "cannot be empty"]
                )
              end
            end
          end
        end
      end
    end
  end

  describe "#argv" do
    context "when the argument can be specified multiple times" do
      let(:repeats) { true }

      context "and it's given an Array" do
        context "and all elements of the Array are Strings" do
          let(:values) { %w[foo bar baz] }

          it "must return an argv of the values" do
            expect(subject.argv(values)).to eq(values)
          end

          context "but one of the Array's elements is nil" do
            let(:values) { ["foo", nil, "bar"] }

            it do
              expect {
                subject.argv(values)
              }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{values.inspect}): cannot be nil")
            end
          end

          context "but one of the Array's elements is invalid" do
            let(:value) { ["foo", " ", "baz"] }

            it do
              expect {
                subject.argv(value)
              }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{value.inspect}): does not allow a blank value (#{value[1].inspect})")
            end
          end

          context "but the Array contains Hashes" do
            let(:values) do
              [{"foo" => 1}, {"bar" => 2 }]
            end

            it do
              expect {
                subject.argv(values)
              }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{values.inspect}): cannot convert a Hash into a String (#{values[0].inspect})")
            end

            context "but #type is a Types::KeyValue object" do
              let(:type) { Types::KeyValue.new }

              it "must format each value using #type.format" do
                expect(subject.argv(values)).to eq(
                  [
                    subject.type.format(values[0]),
                    subject.type.format(values[1])
                  ]
                )
              end

              context "but one of the Hashes is empty" do
                let(:values) do
                  [{"foo" => 1}, {}]
                end

                it do
                  expect {
                    subject.argv(values)
                  }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{values.inspect}): cannot be empty")
                end
              end
            end
          end

          context "but it's empty" do
            let(:value) { [] }

            it do
              expect {
                subject.argv(value)
              }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{value.inspect}): requires at least one value")
            end
          end
        end
      end
    end

    context "and it's only given one value" do
      context "and it's a String" do
        let(:value) { "foo" }

        it "must return an argv only containing the value" do
          expect(subject.argv(value)).to eq([value])
        end

        context "but the String is invalid" do
          let(:value) { " " }

          it do
            expect {
              subject.argv(value)
            }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{value.inspect}): does not allow a blank value (#{value.inspect})")
          end
        end
      end

      context "and it's a Hash" do
        let(:value) do
          {"foo" => "bar"}
        end

        context "but #type is a Types::KeyValue object" do
          let(:type) { Types::KeyValue.new }

          it "must format the value using #type.format" do
            expect(subject.argv(value)).to eq(
              [subject.type.format(value)]
            )
          end

          context "but it's empty" do
            let(:value) { {} }

            it do
              expect {
                subject.argv(value)
              }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{value.inspect}): cannot be empty")
            end
          end
        end
      end
    end
  end

  context "when the argument can only be specified once" do
    let(:repeats) { false }

    context "and it's a String" do
      let(:value)   { "foo" }

      it "must return an argv only containing the value" do
        expect(subject.argv(value)).to eq([value])
      end
    end

    context "and it's an Array" do
      let(:value) { %w[foo bar baz] }

      it do
        expect {
          subject.argv(value)
        }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{value.inspect}): cannot convert a Array into a String (#{value.inspect})")
      end

      context "but #type is a Types::List object" do
        let(:type) { Types::List.new }

        it "must format the value using #type.format" do
          expect(subject.argv(value)).to eq(
            [subject.type.format(value)]
          )
        end

        context "but one of the Array elements is nil" do
          let(:value) { [1,2,nil,4] }

          it do
            expect {
              subject.argv(value)
            }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{value.inspect}): element cannot be nil")
          end
        end

        context "but one of the Array's elements is invalid" do
          let(:value)   { ["foo", " ", "baz"] }
          let(:message) { "does not allow a blank value" }

          it do
            expect {
              subject.argv(value)
            }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{value.inspect}): element does not allow a blank value (#{value[1].inspect})")
          end
        end

        context "but it's empty" do
          let(:value) { [] }

          it do
            expect {
              subject.argv(value)
            }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{value.inspect}): cannot be empty")
          end
        end
      end
    end

    context "and it's a Hash" do
      let(:value) do
        {"foo" => "bar"}
      end

      it do
        expect {
          subject.argv(value)
        }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{value.inspect}): cannot convert a Hash into a String (#{value.inspect})")
      end

      context "but #type is a Types::KeyValue object" do
        let(:type) { Types::KeyValue.new }

        it "must format the value using #type.format" do
          expect(subject.argv(value)).to eq(
            [subject.type.format(value)]
          )
        end

        context "but it's empty" do
          let(:value) { {} }

          it do
            expect {
              subject.argv(value)
            }.to raise_error(ValidationError,"argument #{name} was given an invalid value (#{value.inspect}): cannot be empty")
          end
        end
      end
    end

    context "when given an argv array and a value" do
      let(:value) { "foo" }

      let(:argv) { [] }

      before { subject.argv(argv,value) }

      it "must concat the args to the argv array" do
        expect(argv).to eq([value])
      end
    end
  end
end