# frozen_string_literal: true

RSpec.describe MemoWise do
  describe "#reset_memo_wise" do
    shared_examples "#reset_memo_wise shared examples" do
      context "when method_name is given" do
        it "resets memoization for methods with no arguments" do
          target.no_args
          target.reset_memo_wise(:no_args)
          expect(Array.new(4) { target.no_args }).to all eq("no_args")
          expect(target.no_args_counter).to eq(2)
        end

        it "resets memoization for methods with one positional argument" do
          target.with_one_positional_arg(1)
          target.with_one_positional_arg(2)
          target.reset_memo_wise(:with_one_positional_arg)

          expect(Array.new(4) { target.with_one_positional_arg(1) }).
            to all eq("with_one_positional_arg: a=1")

          expect(Array.new(4) { target.with_one_positional_arg(2) }).
            to all eq("with_one_positional_arg: a=2")

          # This should be executed twice for each set of arguments passed
          expect(target.with_one_positional_arg_counter).to eq(4)
        end

        it "resets memoization for methods for one specific positional "\
           "argument" do
          target.with_one_positional_arg(1)
          target.with_one_positional_arg(2)
          target.reset_memo_wise(:with_one_positional_arg, 1)

          expect(Array.new(4) { target.with_one_positional_arg(1) }).
            to all eq("with_one_positional_arg: a=1")

          expect(Array.new(4) { target.with_one_positional_arg(2) }).
            to all eq("with_one_positional_arg: a=2")

          # This should be executed twice for each set of arguments passed,
          # and a third time for the argument that was reset.
          expect(target.with_one_positional_arg_counter).to eq(3)
        end

        it "resets memoization for methods with positional arguments" do
          target.with_positional_args(1, 2)
          target.with_positional_args(2, 3)
          target.reset_memo_wise(:with_positional_args)

          expect(Array.new(4) { target.with_positional_args(1, 2) }).
            to all eq("with_positional_args: a=1, b=2")

          expect(Array.new(4) { target.with_positional_args(1, 3) }).
            to all eq("with_positional_args: a=1, b=3")

          # This should be executed twice for each set of arguments passed
          expect(target.with_positional_args_counter).to eq(4)
        end

        it "resets memoization for methods for specific positional arguments" do
          target.with_positional_args(1, 2)
          target.with_positional_args(2, 3)
          target.reset_memo_wise(:with_positional_args, 1, 2)

          expect(Array.new(4) { target.with_positional_args(1, 2) }).
            to all eq("with_positional_args: a=1, b=2")

          expect(Array.new(4) { target.with_positional_args(2, 3) }).
            to all eq("with_positional_args: a=2, b=3")

          # This should be executed twice for each set of arguments passed,
          # and a third time for the set of arguments that was reset.
          expect(target.with_positional_args_counter).to eq(3)
        end

        it "resets memoization for methods with one keyword argument" do
          target.with_one_keyword_arg(a: 1)
          target.with_one_keyword_arg(a: 2)
          target.reset_memo_wise(:with_one_keyword_arg)

          expect(Array.new(4) { target.with_one_keyword_arg(a: 1) }).
            to all eq("with_one_keyword_arg: a=1")

          expect(Array.new(4) { target.with_one_keyword_arg(a: 2) }).
            to all eq("with_one_keyword_arg: a=2")

          # This should be executed twice for each set of arguments passed
          expect(target.with_one_keyword_arg_counter).to eq(4)
        end

        it "resets memoization for methods for one specific keyword argument" do
          target.with_one_keyword_arg(a: 1)
          target.with_one_keyword_arg(a: 2)
          target.reset_memo_wise(:with_one_keyword_arg, a: 1)

          expect(Array.new(4) { target.with_one_keyword_arg(a: 1) }).
            to all eq("with_one_keyword_arg: a=1")

          expect(Array.new(4) { target.with_one_keyword_arg(a: 2) }).
            to all eq("with_one_keyword_arg: a=2")

          # This should be executed twice for each set of arguments passed,
          # and a third time for the argument that was reset.
          expect(target.with_one_keyword_arg_counter).to eq(3)
        end

        it "resets memoization for methods with keyword arguments" do
          target.with_keyword_args(a: 1, b: 2)
          target.with_keyword_args(a: 2, b: 3)
          target.reset_memo_wise(:with_keyword_args)

          expect(Array.new(4) { target.with_keyword_args(a: 1, b: 2) }).
            to all eq("with_keyword_args: a=1, b=2")

          expect(Array.new(4) { target.with_keyword_args(a: 2, b: 3) }).
            to all eq("with_keyword_args: a=2, b=3")

          # This should be executed twice for each set of arguments passed
          expect(target.with_keyword_args_counter).to eq(4)
        end

        it "resets memoization for methods for specific keyword arguments" do
          target.with_keyword_args(a: 1, b: 2)
          target.with_keyword_args(a: 2, b: 3)
          target.reset_memo_wise(:with_keyword_args, a: 1, b: 2)

          expect(Array.new(4) { target.with_keyword_args(a: 1, b: 2) }).
            to all eq("with_keyword_args: a=1, b=2")

          expect(Array.new(4) { target.with_keyword_args(a: 2, b: 3) }).
            to all eq("with_keyword_args: a=2, b=3")

          # This should be executed twice for each set of arguments passed,
          # and a third time for the set of arguments that was reset.
          expect(target.with_keyword_args_counter).to eq(3)
        end

        it "resets memoization for methods with positional and keyword args" do
          target.with_positional_and_keyword_args(1, b: 2)
          target.with_positional_and_keyword_args(2, b: 3)
          target.reset_memo_wise(:with_positional_and_keyword_args, 1, b: 2)

          expect(Array.new(4) do
            target.with_positional_and_keyword_args(1, b: 2)
          end).to all eq("with_positional_and_keyword_args: a=1, b=2")

          expect(Array.new(4) do
            target.with_positional_and_keyword_args(2, b: 3)
          end).to all eq("with_positional_and_keyword_args: a=2, b=3")

          # This should be executed once for each set of arguments passed,
          # and a third time for the set of arguments that was reset.
          expect(target.with_positional_and_keyword_args_counter).to eq(3)
        end

        it "resets memoization for methods with special characters in the "\
           "name" do
          target.special_chars?
          target.reset_memo_wise(:special_chars?)
          expect(Array.new(4) { target.special_chars? }).
            to all eq("special_chars?")
          expect(target.special_chars_counter).to eq(2)
        end

        it "resets memoization for methods set to false values" do
          target.false_method
          target.reset_memo_wise(:false_method)
          expect(Array.new(4) { target.false_method }).to all eq(false)
          expect(target.false_method_counter).to eq(2)
        end

        it "resets memoization for methods set to nil values" do
          target.nil_method
          target.reset_memo_wise(:nil_method)
          expect(Array.new(4) { target.nil_method }).to all eq(nil)
          expect(target.nil_method_counter).to eq(2)
        end

        it "resets memoization for private methods" do
          target.send(:private_memowise_method)
          target.reset_memo_wise(:private_memowise_method)
          expect(Array.new(4) { target.send(:private_memowise_method) }).
            to all eq("private_memowise_method")
          expect(target.private_memowise_method_counter).to eq(2)
        end

        context "when args are given" do
          context "when no value is memoized for the method" do
            it "doesn't raise an error" do
              expect { target.reset_memo_wise(:with_positional_args, 1, 2) }.
                not_to raise_error
            end
          end
        end

        context "when the name of the method is not a symbol" do
          it do
            expect { target.reset_memo_wise("no_args") }.
              to raise_error(ArgumentError)
          end
        end

        context "when the method to reset memoization for is not memoized" do
          it do
            expect { target.reset_memo_wise(:unmemoized_method) { nil } }.
              to raise_error(ArgumentError)
          end
        end

        context "when the method to reset memoization for is not defined" do
          it do
            expect { target.reset_memo_wise(:not_defined) }.
              to raise_error(ArgumentError)
          end
        end
      end

      context "when method_name is *not* given (e.g. 'reset all' mode)" do
        before :each do
          # Memoize some method calls
          target.no_args
          target.with_positional_args(1, 2)
          target.with_positional_args(2, 3)
          target.with_keyword_args(a: 1, b: 2)
          target.with_keyword_args(a: 2, b: 3)
          target.special_chars?
          target.false_method
          target.nil_method

          # This is 'reset all' mode, as no method name is given
          target.reset_memo_wise
        end

        it "resets memoization for methods with no arguments" do
          expect(Array.new(4) { target.no_args }).to all eq("no_args")
          expect(target.no_args_counter).to eq(2)
        end

        it "resets memoization for methods with positional arguments" do
          expect(Array.new(4) { target.with_positional_args(1, 2) }).
            to all eq("with_positional_args: a=1, b=2")

          expect(Array.new(4) { target.with_positional_args(1, 3) }).
            to all eq("with_positional_args: a=1, b=3")

          # This should be executed twice for each set of arguments passed
          expect(target.with_positional_args_counter).to eq(4)
        end

        it "resets memoization for methods with keyword arguments" do
          expect(Array.new(4) { target.with_keyword_args(a: 1, b: 2) }).
            to all eq("with_keyword_args: a=1, b=2")

          expect(Array.new(4) { target.with_keyword_args(a: 2, b: 3) }).
            to all eq("with_keyword_args: a=2, b=3")

          # This should be executed twice for each set of arguments passed
          expect(target.with_keyword_args_counter).to eq(4)
        end

        it "resets memoization for methods with special characters in the "\
           "name" do
          expect(Array.new(4) { target.special_chars? }).
            to all eq("special_chars?")
          expect(target.special_chars_counter).to eq(2)
        end

        it "resets memoization for methods set to false values" do
          expect(Array.new(4) { target.false_method }).to all eq(false)
          expect(target.false_method_counter).to eq(2)
        end

        it "resets memoization for methods set to nil values" do
          expect(Array.new(4) { target.nil_method }).to all eq(nil)
          expect(target.nil_method_counter).to eq(2)
        end
      end

      context "when method_name=nil and positional args given" do
        it do
          expect { target.reset_memo_wise(nil, 42) { nil } }.
            to raise_error(ArgumentError)
        end
      end

      context "when method_name=nil and keyword args given" do
        it do
          expect { target.reset_memo_wise(foo: 42) { nil } }.
            to raise_error(ArgumentError)
        end
      end
    end

    context "with instance methods" do
      include_context "with context for instance methods"

      # Use the instance as the target of "#preset_memo_wise shared examples"
      let(:target) { instance }

      it_behaves_like "#reset_memo_wise shared examples"

      context "when method_name is given" do
        it "does not reset memoization methods across instances" do
          instance2 = class_with_memo.new

          instance.no_args
          instance2.no_args

          instance.reset_memo_wise(:no_args)

          instance.no_args
          instance2.no_args

          expect(instance.no_args_counter).to eq(2)
          expect(instance2.no_args_counter).to eq(1)
        end
      end

      context "when method_name is *not* given (e.g. 'reset all' mode)" do
        it "does not reset memoization methods across instances" do
          instance2 = class_with_memo.new

          instance.no_args
          instance2.no_args

          instance.reset_memo_wise

          instance.no_args
          instance2.no_args

          expect(instance.no_args_counter).to eq(2)
          expect(instance2.no_args_counter).to eq(1)
        end
      end
    end

    context "with class methods" do
      context "when defined with 'def self.'" do
        include_context "with context for class methods via 'def self.'"

        # Use the class as the target of "#reset_memo_wise shared examples"
        let(:target) { class_with_memo }

        it_behaves_like "#reset_memo_wise shared examples"
      end

      context "when defined with scope 'class << self'" do
        include_context "with context for class methods via scope "\
                        "'class << self'"

        # Use the class as the target of "#reset_memo_wise shared examples"
        let(:target) { class_with_memo }

        it_behaves_like "#reset_memo_wise shared examples"
      end
    end
  end
end
