module Sonos
  # The Sonos system. The root object to manage the collection of groups and devices. This is
  # intended to be a singleton accessed from `Sonos.system`.
  class System
    attr_reader :topology
    attr_reader :groups
    attr_reader :devices

    # Initialize the system
    # @param [Array] the system topology. If this is nil, it will autodiscover.
    def initialize(topology = Discovery.new.topology)
      rescan topology
    end

    # Returns all speakers
    def speakers
      @devices.select(&:speaker?)
    end

    # Pause all speakers
    def pause_all
      speakers.each do |speaker|
        speaker.pause if speaker.has_music?
      end
    end

    # Play all speakers
    def play_all
      speakers.each do |speaker|
        speaker.play if speaker.has_music?
      end
    end

    # Party Mode!  Join all speakers into a single group.
    def party_mode new_master = nil
      return nil unless speakers.length > 1

      new_master = find_party_master if new_master.nil?

      party_over
      speakers.each do |slave|
        next if slave.uid == new_master.uid
        slave.join new_master
      end
      rescan @topology
    end

    def find_party_master
      # 1: If there are any pre-existing groups playing something, use
      # the lowest-numbered group's master. But ensure to only check a
      # master_speaker that is actually a speaker, and not an Accessory.
      groups.each do |group|
        if group.master_speaker.speaker? and group.master_speaker.has_music?
          return group.master_speaker
        end
      end

      # 2: Lowest-number speaker that's playing something
      speakers.each do |speaker|
        return speaker if speaker.has_music?
      end

      # 3: lowest-numbered speaker
      speakers[0]
    end

    def find_speaker_by_name(name)
      speakers.each do |speaker|
        return speaker if(speaker.name == name)
      end
      return nil
    end

    def find_speaker_by_uid(uid)
      uid = "uuid:" + uid unless uid[0,5] == "uuid:"
      speakers.each do |speaker|
        return speaker if(speaker.uid == uid)
      end
      return nil
    end

    # Party's over :(
    def party_over
      groups.each { |g| g.disband }
      rescan @topology
    end

    def rescan(topology = Discovery.new.topology)
      @topology = topology
      @groups = []
      @devices = @topology.collect(&:device)

      construct_groups

      speakers.each do |speaker|
        speaker.group_master = speaker
        @groups.each do |group|
          if group.master_speaker.uid == speaker.uid
            speaker.group_master = speaker
            group.master_speaker = speaker
          end
          group.slave_speakers.each do |slave|
            speaker.group_master = group.master_speaker if slave.uid == speaker.uid
          end
        end
      end
    end

  private

    def construct_groups
      # Loop through all of the unique groups
      @topology.collect(&:group).uniq.each do |group_uid|

        # set group's master nade using topology's coordinator parameter
        master = nil
        @topology.each do |node|
          # Select only the nodes with this group uid
          next unless node.group == group_uid
          master = node if node.coordinator == "true"
        end

        # Skip this group if there is no master
        next if master.nil?

        # register other nodes in groups as slave nodes
        nodes = []
        @topology.each do |node|
          # Select only the nodes with this group uid
          next unless node.group == group_uid
          nodes << node unless node.uuid == master.uuid
        end

        # Add the group
        @groups << Group.new(master.device, nodes.collect(&:device))
      end
    end
  end
end
