require 'i3ipc/protocol'
require 'i3ipc/reply'

module I3Ipc
  # Throws when subscribing to an invalid event. Valid events are
  # listed in function event_number().
  class WrongEvent < RuntimeError
    def initialize(event_name)
      @event_name = event_name
    end

    def message
      %Q{Tried to subscribe to invalid event type '#{@event_name}'}
    end
  end

  # Entry point for communication with i3-ipc.
  # Able to send/receive messages and convert
  # responses.
  #
  # @example
  #   con = Connection.new
  #   p con.version.human_readable         # => 4.10.2 (2015-0...
  #   p con.command('focus left').success? # => true
  #   p con.workspaces[0].name             # => 0 Term
  #   # ...
  #   con.close
  class Connection

    def initialize(protocol = Protocol.new, autoconnect = true)
      @protocol = protocol
      open if autoconnect
    end

    def open
      @protocol.connect
    end

    def close
      @protocol.disconnect
    end

    def command(cmds)
      reply_for(0, cmds)
    end

    def workspaces
      reply_for(1)
    end

    def subscribe(event, block)
      event_number = event_number(event)

      # Send subscription request
      @protocol.send(2, [event])

      reply = Reply.parse(@protocol.receive 2)
      raise WrongEvent.new(event) unless reply.success?

      pid = Thread.new do
        while true
          reply = Reply.parse(@protocol.receive_event event_number)
          block.call(reply)
        end
      end

      pid
    end

    def outputs
      reply_for(3)
    end

    def tree
      reply_for(4)
    end

    def marks
      reply_for(5)
    end

    def bar_config
      reply_for(6)
    end

    def version
      reply_for(7)
    end

    private

    def reply_for(type, message = nil)
      @protocol.send(type, message)

      Reply.parse(@protocol.receive type)
    end

    def event_number(event)
      case event
      when 'workspace'         then 0
      when 'output'            then 1
      when 'mode'              then 2
      when 'window'            then 3
      when 'barconfig_update'  then 4
      when 'binding'           then 5
      else raise WrongEvent.new(event)
      end
    end
  end
end
