class ProxyIcon : Object {
    StatusNotifierItem item;
    Gtk.StatusIcon icon;
    DbusmenuGtk.Menu menu;

    public ProxyIcon(string name, string object) throws IOError {
        init.begin(name, object);
    }

    async void init(string name, string object) {
        item = yield Bus.get_proxy(BusType.SESSION, name, object, DBusProxyFlags.NONE, null);
        icon = new Gtk.StatusIcon();

        item.changed.connect(property => {
            switch(property) {
            case "tool-tip": set_tooltip(); break;
            }
        });
        item.icon_changed.connect(set_icon);

        set_icon();
        icon.set_name(item.id); // MUST be called after an icon is set due to Gdk internals
        set_tooltip();

        if (item.menu != null) {
            stdout.printf("%s set menu: %s\n", name, item.menu);
            menu = new DbusmenuGtk.Menu(name, item.menu);
        }

        icon.activate.connect(() => item.activate(0, 0)); // TODO: consider smarter coordinate hints
        icon.button_press_event.connect(on_button_press);
        icon.scroll_event.connect(on_scroll);
    }

    bool on_button_press(Gdk.EventButton event) {
        if (event.button == 3) {
            if (menu != null) {
                menu.popup_at_pointer(event);
                return true;
            }

            item.context_menu((int)event.x_root, (int)event.y_root);
            return true;
        }
        return false;
    }

    bool on_scroll(Gdk.EventScroll event) {
        switch(event.direction) {
            case Gdk.ScrollDirection.SMOOTH:
                 // It seems that we can safely ignore this
                 break;
            case Gdk.ScrollDirection.DOWN:
            case Gdk.ScrollDirection.UP:
                 item.scroll((int)event.delta_y, "vertical");
                 break;
            case Gdk.ScrollDirection.LEFT:
            case Gdk.ScrollDirection.RIGHT:
                 item.scroll((int)event.delta_x, "horizontal");
                 break;
            default: stderr.printf("unsupported scrolling event\n");
                 break;
        }
        return true;
    }

    void set_icon() {
        var name = item.icon_name;
        if (name != null && name != "") {
            /* Some apps wrongly set a filename for name, relying on this
             * undefined behavior. Since StatusIcon.set_from_file gives no
             * feedback, this check and race is about the best we can do. */
            if (name.contains("/") && FileUtils.test(name, FileTest.EXISTS)) {
                icon.set_from_file(name);
                return;
            }

            var theme = Gtk.IconTheme.get_for_screen(icon.screen);
            var path = item.icon_theme_path;
            if (path != null) {
                string[] paths;
                theme.get_search_path(out paths);
                if (!(path in paths)) {
                    theme.append_search_path(path);
                }
            }
            if (!theme.rescan_if_needed() && !theme.has_icon(name)) {
                /* Forcibly trigger a rescan in case the icon file was added
                 * after the theme has been scanned (new files don't change
                 * mtime if they're in a subdirectory). */
                string[] paths;
                theme.get_search_path(out paths);
                theme.set_search_path(paths);
            }

            set_icon_name(name);
            if (theme.has_icon(name)) {
                return;
            }
        }

        var pixmaps = item.icon_pixmap;
        if (pixmaps != null) {
            set_icon_pixmap(pixmaps);
        }
    }

    void set_icon_name(string name) {
        icon.set_from_icon_name(name);
    }

    void set_icon_pixmap(IconPixmap[] pixmaps) {
        if (pixmaps.length == 0) return;

        var pixmap = pixmaps[0]; // TODO: choose the best one

        // Convert from ARGB to RGBA
        for (int i = 0; i < pixmap.width * pixmap.height * 4; i += 3) {
            var alpha = pixmap.data[i];
            pixmap.data[i] = pixmap.data[i + 1];
            pixmap.data[i + 1] = pixmap.data[i + 2];
            pixmap.data[i + 2] = pixmap.data[i + 3];
            pixmap.data[i + 3] = alpha;
        }

        var pixbuf = new Gdk.Pixbuf.from_data(pixmap.data, Gdk.Colorspace.RGB,
                true, 8, pixmap.width, pixmap.height, pixmap.width * 4);
        icon.set_from_pixbuf(pixbuf);
    }

    void set_tooltip() {
        var tooltip = item.tool_tip;
        string title = tooltip.title;
        string text = tooltip.text;

        if (text != null && text != "") {
            title += @"\n$(tooltip.text)";
        }
        icon.set_tooltip_markup(title);
    }
}
