require 'cucumber/cucumber_expressions/cucumber_expression'
require 'cucumber/cucumber_expressions/parameter_type_registry'

module Cucumber
  module CucumberExpressions
    describe CucumberExpression do
      it "documents match arguments" do
        parameter_registry = ParameterTypeRegistry.new

        ### [capture-match-arguments]
        expr = "I have {int} cuke(s)"
        expression = CucumberExpression.new(expr, parameter_registry)
        args = expression.match("I have 7 cukes")
        expect(args[0].value(nil)).to eq(7)
        ### [capture-match-arguments]
      end

      it "matches word" do
        expect(match("three {word} mice", "three blind mice")).to eq(['blind'])
      end

      it('matches double quoted string') do
        expect(match('three {string} mice', 'three "blind" mice')).to eq(['blind'])
      end

      it('matches multiple double quoted strings') do
        expect(match('three {string} and {string} mice', 'three "blind" and "crippled" mice')).to eq(['blind', 'crippled'])
      end

      it('matches single quoted string') do
        expect(match('three {string} mice', "three 'blind' mice")).to eq(['blind'])
      end

      it('matches multiple single quoted strings') do
        expect(match('three {string} and {string} mice', "three 'blind' and 'crippled' mice")).to eq(['blind', 'crippled'])
      end

      it('does not match misquoted string') do
        expect(match('three {string} mice', 'three "blind\' mice')).to eq(nil)
      end

      it('matches single quoted string with double quotes') do
        expect(match('three {string} mice', 'three \'"blind"\' mice')).to eq(['"blind"'])
      end

      it('matches double quoted string with single quotes') do
        expect(match('three {string} mice', 'three "\'blind\'" mice')).to eq(["'blind'"])
      end

      it('matches double quoted string with escaped double quote') do
        expect(match('three {string} mice', 'three "bl\\"nd" mice')).to eq(['bl"nd'])
      end

      it('matches single quoted string with escaped single quote') do
        expect(match('three {string} mice', "three 'bl\\'nd' mice")).to eq(["bl'nd"])
      end

      it 'matches escaped parentheses' do
        expect(match('three \\(exceptionally) {string} mice', 'three (exceptionally) "blind" mice')).to eq(['blind'])
      end

      it "matches escaped slash" do
        expect(match("12\\/2020", "12/2020")).to eq([])
      end

      it "matches int" do
        expect(match("{int}", "22")).to eq([22])
      end

      it "doesn't match float as int" do
        expect(match("{int}", "1.22")).to be_nil
      end

      it "matches int as float" do
        expect(match("{float}", "0")).to eq([0.0])
      end

      it "matches float" do
        expect(match("{float}", "")).to eq(nil)
        expect(match("{float}", ".")).to eq(nil)
        expect(match("{float}", ",")).to eq(nil)
        expect(match("{float}", "-")).to eq(nil)
        expect(match("{float}", "E")).to eq(nil)
        expect(match("{float}", "1,")).to eq(nil)
        expect(match("{float}", ",1")).to eq(nil)
        expect(match("{float}", "1.")).to eq(nil)

        expect(match("{float}", "1")).to eq([1])
        expect(match("{float}", "-1")).to eq([-1])
        expect(match("{float}", "1.1")).to eq([1.1])
        expect(match("{float}", "1,000")).to eq(nil)
        expect(match("{float}", "1,000,0")).to eq(nil)
        expect(match("{float}", "1,000.1")).to eq(nil)
        expect(match("{float}", "1,000,10")).to eq(nil)
        expect(match("{float}", "1,0.1")).to eq(nil)
        expect(match("{float}", "1,000,000.1")).to eq(nil)
        expect(match("{float}", "-1.1")).to eq([-1.1])

        expect(match("{float}", ".1")).to eq([0.1])
        expect(match("{float}", "-.1")).to eq([-0.1])
        expect(match("{float}", "-.1000001")).to eq([-0.1000001])
        expect(match("{float}", "1E1")).to eq([10.0])
        expect(match("{float}", ".1E1")).to eq([1])
        expect(match("{float}", "E1")).to eq(nil)
        expect(match("{float}", "-.1E-1")).to eq([-0.01])
        expect(match("{float}", "-.1E-2")).to eq([-0.001])
        expect(match("{float}", "-.1E+1")).to eq([-1])
        expect(match("{float}", "-.1E+2")).to eq([-10])
        expect(match("{float}", "-.1E1")).to eq([-1])
        expect(match("{float}", "-.1E2")).to eq([-10])
      end

      it "matches anonymous" do
        expect(match("{}", "0.22")).to eq(["0.22"])
      end

      '[]()$.|?*+'.split('').each do |char|
        it "does not allow parameter type with #{char}" do
          expect {match("{#{char}string}", "something")}.to raise_error("Illegal character '#{char}' in parameter name {#{char}string}")
        end
      end

      it "throws unknown parameter type" do
        expect {match("{unknown}", "something")}.to raise_error('Undefined parameter type {unknown}')
      end

      it "does not allow optional parameter types" do
        expect {match("({int})", "3")}.to raise_error('Parameter types cannot be optional: ({int})')
      end

      it "does allow escaped optional parameter types" do
        expect(match("\\({int})", "(3)")).to eq([3])
      end

      it "does not allow text/parameter type alternation" do
        expect {match("x/{int}", "3")}.to raise_error('Parameter types cannot be alternative: x/{int}')
      end

      it "does not allow parameter type/text alternation" do
        expect {match("{int}/x", "3")}.to raise_error('Parameter types cannot be alternative: {int}/x')
      end

      it "exposes source" do
        expr = "I have {int} cuke(s)"
        expect(CucumberExpression.new(expr, ParameterTypeRegistry.new).source).to eq(expr)
      end

      it "delegates transform to custom object" do
        parameter_type_registry = ParameterTypeRegistry.new
        parameter_type_registry.define_parameter_type(
          ParameterType.new(
                  'widget',
                  /\w+/,
                  Object,
                  -> (s) {
                    self.create_widget(s)
                  },
                  false,
                  true
              )
        )
        expression = CucumberExpression.new(
          'I have a {widget}',
          parameter_type_registry
        )

        class World
          def create_widget(s)
            "widget:#{s}"
          end
        end

        args = expression.match("I have a bolt")
        expect(args[0].value(World.new)).to eq('widget:bolt')
      end

      describe "escapes special characters" do
        %w(\\ [ ] ^ $ . | ? * +).each do |character|
          it "escapes #{character}" do
            expr = "I have {int} cuke(s) and #{character}"
            expression = CucumberExpression.new(expr, ParameterTypeRegistry.new)
            arg1 = expression.match("I have 800 cukes and #{character}")[0]
            expect(arg1.value(nil)).to eq(800)
          end
        end
      end

      def match(expression, text)
        cucumber_expression = CucumberExpression.new(expression, ParameterTypeRegistry.new)
        args = cucumber_expression.match(text)
        return nil if args.nil?
        args.map {|arg| arg.value(nil)}
      end
    end
  end
end
