module ZMQ
  class PollItems
    include Enumerable

    def initialize
      @element_size = LibZMQ::PollItem.size
      @store = nil
      @items = []
    end

    def size; @items.size; end

    def empty?; @items.empty?; end

    def address
      clean
      @store
    end

    def get index
      unless @items.empty? || index.nil?
        clean

        # pointer arithmetic in ruby! whee!
        pointer = @store + (@element_size * index)

        # cast the memory to a PollItem
        LibZMQ::PollItem.new pointer
      end
    end
    alias :[] :get

    def <<(obj)
      @dirty = true
      @items << obj
    end
    alias :push :<<
    
    def delete sock
      address = sock.socket.address
      found = false
      
      each_with_index do |item, index|
        if address == item[:socket].address
          @items.delete_at index
          found = true
          @dirty = true
          clean
          break
        end
      end
      
      # these semantics are different from the usual Array#delete; returns a
      # boolean instead of the actual item or nil
      found
    end
    
    def delete_at index
      value = nil
      unless @items.empty?
        value = @items.delete_at index
        @dirty = true
        clean
      end
      
      value
    end

    def each &blk
      clean
      index = 0
      until index >= @items.size do
        struct = get index
        yield struct
        index += 1
      end
    end

    def each_with_index &blk
      clean
      index = 0
      until index >= @items.size do
        struct = get index
        yield struct, index
        index += 1
      end
    end
    
    def inspect
      clean
      str = ""
      each { |item| str << "ptr [#{item[:socket]}], events [#{item[:events]}], revents [#{item[:revents]}], " }
      str.chop.chop
    end
    
    def to_s(); inspect; end

    private

    # Allocate a contiguous chunk of memory and copy over the PollItem structs
    # to this block. Note that the old +@store+ value goes out of scope so when
    # it is garbage collected that native memory should be automatically freed.
    def clean
      if @dirty
        @store = FFI::MemoryPointer.new @element_size, @items.size, true

        # copy over
        offset = 0
        @items.each do |item|
          LibC.memcpy(@store + offset, item.pointer, @element_size)
          offset += @element_size
        end

        @dirty = false
      end
    end

  end # class PollItems
end # module ZMQ