module Blather

  # RosterItems hold internal representations of the user's roster
  # including each JID's status.
  class RosterItem
    # @private
    VALID_SUBSCRIPTION_TYPES = [:both, :from, :none, :remove, :to].freeze

    attr_reader :jid,
                :ask,
                :statuses

    attr_accessor :name,
                  :groups

    # @private
    def self.new(item)
      return item if item.is_a?(self)
      super
    end

    # Create a new RosterItem
    #
    # @overload initialize(jid)
    #   Create a new RosterItem based on a JID
    #   @param [Blather::JID] jid the JID object
    # @overload initialize(jid)
    #   Create a new RosterItem based on a JID string
    #   @param [String] jid a JID string
    # @overload initialize(node)
    #   Create a new RosterItem based on a stanza
    #   @param [Blather::Stanza::Iq::Roster::RosterItem] node a RosterItem
    #   stanza
    # @return [Blather::RosterItem] the new RosterItem
    def initialize(item)
      @statuses = []
      @groups = []

      case item
      when JID
        self.jid = item.stripped
      when String
        self.jid = JID.new(item).stripped
      when XMPPNode
        self.jid          = JID.new(item[:jid]).stripped
        self.name         = item[:name]
        self.subscription = item[:subscription]
        self.ask          = item[:ask]
        item.groups.each { |g| @groups << g }
      end

      @groups = [nil] if @groups.empty?
    end

    # Set the jid
    #
    # @param [String, Blather::JID] jid the new jid
    # @see Blather::JID
    def jid=(jid)
      @jid = JID.new(jid).stripped
    end

    # Set the subscription
    # Ensures it is one of VALID_SUBSCRIPTION_TYPES
    #
    # @param [#to_sym] sub the new subscription
    def subscription=(sub)
      if sub && !VALID_SUBSCRIPTION_TYPES.include?(sub = sub.to_sym)
        raise ArgumentError, "Invalid Type (#{sub}), use: #{VALID_SUBSCRIPTION_TYPES*' '}"
      end
      @subscription = sub ? sub : :none
    end

    # Get the current subscription
    #
    # @return [:both, :from, :none, :remove, :to]
    def subscription
      @subscription || :none
    end

    # Set the ask value
    #
    # @param [nil, :subscribe] ask the new ask
    def ask=(ask)
      if ask && (ask = ask.to_sym) != :subscribe
        raise ArgumentError, "Invalid Type (#{ask}), can only be :subscribe"
      end
      @ask = ask ? ask : nil
    end

    # Set the status then sorts them according to priority
    #
    # @param [Blather::Stanza::Status] the new status
    def status=(presence)
      @statuses.delete_if { |s| s.from == presence.from || s.state == :unavailable }
      @statuses << presence
      @statuses.sort!
    end

    # The status with the highest priority
    #
    # @param [String, nil] resource the resource to get the status of
    def status(resource = nil)
      top = if resource
        @statuses.detect { |s| s.from.resource == resource }
      else
        @statuses.last
      end
    end

    # Translate the RosterItem into a proper stanza that can be sent over the
    # stream
    #
    # @return [Blather::Stanza::Iq::Roster]
    def to_stanza(type = nil)
      Stanza::Iq::Roster.new type, to_node
    end

    def to_node
      Stanza::Iq::Roster::RosterItem.new jid, name, subscription, ask, groups
    end

    # Compare two RosterItems by their JID
    #
    # @param [Blather::JID] other the JID to compare against
    # @return [Fixnum<-1, 0, 1>]
    def <=>(other)
      JID.new(self.jid) <=> JID.new(other.jid)
    end

    # Compare two RosterItem objects by name, type and category
    # @param [RosterItem] o the Identity object to compare against
    # @return [true, false]
    def eql?(o, *fields)
      o.is_a?(self.class) &&
      o.jid == self.jid &&
      o.groups == self.groups
    end

    # @private
    def ==(o)
      eql?(o)
    end
  end #RosterItem

end
