/*
* Copyright (c) 2010 Michal Hruby <michal.mhr@gmail.com>
*               2017 elementary LLC.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* Authored by: Michal Hruby <michal.mhr@gmail.com>
*/

namespace Synapse {
    [DBus (name = "org.freedesktop.DBus")]
    public interface FreeDesktopDBus : GLib.Object {
        public const string UNIQUE_NAME = "org.freedesktop.DBus";
        public const string OBJECT_PATH = "/org/freedesktop/DBus";

        public abstract async string[] list_queued_owners (string name) throws Error;
        public abstract async string[] list_names () throws Error;
        public abstract async string[] list_activatable_names () throws Error;
        public abstract async bool name_has_owner (string name) throws Error;
        public signal void name_owner_changed (string name, string old_owner, string new_owner);
        public abstract async uint32 start_service_by_name (string name, uint32 flags) throws Error;
        public abstract async string get_name_owner (string name) throws Error;
    }

    public class DBusService : Object {
        private FreeDesktopDBus proxy;
        private Gee.Set<string> owned_names;
        private Gee.Set<string> activatable_names;
        private Gee.Set<string> system_activatable_names;

        private Utils.AsyncOnce<bool> init_once;

        // singleton that can be easily destroyed
        public static DBusService get_default () {
            return instance ?? new DBusService ();
        }

        private DBusService () { }

        private static unowned DBusService? instance;

        construct {
            instance = this;
            owned_names = new Gee.HashSet<string> ();
            activatable_names = new Gee.HashSet<string> ();
            system_activatable_names = new Gee.HashSet<string> ();
            init_once = new Utils.AsyncOnce<bool> ();

            initialize.begin ();
        }

        ~DBusService () {
            instance = null;
        }

        private void name_owner_changed (FreeDesktopDBus sender, string name, string old_owner, string new_owner) {
            if (name.has_prefix (":")) {
                return;
            }

            if (old_owner == "") {
                owned_names.add (name);
                owner_changed (name, true);
            } else if (new_owner == "") {
                owned_names.remove (name);
                owner_changed (name, false);
            }
        }

        public signal void owner_changed (string name, bool is_owned);

        public bool name_has_owner (string name) {
            return name in owned_names;
        }

        public bool name_is_activatable (string name) {
            return name in activatable_names;
        }

        public bool service_is_available (string name) {
            return name in system_activatable_names;
        }

        public async void initialize () {
            if (init_once.is_initialized ()) {
                return;
            }
            var is_locked = yield init_once.enter ();
            if (!is_locked) {
                return;
            }

            string[] names;
            try {
                proxy = Bus.get_proxy_sync (BusType.SESSION,
                FreeDesktopDBus.UNIQUE_NAME,
                FreeDesktopDBus.OBJECT_PATH);

                proxy.name_owner_changed.connect (this.name_owner_changed);
                names = yield proxy.list_names ();
                foreach (unowned string name in names) {
                    if (name.has_prefix (":")) {
                        continue;
                    }
                    owned_names.add (name);
                }

                names = yield proxy.list_activatable_names ();
                foreach (unowned string session_act in names) {
                    activatable_names.add (session_act);
                }
            } catch (Error err) {
                warning ("%s", err.message);
            }

            try {
                FreeDesktopDBus sys_proxy = Bus.get_proxy_sync (
                BusType.SYSTEM,
                FreeDesktopDBus.UNIQUE_NAME,
                FreeDesktopDBus.OBJECT_PATH);

                names = yield sys_proxy.list_activatable_names ();
                foreach (unowned string system_act in names) {
                    system_activatable_names.add (system_act);
                }
            } catch (Error sys_err) {
                warning ("%s", sys_err.message);
            }
            init_once.leave (true);
        }
    }
}
