/*
 * This file is part of budgie-desktop
 * 
 * Copyright © 2016-2019 Budgie Desktop Developers
 * 
 * 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.
 */

namespace Budgie
{

/**
 * Default width for an OSD notification
 */
public const int OSD_SIZE = 350;

/**
 * How long before the visible OSD expires, default is 2.5 seconds
 */
public const int OSD_EXPIRE_TIME = 2500;

/**
 * Our name on the session bus. Reserved for Budgie use
 */
public const string OSD_DBUS_NAME        = "org.budgie_desktop.BudgieOSD";

/**
 * Unique object path on OSD_DBUS_NAME
 */
public const string OSD_DBUS_OBJECT_PATH = "/org/budgie_desktop/BudgieOSD";


/**
 * The BudgieOSD provides a very simplistic On Screen Display service, complying with the
 * private GNOME Settings Daemon -> GNOME Shell protocol.
 *
 * In short, all elements of the permanently present window should be able to hide or show
 * depending on the updated ShowOSD message, including support for a progress bar (level),
 * icon, optional label.
 *
 * This OSD is used by gnome-settings-daemon to portray special events, such as brightness/volume
 * changes, physical volume changes (disk eject/mount), etc. This special window should remain
 * above all other windows and be non-interactive, allowing unobtrosive overlay of information
 * even in full screen movies and games.
 *
 * Each request to ShowOSD will reset the expiration timeout for the OSD's current visibility,
 * meaning subsequent requests to the OSD will keep it on screen in a natural fashion, allowing
 * users to "hold down" the volume change buttons, for example.
 */
[GtkTemplate (ui = "/com/solus-project/budgie/daemon/osd.ui")]
public class OSD : Gtk.Window
{

    /**
     * Main text display
     */
    [GtkChild]
    private Gtk.Label label_title;

    /**
     * Main display image. Prefer symbolic icons!
     */
    [GtkChild]
    private Gtk.Image image_icon;

    /**
     * Optional progressbar
     */
    [GtkChild]
    private Gtk.ProgressBar progressbar;

    /**
     * Track the primary monitor to show on
     */
    private Gdk.Monitor primary_monitor;

    /**
     * Current text to display. NULL hides the widget.
     */
    public string? osd_title {
        public set {
            string? r = value;
            if (r == null) {
                label_title.set_visible(false);
            } else {
                label_title.set_visible(true);
                label_title.set_markup(r);
            }
        }
        public owned get {
            if (!label_title.get_visible()) {
                return null;
            }
            return label_title.get_label();
        }
    }

    /**
     * Current icon to display. NULL hides the widget
     */
    public string? osd_icon {
        public set {
            string? r = value;
            if (r == null) {
                image_icon.set_visible(false);
            } else {
                image_icon.set_from_icon_name(r, Gtk.IconSize.INVALID);
                image_icon.pixel_size = 48;
                image_icon.set_visible(true);
            }
        }
        public owned get {
            if (!image_icon.get_visible()) {
                return null;
            }
            string ret;
            Gtk.IconSize _icon_size;
            image_icon.get_icon_name(out ret, out _icon_size);
            return ret;
        }
    }

    /**
     * Current value for the progressbar. Values less than 1 hide the bar
     */
    public int32 osd_progress {
        public set {
            int32 v = value;
            if (v < 0) {
                progressbar.set_visible(false);
            } else {
                double fraction = v.clamp(0, 100) / 100.0;
                progressbar.set_fraction(fraction);
                progressbar.set_visible(true);
            }
        }
        public get {
            if (!progressbar.get_visible()) {
                return -1;
            } else {
                return (int32)(progressbar.get_fraction() * 100);
            }
        }
    }

    /**
     * Construct a new BudgieOSD widget
     */
    public OSD()
    {
        Object(type: Gtk.WindowType.POPUP, type_hint: Gdk.WindowTypeHint.NOTIFICATION);
        /* Skip everything, appear above all else, everywhere. */
        resizable = false;
        skip_pager_hint = true;
        skip_taskbar_hint = true;
        set_decorated(false);
        set_keep_above(true);
        stick();

        /* Set up an RGBA map for transparency styling */
        Gdk.Visual? vis = screen.get_rgba_visual();
        if (vis != null) {
            this.set_visual(vis);
        }

        /* Update the primary monitor notion */
        screen.monitors_changed.connect(on_monitors_changed);

        /* Set up size */
        set_default_size(OSD_SIZE, -1);
        realize();

        osd_title = null;
        osd_icon = null;
        osd_progress = -1;

        get_child().show_all();

        /* Get everything into position prior to the first showing */
        on_monitors_changed();
    }

    /**
     * Monitors changed, find out the primary monitor, and schedule move of OSD
     */
    private void on_monitors_changed()
    {
        primary_monitor = screen.get_display().get_primary_monitor();
        move_osd();
    }

    /**
     * Move the OSD into the correct position
     */
    public void move_osd()
    {
        /* Find the primary monitor bounds */
        Gdk.Rectangle bounds = primary_monitor.get_geometry();
        Gtk.Allocation alloc;

        get_child().get_allocation(out alloc);

        /* For now just center it */
        int x = bounds.x + ((bounds.width / 2) - (alloc.width / 2));
        int y = bounds.y + ((int)(bounds.height * 0.85));
        move(x, y);
    }
} /* End class OSD (BudgieOSD) */

/**
 * BudgieOSDManager is responsible for managing the BudgieOSD over d-bus, receiving
 * requests, for example, from budgie-wm
 */
[DBus (name = "org.budgie_desktop.BudgieOSD")]
public class OSDManager
{
    private OSD? osd_window = null;
    private uint32 expire_timeout = 0;

    [DBus (visible = false)]
    public OSDManager()
    {
        osd_window = new OSD();
    }

    /**
     * Own the OSD_DBUS_NAME
     */
    [DBus (visible = false)]
    public void setup_dbus(bool replace)
    {
        var flags = BusNameOwnerFlags.ALLOW_REPLACEMENT;
        if (replace) {
            flags |= BusNameOwnerFlags.REPLACE;
        }
        Bus.own_name(BusType.SESSION, Budgie.OSD_DBUS_NAME, flags,
            on_bus_acquired, ()=> {}, Budgie.DaemonNameLost);
    }

    /**
     * Acquired OSD_DBUS_NAME, register ourselves on the bus
     */
    private void on_bus_acquired(DBusConnection conn)
    {
        try {
            conn.register_object(Budgie.OSD_DBUS_OBJECT_PATH, this);
        } catch (Error e) {
            stderr.printf("Error registering BudgieOSD: %s\n", e.message);
        }
        Budgie.setup = true;
    }

    /**
     * Show the OSD on screen with the given parameters:
     * icon: string Icon-name to use
     * label: string Text to display, if any
     * level: int32 Progress-level to display in the OSD
     * monitor: int32 The monitor to display the OSD on (currently ignored)
     */
    public void ShowOSD(HashTable<string,Variant> params)
    {
        string? icon_name = null;
        string? label = null;
        int32 level = -1;

        if (params.contains("icon")) {
            icon_name = params.lookup("icon").get_string();
        }
        if (params.contains("label")) {
            label = params.lookup("label").get_string();
        }
        if (params.contains("level")) {
            level = params.lookup("level").get_int32();
        }
        /* Update the OSD accordingly */
        osd_window.osd_title = label;
        osd_window.osd_icon = icon_name;
        osd_window.osd_progress = level;

        this.reset_osd_expire(OSD_EXPIRE_TIME);
    }

    /**
     * Reset and update the expiration for the OSD timeout
     */
    private void reset_osd_expire(int timeout_length)
    {
        if (expire_timeout > 0) {
            Source.remove(expire_timeout);
            expire_timeout = 0;
        }
        if (!osd_window.get_visible()) {
            osd_window.move_osd();
        }
        osd_window.show();
        expire_timeout = Timeout.add(timeout_length, this.osd_expire);
    }

    /**
     * Expiration timeout was met, so hide the OSD Window
     */
    private bool osd_expire()
    {
        if (expire_timeout == 0) {
            return false;
        }
        osd_window.hide();
        expire_timeout = 0;
        return false;
    }
} /* End class OSDManager (BudgieOSDManager) */

} /* End namespace Budgie */
