# Refinements on core RDF class behavior for RDF::N3.
module RDF::N3::Refinements
  # @!parse
  #   # Refinements on RDF::Term
  #   module RDF::Term
  #   ##
  #   # As a term is constant, this returns itself.
  #   #
  #   # @param  [Hash{Symbol => RDF::Term}] bindings
  #   #   a query solution containing zero or more variable bindings
  #   # @param [Hash{Symbol => Object}] options ({})
  #   #   options passed from query
  #   # @return [RDF::Term]
  #   # @see SPARQL::Algebra::Expression.evaluate
  #   def evaluate(bindings, formulae: nil, **options); end
  #   end
  refine ::RDF::Term do
    def evaluate(bindings, formulae:, **options)
      self
    end
  end

  # @!parse
  #   # Refinements on RDF::Node
  #   module RDF::Term
  #     ##
  #     # Blank node may refer to a formula.
  #     #
  #     # @param  [Hash{Symbol => RDF::Term}] bindings
  #     #   a query solution containing zero or more variable bindings
  #     # @param [Hash{Symbol => Object}] options ({})
  #     #   options passed from query
  #     # @return [RDF::Node, RDF::N3::Algebra::Formula]
  #     # @see SPARQL::Algebra::Expression.evaluate
  #     def evaluate(bindings, formulae:, **options); end
  #   end
  refine ::RDF::Node do
    ##
    # @return [RDF::Node, RDF::N3::Algebra::Formula]
    def evaluate(bindings, formulae:, **options)
      node? ? formulae.fetch(self, self) : self
    end
  end

  # @!parse
  #   # Refinements on RDF::Statement
  #   class ::RDF::Statement
  #     # Refines `valid?` to allow literal subjects and BNode predicates.
  #     # @return [Boolean]
  #     def valid?; end
  #
  #     # Refines `invalid?` to allow literal subjects and BNode predicates.
  #     # @return [Boolean]
  #     def invalid?; end
  #
  #     # Refines `validate!` to allow literal subjects and BNode predicates.
  #     # @return [RDF::Value] `self`
  #     # @raise  [ArgumentError] if the value is invalid
  #     def validate!; end
  #
  #     ##
  #     # As a statement is constant, this returns itself.
  #     #
  #     # @param  [Hash{Symbol => RDF::Term}] bindings
  #     #   a query solution containing zero or more variable bindings
  #     # @param [Hash{Symbol => Object}] options ({})
  #     #   options passed from query
  #     # @return [RDF::Statement]
  #     # @see SPARQL::Algebra::Expression.evaluate
  #     def evaluate(bindings, formulae:, **options); end
  #   end
  refine ::RDF::Statement do
    ##
    # Override `valid?` terms as subjects and resources as predicates.
    #
    # @return [Boolean]
    def valid?
      has_subject?    && subject.term? && subject.valid? &&
      has_predicate?  && predicate.term? && predicate.valid? &&
      has_object?     && object.term? && object.valid? &&
      (has_graph?      ? (graph_name.resource? && graph_name.valid?) : true)
    end

    ##
    # @return [Boolean]
    def invalid?
      !valid?
    end

    ##
    # Default validate! implementation, overridden in concrete classes
    # @return [RDF::Value] `self`
    # @raise  [ArgumentError] if the value is invalid
    def validate!
      raise ArgumentError, "#{self.inspect} is not valid" if invalid?
      self
    end
    alias_method :validate, :validate!

    ##
    # @return [RDF::Statement]
    def evaluate(bindings, formulae:, **options)
      self
    end
  end

  # @!parse
  #   # Refinements on RDF::Query::Pattern
  #   class ::RDF::Query::Pattern
  #     # Refines `#valid?` to allow literal subjects and BNode predicates.
  #     # @return [Boolean]
  #     def valid?; end
  #
  #     ##
  #     # Evaluates the pattern using the given variable `bindings` by cloning the pattern replacing variables with their bindings recursively. If the resulting pattern is constant, it is cast as a statement.
  #     #
  #     # @param  [Hash{Symbol => RDF::Term}] bindings
  #     #   a query solution containing zero or more variable bindings
  #     # @param [Hash{Symbol => Object}] options ({})
  #     #   options passed from query
  #     # @return [RDF::Statement, RDF::N3::Algebra::Formula]
  #     # @see SPARQL::Algebra::Expression.evaluate
  #     def evaluate(bindings, formulae:, **options); end
  #   end
  refine ::RDF::Query::Pattern do
    ##
    # Is this pattern composed only of valid components?
    #
    # @return [Boolean] `true` or `false`
    def valid?
      (has_subject?   ? (subject.term? || subject.variable?) && subject.valid? : true) && 
      (has_predicate? ? (predicate.term? || predicate.variable?) && predicate.valid? : true) &&
      (has_object?    ? (object.term? || object.variable?) && object.valid? : true) &&
      (has_graph?     ? (graph_name.resource? || graph_name.variable?) && graph_name.valid? : true)
    rescue NoMethodError
      false
    end

    # @return [RDF::Statement, RDF::N3::Algebra::Formula]
    def evaluate(bindings, formulae:, **options)
      elements = self.to_quad.map do |term|
        term.evaluate(bindings, formulae: formulae, **options)
      end.compact.map do |term|
        term.node? ? formulae.fetch(term, term) : term
      end

      self.class.from(elements)
    end
  end

  # @!parse
  #   # Refinements on RDF::Query::Variable
  #   class RDF::Query::Variable
  #     ##
  #     # If variable is bound, replace with the bound value, otherwise, returns itself
  #     #
  #     # @param  [Hash{Symbol => RDF::Term}] bindings
  #     #   a query solution containing zero or more variable bindings
  #     # @param [Hash{Symbol => Object}] options ({})
  #     #   options passed from query
  #     # @return [RDF::Term]
  #     # @see SPARQL::Algebra::Expression.evaluate
  #     def evaluate(bindings, formulae:, **options); end
  #   end
  refine ::RDF::Query::Variable do
    ##
    # @return [RDF::Term]
    def evaluate(bindings, formulae:, **options)
      value = bindings.has_key?(name) ? bindings[name] : self
      value.node? ? formulae.fetch(value, value) : value
    end
  end

  refine ::RDF::Graph do
    # Allow a graph to be treated as a term in a statement.

    ##
    # @overload term?
    #   Returns `true` if `self` is a {RDF::Term}.
    #
    #   @return [Boolean]
    # @overload term?(name)
    #   Returns `true` if `self` contains the given RDF subject term.
    #
    #   @param  [RDF::Resource] value
    #   @return [Boolean]
    def term?(*args)
      case args.length
      when 0 then true
      when 1 then false
      else raise ArgumentError("wrong number of arguments (given #{args.length}, expected 0 or 1)")
      end
    end

    ##
    # Returns itself.
    #
    # @return [RDF::Value]
    def to_term
      statements.map(&:terms)
      self
    end
  end
end