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

require 'xmpp4r/xmlstanza'
require 'xmpp4r/x'

module Jabber
  ##
  # The presence class is used to construct presence messages to 
  # send to the Jabber service.
  class Presence < XMLStanza
    include Comparable

    ##
    # Create presence stanza
    # show:: [String] Initial Availability Status
    # status:: [String] Initial status message
    # priority:: [Fixnum] Initial priority value
    def initialize(show=nil, status=nil, priority=nil)
      super("presence")
      set_show(show) if show
      set_status(status) if status
      set_priority(priority) if priority
    end

    ##
    # Add an element to the presence stanza
    # * <x/> elements are converted to [X]
    # element:: [REXML::Element] to add
    def typed_add(element)
      if element.kind_of?(REXML::Element) && (element.name == 'x')
        super(X::import(element))
      else
        super(element)
      end
    end

    ##
    # Create a new presence from a stanza
    # result:: [Presence] Imported XMLStanza
    def Presence.import(xmlstanza)
      Presence::new.import(xmlstanza)
    end

    ##
    # Get type of presence
    #
    # result:: [Symbol] or [Nil] Possible values are:
    # * :error
    # * :probe (Servers send this to request presence information)
    # * :subscribe (Subscription request)
    # * :subscribed (Subscription approval)
    # * :unavailable (User has gone offline)
    # * :unsubscribe (Unsubscription request)
    # * :unsubscribed (Unsubscription approval)
    # * [nil] (available)
    # See RFC3921 - 2.2.1. for explanation.
    def type
      case super
        when 'error' then :error
        when 'probe' then :probe
        when 'subscribe' then :subscribe
        when 'subscribed' then :subscribed
        when 'unavailable' then :unavailable
        when 'unsubscribe' then :unsubscribe
        when 'unsubscribed' then :unsubscribed
        else nil
      end
    end

    ##
    # Set type of presence
    # val:: [Symbol] See type for possible subscription types
    def type=(val)
      case val
        when :error then super('error')
        when :probe then super('probe')
        when :subscribe then super('subscribe')
        when :subscribed then super('subscribed')
        when :unavailable then super('unavailable')
        when :unsubscribe then super('unsubscribe')
        when :unsubscribed then super('unsubscribed')
        else super(nil)
      end
    end

    ##
    # Set type of presence (chaining-friendly)
    # val:: [Symbol] See type for possible subscription types
    def set_type(val)
      self.type = val
      self
    end

    ##
    # Get the first <x/> element of this stanza
    # result:: [REXML::Element] or nil
    def x
      xe = nil
      each_element('x') { |e| xe = e }
      xe
    end

    ##
    # Get Availability Status (RFC3921 - 5.2)
    # result:: [Symbol] or [Nil] Valid values according to RFC3921:
    # * nil (Available, no <show/> element)
    # * :away
    # * :chat (Free for chat)
    # * :dnd (Do not disturb)
    # * :xa (Extended away)
    def show
      e = first_element('show')
      text = e ? e.text : nil
      case text
        when 'away' then :away
        when 'chat' then :chat
        when 'dnd' then :dnd
        when 'xa' then :xa
        else nil
      end
    end

    ##
    # Set Availability Status
    # val:: [Symbol] or [Nil] See show for explanation
    def show=(val)
      xe = first_element('show')
      if xe.nil?
        xe = add_element('show')
      end
      case val
        when :away then text = 'away'
        when :chat then text = 'chat'
        when :dnd then text = 'dnd'
        when :xa then text = 'xa'
        when nil then text = nil
        else raise "Invalid value for show."
      end

      if text.nil?
        delete_element(xe)
      else
        xe.text = text
      end
    end

    ##
    # Set Availability Status (chaining-friendly)
    # val:: [Symbol] or [Nil] See show for explanation
    def set_show(val)
      self.show = val
      self
    end

    ##
    # Get status message
    # result:: [String] or nil
    def status
      first_element_text('status')
    end

    ##
    # Set status message
    # val:: [String] or nil
    def status=(val)
      if val.nil?
        delete_element('status')
      else
        replace_element_text('status', val)
      end
    end

    ##
    # Set status message (chaining-friendly)
    # val:: [String] or nil
    def set_status(val)
      self.status = val
      self
    end

    ##
    # Get presence priority, or nil if absent
    # result:: [Integer]
    def priority
       e = first_element_text('priority')
      if e
        return e.to_i
      else
        return nil
      end
    end

    ##
    # Set presence priority
    # val:: [Integer] Priority value between -128 and +127
    #
    # *Warning:* negative values make you receive no subscription requests etc.
    # (RFC3921 - 2.2.2.3.)
    def priority=(val)
      if val.nil?
        delete_element('priority')
      else
        replace_element_text('priority', val)
      end
    end

    ##
    # Set presence priority (chaining-friendly)
    # val:: [Integer] Priority value between -128 and +127
    def set_priority(val)
      self.priority = val
      self
    end

    ##
    # Compare two presences using priority
    # (with cmp_interest as fall-back).
    def <=>(o)
      if priority.to_i == o.priority.to_i
        cmp_interest(o)
      else
        priority.to_i <=> o.priority.to_i
      end
    end

    ##
    # Compare two presences. The most suitable to talk with is the
    # biggest.
    PRESENCE_STATUS = { :chat => 4,
                        nil => 3,
                        :dnd => 2,
                        :away => 1,
                        :xa => 0,
                        :unavailable => -1,
                        :error => -2 }
    def cmp_interest(o)
      if type.nil?
        if o.type.nil?
          # both available.
          PRESENCE_STATUS[show] <=> PRESENCE_STATUS[o.show]
        else
          return -1
        end
      elsif o.type.nil?
        return 1
      else
        # both are non-nil. We consider this is equal.
        return 0
      end
    end

  end
end
