$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$LOAD_PATH.unshift(File.dirname(__FILE__))
require 'rspec'
require 'matchete'

describe Matchete do
  it 'can use a pattern based on `either` helpers' do
    class A
      include Matchete

      on either(FalseClass, Proc), FalseClass,
      def work(g, h)
        g
      end

      on FalseClass, NilClass,
      def work(g, h)
        :love
      end
    end

    expect(A.new.work(false, false)).to eq false
    expect(A.new.work(false, nil)).to eq :love
  end

  it 'can be used to overload a method in a class' do
    class A
      include Matchete

      on Integer,
      def play(value)
        :integer
      end

      on Float, Any,
      def play(value, object)
        :float
      end
    end

    a = A.new
    expect(a.play(2)).to eq :integer
    expect(a.play(2.2, 4)).to eq :float
  end

  it 'can use a pattern based on classes and modules' do
    class A
      include Matchete

      on Integer,
      def play(value)
        :integer
      end

      on Float,
      def play(value)
        :float
      end

      on Enumerable,
      def play(value)
        :enumerable
      end
    end

    a = A.new
    expect(a.play(2)).to eq :integer
    expect(a.play(2.2)).to eq :float
    expect(a.play([2])).to eq :enumerable
  end

  it 'can use a pattern based on nested arrays with classes/modules' do
    class A
      include Matchete

      on [Integer, Float],
      def play(values)
        [:integer, :float]
      end

      on [[Integer], Any],
      def play(values)
        :s
      end
    end

    a = A.new
    expect(a.play([2, 2.2])).to eq [:integer, :float]
    expect(a.play([[2], Matchete])).to eq :s
  end

  it 'can use a pattern based on exact values' do
    class A
      include Matchete

      on 2, Integer,
      def play(value, obj)
        2
      end

      on 4, Integer,
      def play(value, obj)
        4
      end
    end

    a = A.new
    expect(a.play(2, 4)).to eq 2
    expect(a.play(4, 4)).to eq 4
    expect { a.play(8, 2) }.to raise_error(Matchete::NotResolvedError)
  end

  it 'can use a pattern based on regexes' do
    class A
      include Matchete

      on /z/,
      def play(value)
        'z'
      end

      on /y/,
      def play(value)
        'y'
      end
    end

    a = A.new
    expect(a.play('zewr')).to eq 'z'
    expect(a.play('yy')).to eq 'y'
  end

  it 'can use a default method when everything else fails' do
    class A
      include Matchete

      on Integer,
      def play(value)
        :integer
      end
    end

    expect { A.new.play(2.2) }.to raise_error(Matchete::NotResolvedError)

    class A
      default def play(value)
        :else
      end
    end

    expect(A.new.play(2.2)).to eq :else
  end

  it 'can use a pattern based on `exact` helpers' do
    class A
      include Matchete

      on Integer,
      def lala(a)
        a - 2
      end

      on exact(Integer),
      def lala(a)
        a
      end
    end

    expect(A.new.lala(4)).to eq 2
    expect(A.new.lala(Integer)).to eq Integer
  end

  it 'can use a pattern based on existing predicate methods given as symbols' do
    class A
      include Matchete

      on :even?,
      def play(value)
        value
      end

      def even?(value)
        value.remainder(2).zero? #so gay and gay
      end
    end

    expect(A.new.play(2)).to eq 2
    expect { A.new.play(5) }.to raise_error(Matchete::NotResolvedError)
  end

  it 'can use a pattern based on a lambda predicate' do
    class A
      include Matchete

      on -> x { x % 2 == 0 },
      def play(value)
        value
      end
    end

    expect(A.new.play(2)).to eq 2
    expect { A.new.play(7) }.to raise_error(Matchete::NotResolvedError)
  end

  it 'can use a pattern based on responding to methods' do
    class A
      include Matchete

      on supporting(:map),
      def play(value)
        value
      end
    end

    expect(A.new.play([])).to eq []
    expect { A.new.play(4) }.to raise_error(Matchete::NotResolvedError)
  end

  it 'can match on different keyword arguments' do
    class A
      include Matchete

      on e: Integer, f: String, method:
      def play(e:, f:)
        :y
      end
    end

    expect(A.new.play(e: 0, f: "y")).to eq :y
    expect { A.new.play(e: "f", f: Class)}.to raise_error(Matchete::NotResolvedError)
  end

  it 'can match on multiple different kinds of patterns' do
    class A
      include Matchete
    end

    expect(A.new.match_guards([Integer, Float], {}, [8, 8.8], {})).to be_truthy
  end

  describe '#match_guard' do
    before :all do
      class A
        include Matchete

        def even?(value)
          value.remainder(2).zero?
        end
      end
      @a = A.new
    end

    it 'matches modules and classes' do
      expect(@a.match_guard(Integer, 2)).to be_truthy
      expect(@a.match_guard(Class, 4)).to be_falsey
    end

    it 'matches methods given as symbols' do
      expect(@a.match_guard(:even?, 2)).to be_truthy
      expect { @a.match_guard(:odd?, 4) }.to raise_error
    end

    it 'matches predicates given as lambdas' do
      expect(@a.match_guard(-> x { x == {} }, {})).to be_truthy
    end

    it 'matches on regex' do
      expect(@a.match_guard(/a/, 'aw')).to be_truthy
      expect(@a.match_guard(/z/, 'lol')).to be_falsey
    end

    it 'matches on nested arrays' do
      expect(@a.match_guard([Integer, [:even?]], [2, [4]])).to be_truthy
      expect(@a.match_guard([Float, [:even?]], [2.2, [7]])).to be_falsey
    end

    it 'matches on exact values' do
      expect(@a.match_guard(2, 2)).to be_truthy
      expect(@a.match_guard('d', 'f')).to be_falsey
    end
  end
end

