/*
 * Copyright 2014 Canonical Ltd.
 * Copyright 2021 Robert Tari
 *
 * 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; version 3.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *      Ted Gould <ted@canonical.com>
 *      Robert Tari <robert@tari.in>
 */

public class MediaPlayerUser : MediaPlayer {
    Act.UserManager accounts_manager = Act.UserManager.get_default();
    string username;
    Act.User? actuser = null;
    AccountsServiceSoundSettings? proxy = null;
    GreeterBroadcast? greeter = null;

    HashTable<string, bool> properties_queued = new HashTable<string, bool>(str_hash, str_equal);
    uint properties_timeout = 0;

    /* Grab the user from the Accounts service and, when it is loaded then
       set up a proxy to its sound settings */
    public MediaPlayerUser(string user) {
        username = user;

        actuser = accounts_manager.get_user(user);
        actuser.notify["is-loaded"].connect(() => {
            debug("User loaded");

            this.proxy = null;

            Bus.get_proxy.begin<AccountsServiceSoundSettings> (
                BusType.SYSTEM,
                "org.freedesktop.Accounts",
                actuser.get_object_path(),
                DBusProxyFlags.GET_INVALIDATED_PROPERTIES,
                null,
                new_proxy);
        });

        Bus.get_proxy.begin<GreeterBroadcast> (
            BusType.SYSTEM,
            "org.ayatana.Desktop.Greeter.Broadcast",
            "/org/ayatana/Desktop/Greeter/Broadcast",
            DBusProxyFlags.NONE,
            null,
            greeter_proxy_new);
    }

    ~MediaPlayerUser () {
        if (properties_timeout != 0) {
            Source.remove(properties_timeout);
            properties_timeout = 0;
        }
    }

    /* Ensure that we've collected all the changes so that we only signal
       once for variables like 'track' */
    bool properties_idle () {
        properties_timeout = 0;

        properties_queued.@foreach((key, value) => {
            debug("Notifying '%s' changed", key);
            this.notify_property(key);
        });

        properties_queued.remove_all();

        return Source.REMOVE;
    }

    /* Turns the DBus names into the object properties */
    void queue_property_notification (string dbus_property_name) {
        if (properties_timeout == 0) {
            properties_timeout = Idle.add(properties_idle);
        }

        switch (dbus_property_name) {
        case "Timestamp":
            properties_queued.insert("name", true);
            properties_queued.insert("icon", true);
            properties_queued.insert("state", true);
            properties_queued.insert("current-track", true);
            properties_queued.insert("is-running", true);
            break;
        case "PlayerName":
            properties_queued.insert("name", true);
            break;
        case "PlayerIcon":
            properties_queued.insert("icon", true);
            break;
        case "State":
            properties_queued.insert("state", true);
            break;
        case "Title":
        case "Artist":
        case "Album":
        case "ArtUrl":
            properties_queued.insert("current-track", true);
            break;
        }
    }

    void new_proxy (GLib.Object? obj, AsyncResult res) {
        try {
            this.proxy = Bus.get_proxy.end (res);

            var gproxy = this.proxy as DBusProxy;
            gproxy.g_properties_changed.connect ((proxy, changed, invalidated) => {
                string key = "";
                Variant value;
                VariantIter iter = new VariantIter(changed);

                while (iter.next("{sv}", &key, &value)) {
                    queue_property_notification(key);
                }

                foreach (var invalid in invalidated) {
                    queue_property_notification(invalid);
                }
            });

            debug("Notifying player is ready for user: %s", this.username);
            this.notify_property("is-running");
        } catch (Error e) {
            this.proxy = null;
            warning("Unable to get proxy to user '%s' sound settings: %s", username, e.message);
        }
    }

    bool proxy_is_valid () {
        if (this.proxy == null) {
            return false;
        }

        /* More than 10 minutes old */
        if (this.proxy.timestamp < GLib.get_monotonic_time() - 10 * 60 * 1000 * 1000) {
            return false;
        }

        return true;
    }

    public override string id {
        get { return username; }
    }

    /* These values come from the proxy */
    string name_cache;
    public override string name {
        get {
            if (proxy_is_valid()) {
                name_cache = this.proxy.player_name;
                debug("Player Name: %s", name_cache);
                return name_cache;
            } else {
                return "";
            }
        }
    }
    string state_cache;
    public override string state {
        get {
            if (proxy_is_valid()) {
                state_cache = this.proxy.state;
                debug("State: %s", state_cache);
                return state_cache;
            } else {
                return "";
            }
        }
        set { }
    }
    Icon icon_cache;
    public override Icon? icon {
        get {
            if (proxy_is_valid()) {
                icon_cache = Icon.deserialize(this.proxy.player_icon);
                return icon_cache;
            } else {
                return null;
            }
        }
    }

    /* Placeholder */
    public override string dbus_name { get { return ""; } }

    /* If it's shown externally it's running */
    public override bool is_running { get { return proxy_is_valid(); } }
    /* A bit weird.  Not sure how we should handle this. */
    public override bool can_raise { get { return true; } }

    /* Fill out the track based on the values in the proxy */
    MediaPlayer.Track track_cache;
    public override MediaPlayer.Track? current_track {
        get {
            if (proxy_is_valid()) {
                track_cache = new MediaPlayer.Track(
                    this.proxy.artist,
                    this.proxy.title,
                    this.proxy.album,
                    this.proxy.art_url
                );
                return track_cache;
            } else {
                return null;
            }
        }
        set { }
    }

    void greeter_proxy_new (GLib.Object? obj, AsyncResult res) {
        try {
            this.greeter = Bus.get_proxy.end (res);
        } catch (Error e) {
            this.greeter = null;
            warning("Unable to get greeter proxy: %s", e.message);
        }
    }

    /* Control functions through ayatana-greeter-session-broadcast */
    public override void activate () {
        /* TODO: */
    }
    public override void play_pause () {
        debug("Play Pause for user: %s", this.username);

        if (this.greeter != null) {
            this.greeter.RequestSoundPlayPause.begin(this.username, (obj, res) => {
                try {
                    var broadcasts = (obj as GreeterBroadcast);

                    if (broadcasts != null)
                    {
                        broadcasts.RequestSoundPlayPause.end(res);
                    }
                } catch (Error e) {
                    warning("Unable to send play pause: %s", e.message);
                }
            });
        } else {
            warning("No ayatana-greeter-session-broadcast to send play-pause");
        }
    }
    public override void next () {
        debug("Next for user: %s", this.username);

        if (this.greeter != null) {
            this.greeter.RequestSoundNext.begin(this.username, (obj, res) => {
                try {
                    var broadcasts = (obj as GreeterBroadcast);

                    if (broadcasts != null)
                    {
                        broadcasts.RequestSoundNext.end(res);
                    }
                } catch (Error e) {
                    warning("Unable to send next: %s", e.message);
                }
            });
        } else {
            warning("No ayatana-greeter-session-broadcast to send next");
        }
    }
    public override void previous () {
        debug("Previous for user: %s", this.username);

        if (this.greeter != null) {
            this.greeter.RequestSoundPrev.begin(this.username, (obj, res) => {
                try {
                    var broadcasts = (obj as GreeterBroadcast);

                    if (broadcasts != null)
                    {
                        broadcasts.RequestSoundPrev.end(res);
                    }
                } catch (Error e) {
                    warning("Unable to send previous: %s", e.message);
                }
            });
        } else {
            warning("No ayatana-greeter-session-broadcast to send previous");
        }
    }

    /* Play list functions are all null as we don't support the
       playlist feature on the greeter */
    public override uint get_n_playlists() {
        return 0;
    }
    public override string get_playlist_id (int index) {
        return "";
    }
    public override string get_playlist_name (int index) {
        return "";
    }
    public override void activate_playlist_by_name (string playlist) {
    }
}
