require 'eventmachine'
require 'libvirt'

#ENV["LIBVIRT_DEBUG"] = "1"

module Handle
    attr_reader :opaque
    def initialize(id, events, opaque)
        @id = id
        @events = events
        @opaque = opaque
        notifable(events)
    end

    def notifable(events)
        @events = events
        self.notify_readable = events & (Libvirt::EVENT_HANDLE_READABLE | Libvirt::EVENT_HANDLE_ERROR | Libvirt::EVENT_HANDLE_HANGUP) != 0
        self.notify_writable = events & Libvirt::EVENT_HANDLE_WRITABLE != 0
    end

    def notify_readable
        puts ":notify_readable id=#{@id} fd=#{@fd} events=#{@events}" 
        Libvirt::event_invoke_handle_callback(@id, @fd, @events, @opaque)
    end

    def notify_writable
        puts "notify_writable id=#{@id} fd=#{@fd} events=#{@events}" 
        Libvirt::event_invoke_handle_callback(@id, @fd, @events, @opaque)
    end

    def unbind
        puts "unbind id=#{@id} fd=#{@fd}"
#        self.notify_readable = false
#        self.notify_writable = false
#        self.detach
    end
end

class Handles
    def initialize()
        @handles = []
    end

    def add_handle(fd, events, opaque)
        puts "add_handle fd=#{fd} events=#{events}"
        id = @handles.length
        @handles << EventMachine.watch(fd, Handle, id, events, opaque)
        id
    end

    def remove_handle(id)
        puts "remove_handle id=#{id}"
        @handles[id].detach
        @handles.delete_at(id).opaque
    end

    def update_handle(id, events)
        puts "update_handle id=#{id} events=#{events}"
        @handles[id].notifable(events)
    end
end

class Timers
    def initialize()
        @timers = []
    end

    def add_timer (interval, opaque)
        puts "add_timer interval=#{interval}"
        id = @timers.length
        @timers << {
            timer: EventMachine.add_periodic_timer(interval) { Libvirt::event_invoke_timeout_callback(id, opaque) },
            opaque: opaque
        }
        if interval < 0
            @timers.last[:timer].cancel
        end
        id
    end

    def update_timer (id, interval)
        puts "update_timer id=#{id} interval=#{interval}"
        if interval == -1
            @timers[id][:timer].cancel
        else
            @timers[id][:timer].interval = interval
        end
    end

    def remove_timer (id)
        puts "remove_timer id=#{id}"
        @timers[id][:timer].cancel
        @timers.delete_at(id)[:opaque]
    end
end

def dom_event_callback_lifecycle(conn, dom, event, detail, opaque)
    puts " dom_event_callback_lifecycle conn=#{conn} dom=#{dom} event=#{event} detail=#{detail} opaque=#{opaque}"
end

def dom_event_callback_reboot(conn, dom, opaque)
    puts "dom_event_callback_reboot conn=#{conn} dom=#{dom} opaque=#{opaque}"
end

EventMachine.run {
    handles = Handles.new
    timers  = Timers.new
    Libvirt::event_register_impl(
        lambda {|fd, events, opaque| handles.add_handle(fd, events, opaque) },
        lambda {|id, events| handles.update_handle(id, events) },
        lambda {|id| handles.remove_handle(id) },
        lambda {|interval, opaque| timers.add_timer(interval, opaque) },
        lambda {|id, interval| timers.update_timer(id, interval) },
        lambda {|id| timers.remove_timer(id) },
    )
    conn = Libvirt::open("qemu:///system")

    cbs = []
    cbs << conn.domain_event_register_any(Libvirt::Connect::DOMAIN_EVENT_ID_LIFECYCLE, :dom_event_callback_lifecycle, nil, "sweet")
    cbs << conn.domain_event_register_any(Libvirt::Connect::DOMAIN_EVENT_ID_REBOOT, :dom_event_callback_reboot)

    EventMachine.add_timer(10) {
        puts "ending"
        cbs.each { |c| conn.domain_event_deregister_any(c) }
        conn.close
        EventMachine.stop_event_loop
    }
}
