# =XMPP4R - XMPP Library for Ruby
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
# Website::http://xmpp4r.github.io

require 'xmpp4r/iq'
require 'xmpp4r/discovery/iq/discoinfo'
require 'xmpp4r/discovery/iq/discoitems'
require 'xmpp4r/caps/helper/generator'

module Jabber
  module Discovery
    ##
    # Responds to Service Discovery queries on a given node
    #
    # Modify returned elements by these attributes:
    # * Responder#identities
    # * Responder#features (Responder#add_features is a short-cut accepting an Array of Strings, too)
    # * Responder#forms
    # * Responder#items
    class Responder
      ##
      # Service Discovery node this Responder is responsible for
      # (will not answer queries for other nodes)
      attr_reader :node

      ##
      # Identities returned on Discovery Info query
      #
      # Array of [Discovery::Identity]
      attr_accessor :identities
      ##
      # Features returned on Discovery Info query,
      #
      # Array of [Discovery::Feature]
      attr_accessor :features
      ##
      # Forms returned on Discovery Info query
      # (such as Software Information)
      #
      # Array of [Dataforms::XData]
      attr_accessor :forms

      ##
      # Children returned on Discovery Item query
      #
      # May contain other Discovery::Responder instances
      # which will generate an item dynamically from their
      # first identity
      #
      # Array of [Discovery::Item] or [Discovery::Responder] (mixed)
      attr_accessor :items

      ##
      # Set the JID this helper feels responsible for
      # (default: nil, responsible for any JID)
      attr_accessor :my_jid

      CALLBACK_PRIORITY = 180

      ##
      # Initialize responder for a specific node
      # stream:: [Jabber::Stream]
      # node:: [nil] or [String]
      def initialize(stream, node=nil, identities=[], features=[], items=[])
        @stream = stream
        @my_jid = nil
        @node = node
        @identities = identities
        @features = []
        add_features(features)
        @forms = []
        @items = items

        @stream.add_iq_callback(CALLBACK_PRIORITY, self) do |iq|
          my_nodes = [@node, "#{@node}##{generate_ver}"]

          if iq.type == :get and
             iq.query.kind_of? IqQueryDiscoInfo and
             my_nodes.include?(iq.query.node)

            answer = iq.answer(false)
            answer.type = :result
            query = answer.add(IqQueryDiscoInfo.new)
            query.node = iq.query.node
            (@identities + @features + @forms).each do |element|
              query.add(element)
            end
            @stream.send(answer)

            true  # handled

          elsif iq.type == :get and
                iq.query.kind_of? IqQueryDiscoItems and
                my_nodes.include?(iq.query.node)

            answer = iq.answer(false)
            answer.type = :result
            query = answer.add(IqQueryDiscoItems.new)
            query.node = iq.query.node
            @items.each do |item|
              if item.kind_of? Responder
                query.add(item.generate_item)
              else
                query.add(item)
              end
            end
            @stream.send(answer)

            true  # handled

          else
            false # not handled
          end
        end
      end

      ##
      # Add a feature
      # feature:: [Jabber::Discovery::Feature] or [String]
      def add_feature(feature)
        if feature.kind_of? Feature
          @features << feature
        else
          @features << Feature.new(feature.to_s)
        end
      end

      ##
      # Add a series of features
      # features:: Array of [Jabber::Discovery::Feature] or [String]
      def add_features(features)
        features.each { |feature|
          add_feature(feature)
        }
      end

      ##
      # Generate a XEP-0115: Entity Capabilities <c/> element
      # for inclusion in Presence stanzas. This enables efficient
      # caching of Service Discovery information.
      def generate_caps
        Caps::C.new(@node, generate_ver)
      end

      ##
      # Generate an item for inclusion in items discovery in other
      # responders
      # return:: [Discovery::Item] or nil
      def generate_item
        i = @identities.first
        if i
          Item.new(@my_jid || @stream.jid, i.iname, @node)
        else
          nil
        end
      end

      private

      def generate_ver
        Caps::generate_ver(@identities, @features, @forms)
      end
    end
  end
end
