module WithClues
  module Private
    class CustomClueMethodAnalysis

      def self.from_method(unbound_method)

        params = unbound_method.parameters.map { |param_array| Param.new(param_array) }

        if params.size == 2
          two_arg_method = TwoArgMethod.new(params)
          if two_arg_method.valid?
            return StandardImplementation.new
          end

          return BadParams.new(two_arg_method.errors)

        elsif params.size == 3
          three_arg_method = ThreeArgMethod.new(params)
          if three_arg_method.valid?
            return RequiresPageObject.new
          end
          return BadParams.new(three_arg_method.errors)
        end

        BadParams.new([])
      end

      def standard_implementation?
        false
      end

      def requires_page_object?
        false
      end

      def raise_exception!
        raise StandardError.new("Unimplemented condition found inside #from_method")
      end

      class Param

        def initialize(method_param_array)
          @type = method_param_array[0]
          @name = method_param_array[1]

        end

        def required?
          @type == :req
        end
        def keyword_required?
          @type == :keyreq
        end

        def named?(*allowed_names)
          allowed_names.include?(@name)
        end
        def name
          if self.keyword_required?
            "#{@name}:"
          else
            @name
          end
        end
      end

      class TwoArgMethod
        attr_reader :errors
        def initialize(params)
          @errors = []
          if !params[0].required?
            @errors << "Param 1, #{params[0].name}, is not required"
          end
          require_keyword(2,params[1])
        end

        def valid?
          @errors.empty?
        end
      private

        def require_keyword(param_number, param)
          if !param.keyword_required?
            @errors << "Param #{param_number}, #{param.name}, is not a required keyword param"
          end
          if !param.named?(*allowed_names)
            @errors << "Param #{param_number}, #{param.name}, should be named context:"
          end
        end

        def allowed_names
          [ :context ]
        end
      end

      class ThreeArgMethod < TwoArgMethod
        def initialize(params)
          super(params)
          require_keyword(3,params[2])
        end
      private
        def allowed_names
          [ :context, :page ]
        end
      end

    end

    class GoodParams < CustomClueMethodAnalysis
      def raise_exception!
        raise StandardError.new("You should not have called .exception on a #{self.class.name}")
      end
    end

    class RequiresPageObject < CustomClueMethodAnalysis
      def requires_page_object?
        true
      end
    end

    class StandardImplementation < CustomClueMethodAnalysis
      def standard_implementation?
        true
      end
    end

    class BadParams < CustomClueMethodAnalysis
      def initialize(errors)
        @message = errors.empty? ? DEFAULT_ERROR : errors.join(", ")
      end

      DEFAULT_ERROR = "dump must take one required param, one keyword param named context: and an optional keyword param named page:"

      def raise_exception!
        raise NameError.new(@message)
      end
    end
  end
end