# frozen_string_literal: true

RSpec.describe Fear::PartialFunction, "#or_else" do
  subject(:first_or_else_second) { first.or_else(second) }

  let(:first) { Fear.case(->(x) { x.even? }) { |x| "first: #{x}" } }
  let(:second) { Fear.case(->(x) { x % 3 == 0 }) { |x| "second: #{x}" } }

  describe "#defined_at?" do
    context "first defined, second not" do
      subject { first_or_else_second.defined_at?(4) }

      it { is_expected.to eq(true) }
    end

    context "first not defined, second defined" do
      subject { first_or_else_second.defined_at?(9) }

      it { is_expected.to eq(true) }
    end

    context "first not defined, second not defined" do
      subject { first_or_else_second.defined_at?(5) }

      it { is_expected.to eq(false) }
    end

    context "first and second defined" do
      subject { first_or_else_second.defined_at?(6) }

      it { is_expected.to eq(true) }
    end
  end

  describe "#call" do
    context "first defined, second not" do
      subject { first_or_else_second.call(4) }

      it { is_expected.to eq("first: 4") }
    end

    context "first not defined, second defined" do
      subject { first_or_else_second.call(9) }

      it { is_expected.to eq("second: 9") }
    end

    context "first not defined, second not defined" do
      subject { -> { first_or_else_second.call(5) } }

      it { is_expected.to raise_error(Fear::MatchError, "partial function not defined at: 5") }
    end

    context "first and second defined" do
      subject { first_or_else_second.call(6) }

      it { is_expected.to eq("first: 6") }
    end
  end

  describe "#call_or_else" do
    let(:fallback) { ->(x) { "fallback: #{x}" } }

    context "first defined, second not" do
      subject { first_or_else_second.call_or_else(4, &fallback) }

      it { is_expected.to eq("first: 4") }
    end

    context "first not defined, second defined" do
      subject { first_or_else_second.call_or_else(9, &fallback) }

      it { is_expected.to eq("second: 9") }
    end

    context "first not defined, second not defined" do
      subject { first_or_else_second.call_or_else(5, &fallback) }

      it { is_expected.to eq("fallback: 5") }
    end

    context "first and second defined" do
      subject { first_or_else_second.call_or_else(6, &fallback) }

      it { is_expected.to eq("first: 6") }
    end
  end

  describe "#or_else" do
    let(:first_or_else_second_or_else_third) { first_or_else_second.or_else(third) }
    let(:third) { Fear.case(->(x) { x % 7 == 0 }) { |x| "third: #{x}" } }

    describe "#defined_at?" do
      context "first defined, second not" do
        subject { first_or_else_second_or_else_third.defined_at?(4) }

        it { is_expected.to eq(true) }
      end

      context "first not defined, second defined" do
        subject { first_or_else_second_or_else_third.defined_at?(9) }

        it { is_expected.to eq(true) }
      end

      context "first not defined, second not defined, third defined" do
        subject { first_or_else_second_or_else_third.defined_at?(7) }

        it { is_expected.to eq(true) }
      end

      context "first not defined, second not defined, third not defined" do
        subject { first_or_else_second_or_else_third.defined_at?(1) }

        it { is_expected.to eq(false) }
      end

      context "first, second and third defined" do
        subject { first_or_else_second.defined_at?(42) }

        it { is_expected.to eq(true) }
      end
    end

    describe "#call" do
      context "first defined, second not" do
        subject { first_or_else_second_or_else_third.call(4) }

        it { is_expected.to eq("first: 4") }
      end

      context "first not defined, second defined" do
        subject { first_or_else_second_or_else_third.call(9) }

        it { is_expected.to eq("second: 9") }
      end

      context "first not defined, second not defined, third defined" do
        subject { first_or_else_second_or_else_third.call(7) }

        it { is_expected.to eq("third: 7") }
      end

      context "first not defined, second not defined, third not defined" do
        subject { -> { first_or_else_second_or_else_third.call(1) } }

        it { is_expected.to raise_error(Fear::MatchError, "partial function not defined at: 1") }
      end

      context "first, second and third defined" do
        subject { first_or_else_second.call(42) }

        it { is_expected.to eq("first: 42") }
      end
    end

    describe "#call_or_else" do
      let(:fallback) { ->(x) { "fallback: #{x}" } }

      context "first defined, second not" do
        subject { first_or_else_second_or_else_third.call_or_else(4, &fallback) }

        it { is_expected.to eq("first: 4") }
      end

      context "first not defined, second defined" do
        subject { first_or_else_second_or_else_third.call_or_else(9, &fallback) }

        it { is_expected.to eq("second: 9") }
      end

      context "first not defined, second not defined, third defined" do
        subject { first_or_else_second_or_else_third.call_or_else(7, &fallback) }

        it { is_expected.to eq("third: 7") }
      end

      context "first not defined, second not defined, third not defined" do
        subject { first_or_else_second_or_else_third.call_or_else(1, &fallback) }

        it { is_expected.to eq("fallback: 1") }
      end

      context "first, second and third defined" do
        subject { first_or_else_second_or_else_third.call_or_else(42, &fallback) }

        it { is_expected.to eq("first: 42") }
      end
    end
  end

  describe "#and_then" do
    let(:first_or_else_second_and_then_function) { first_or_else_second.and_then(&function) }
    let(:function) { ->(x) { "f: #{x}" } }

    describe "#defined_at?" do
      context "first defined, second not" do
        subject { first_or_else_second_and_then_function.defined_at?(2) }

        it { is_expected.to eq(true) }
      end

      context "first not defined, second defined" do
        subject { first_or_else_second_and_then_function.defined_at?(3) }

        it { is_expected.to eq(true) }
      end

      context "first not defined, second not defined" do
        subject { first_or_else_second_and_then_function.defined_at?(5) }

        it { is_expected.to eq(false) }
      end

      context "first defined, second defined" do
        subject { first_or_else_second_and_then_function.defined_at?(6) }

        it { is_expected.to eq(true) }
      end
    end

    describe "#call" do
      context "first defined, second not" do
        subject { first_or_else_second_and_then_function.call(2) }

        it { is_expected.to eq("f: first: 2") }
      end

      context "first not defined, second defined" do
        subject { first_or_else_second_and_then_function.call(3) }

        it { is_expected.to eq("f: second: 3") }
      end

      context "first not defined, second not defined" do
        subject { -> { first_or_else_second_and_then_function.call(5) } }

        it { is_expected.to raise_error(Fear::MatchError, "partial function not defined at: 5") }
      end

      context "first defined, second defined" do
        subject { first_or_else_second_and_then_function.call(6) }

        it { is_expected.to eq("f: first: 6") }
      end
    end

    describe "#call_or_else" do
      let(:fallback) { ->(x) { "fallback: #{x}" } }

      context "first defined, second not" do
        subject { first_or_else_second_and_then_function.call_or_else(2, &fallback) }

        it { is_expected.to eq("f: first: 2") }
      end

      context "first not defined, second defined" do
        subject { first_or_else_second_and_then_function.call_or_else(3, &fallback) }

        it { is_expected.to eq("f: second: 3") }
      end

      context "first not defined, second not defined" do
        subject { first_or_else_second_and_then_function.call_or_else(5, &fallback) }

        it { is_expected.to eq("fallback: 5") }
      end

      context "first defined, second defined" do
        subject { first_or_else_second_and_then_function.call_or_else(6, &fallback) }

        it { is_expected.to eq("f: first: 6") }
      end
    end
  end
end
