require 'forwardable'

module Parliament
  module Response
    # N-Triple response object that wraps an Array of Grom::Node objects with common helper methods.
    #
    # Delegates a number of common methods to the array of Grom::Nodes including, but not limited to, :size, :each, :map, :count etc.
    #
    # @since 0.1.0
    #
    # @see Parliament::Response::BaseReponse#initialize
    #
    # @attr_reader [Array<Grom::Node>] nodes Graph nodes.
    class NTripleResponse < Parliament::Response::BaseResponse
      include Enumerable
      extend Forwardable
      attr_reader :nodes
      def_delegators :@nodes, :size, :each, :select, :map, :select!, :map!, :count, :length, :[], :empty?

      # @param [Array<Grom::Node>] nodes An array of nodes the response should wrap
      def initialize(nodes)
        @nodes = nodes
      end

      # Given our array of Grom::Nodes, filter them into arrays of 'types' of nodes.
      #
      # Note: this method assumes all of your nodes include a #type attribute or are blank nodes.
      #
      # @since 0.1.0
      #
      # @example Filtering for a single type
      #    node_1 = Grom::Node.new
      #    node_1.instance_variable_set(:type, 'type_1')
      #    node_2 = Grom::Node.new
      #    node_2.instance_variable_set(:type, 'type_3')
      #    node_3 = Grom::Node.new
      #    node_3.instance_variable_set(:type, 'type_1')
      #    node_4 = Grom::Node.new
      #    node_4.instance_variable_set(:type, 'type_2')
      #    nodes = [node_1, node_2, node_3, node_4]
      #
      #    response = Parliament::Response::NTriple.new(nodes)
      #    response.filter('type_2') #=> [#<Grom::Node @type='type_2'>]
      #
      # @example Filtering for multiple types
      #    node_1 = Grom::Node.new
      #    node_1.instance_variable_set(:type, 'type_1')
      #    node_2 = Grom::Node.new
      #    node_2.instance_variable_set(:type, 'type_3')
      #    node_3 = Grom::Node.new
      #    node_3.instance_variable_set(:type, 'type_1')
      #    node_4 = Grom::Node.new
      #    node_4.instance_variable_set(:type, 'type_2')
      #    nodes = [node_1, node_2, node_3, node_4]
      #
      #    response = Parliament::Response::NTripleResponse.new(nodes)
      #    response.filter('type_2', 'type_1') #=> [[#<Grom::Node @type='type_2'>], [#<Grom::Node @type='type_1'>, #<Grom::Node @type='type_1'>]]
      #
      #    # Also consider
      #    type_2, type_1 = response.filter('type_2', 'type_1')
      #    type_2 #=> [#<Grom::Node @type='type_2'>]
      #    type_1 #=> [#<Grom::Node @type='type_1'>, #<Grom::Node @type='type_1'>]
      #
      # @example Filtering blank nodes
      #    node_1 = Grom::Node.new
      #    node_1.instance_variable_set(:type, 'type_1')
      #    node_2 = Grom::Node.new
      #    node_3 = Grom::Node.new
      #    node_3.instance_variable_set(:type, 'type_1')
      #    nodes = [node_1, node_2, node_3]
      #
      #    response = Parliament::Response::NTripleResponse.new(nodes)
      #    response.filter(Grom::Node::BLANK) #=> [#<Grom::Node>]
      #
      # @param [Array<String>] types An array of type strings that you are looking for.
      # @return [Array<Grom::Node> || Array<*Array<Grom::Node>>] If you pass one type, this returns an Array of Grom::Node objects. If you pass multiple, it returns an array, of arrays of Grom::Node objects.
      def filter(*types)
        filtered_objects = Array.new(types.size) { [] }

        unless types.empty?
          @nodes.each do |node|
            type_index = node.blank? ? types.index(::Grom::Node::BLANK) : types.index(node.type)

            filtered_objects[type_index] << node unless type_index.nil?
          end
        end

        result = build_responses(filtered_objects)

        types.size == 1 ? result.first : result
      end

      # Sort the Parliament::Response nodes in ascending order by a set of attributes on each node.
      #
      # @see Parliament::Utils.sort_by
      #
      # @since 0.1.0
      #
      # @param [Array<Symbol>] parameters Attributes to sort on - left to right.
      # @return [Array<Grom::Node>] A sorted array of nodes.
      def sort_by(*parameters)
        Parliament::NTriple::Utils.sort_by(
          {
            list:       @nodes,
            parameters: parameters
          }
        )
      end

      # Sort the Parliament::Response nodes in ascending or descending order by a set of attributes on each node.
      #
      # @see Parliament::Utils.multi_direction_sort
      #
      # @since 0.1.2
      #
      # @param [Hash<Symbol,Symbol>] parameters Attributes to sort on (left to right) - and their direction of sort(asc or desc).
      # @return [Array<Grom::Node>] A sorted array of nodes.
      def multi_direction_sort(parameters)
        Parliament::NTriple::Utils.multi_direction_sort(
          {
            list:       @nodes,
            parameters: parameters
          }
        )
      end

      # Sort the Parliament::Response nodes in descending order by a set of attributes on each node.
      #
      # @see Parliament::Utils.reverse_sort_by
      #
      # @since 0.1.0
      #
      # @param [Array<Symbol>] parameters Attributes to sort on - left to right.
      # @return [Array<Grom::Node>] A sorted array of nodes.
      def reverse_sort_by(*parameters)
        Parliament::NTriple::Utils.reverse_sort_by(
          {
            list:       @nodes,
            parameters: parameters
          }
        )
      end

      private

      def build_responses(filtered_objects)
        result = []

        filtered_objects.each do |objects|
          result << Parliament::Response::NTripleResponse.new(objects)
        end
        result
      end
    end
  end
end