const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Mainloop = imports.mainloop;

const ExtensionUtils = imports.misc.extensionUtils;
const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main;
const StatusSystem = imports.ui.status.system;
const PopupMenu = imports.ui.popupMenu;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const ExtensionSystem = imports.ui.extensionSystem;
const ConfirmDialog = Me.imports.confirmDialog;
const Prefs = new Me.imports.prefs.Prefs();


// Use __ () and N__() for the extension gettext domain, and reuse
// the shell domain with the default _() and N_()
const Gettext = imports.gettext.domain('hibernate-status-button');
const __ = Gettext.gettext;
const N__ = function(e) { return e };
ExtensionUtils.initTranslations('hibernate-status-button');

const HIBERNATE_CHECK_TIMEOUT = 20000;

class Extension {
    _loginManagerCanHibernate(asyncCallback) {
        if (this._loginManager._proxy) {
            // systemd path
            this._loginManager._proxy.call("CanHibernate",
                null,
                Gio.DBusCallFlags.NONE,
                -1, null, function (proxy, asyncResult) {
                    let result, error;

                    try {
                        result = proxy.call_finish(asyncResult).deep_unpack();
                    } catch (e) {
                        error = e;
                    }

                    if (error)
                        asyncCallback(false);
                    else
                        asyncCallback(result[0] != 'no');
                });
        } else {
            Mainloop.idle_add(() => {
                asyncCallback(false);
                return false;
            });
        }
    }

    _loginManagerHibernate() {
        if (Prefs.getHibernateWorksCheckEnabled()) {
            this._hibernateStarted = new Date();
            GLib.timeout_add(GLib.PRIORITY_DEFAULT, HIBERNATE_CHECK_TIMEOUT,
                () => this._checkDidHibernate());
        }
        if (this._loginManager._proxy) {
            // systemd path
            this._loginManager._proxy.call("Hibernate",
                GLib.Variant.new('(b)', [true]),
                Gio.DBusCallFlags.NONE,
                -1, null, null);
        } else {
            // Can't do in ConsoleKit
            this._loginManager.emit('prepare-for-sleep', true);
            this._loginManager.emit('prepare-for-sleep', false);
        }
    }

    _loginManagerCanHybridSleep(asyncCallback) {
        if (this._loginManager._proxy) {
            // systemd path
            this._loginManager._proxy.call("CanHybridSleep",
                null,
                Gio.DBusCallFlags.NONE,
                -1, null, function (proxy, asyncResult) {
                    let result, error;

                    try {
                        result = proxy.call_finish(asyncResult).deep_unpack();
                    } catch (e) {
                        error = e;
                    }

                    if (error)
                        asyncCallback(false);
                    else
                        asyncCallback(result[0] != 'no');
                });
        } else {
            Mainloop.idle_add(() => {
                asyncCallback(false);
                return false;
            });
        }
    }

    _loginManagerHybridSleep() {
        if (this._loginManager._proxy) {
            // systemd path
            this._loginManager._proxy.call("HybridSleep",
                GLib.Variant.new('(b)', [true]),
                Gio.DBusCallFlags.NONE,
                -1, null, null);
        } else {
            // Can't do in ConsoleKit
            this._loginManager.emit('prepare-for-sleep', true);
            this._loginManager.emit('prepare-for-sleep', false);
        }
    }
    _updateHaveHibernate() {
        this._loginManagerCanHibernate((result) => {
            log(`have hibernate ${result}`);
            this._haveHibernate = result;
            this._updateHibernate();
        });
    }

    _updateHibernate() {
        this._hibernateMenuItem.visible = this._haveHibernate && !Main.sessionMode.isLocked;
    }

    _updateHaveHybridSleep() {
        this._loginManagerCanHybridSleep((result) => {
            this._haveHybridSleep = result;
            this._updateHybridSleep();
        });
    }

    _updateHybridSleep() {
        this._hybridSleepMenuItem.visible = this._haveHybridSleep && !Main.sessionMode.isLocked;
    }

    _onHibernateClicked() {
        this.systemMenu._systemItem.menu.itemActivated();
        this._dialog = new ConfirmDialog.ConfirmDialog(ConfirmDialog.HibernateDialogContent);
        this._dialog.connect('ConfirmedHibernate', () => this._loginManagerHibernate());
        this._dialog.open();
    }

    _onHybridSleepClicked() {
        this.systemMenu._systemItem.menu.itemActivated();
        this._loginManagerHybridSleep();
    }

    _disableExtension() {
        let enabledExtensions = global.settings.get_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY);
        enabledExtensions.splice(enabledExtensions.indexOf(Me.uuid), 1);
        global.settings.set_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY, enabledExtensions);
    }

    _cancelDisableExtension(notAgain) {
        if (notAgain)
            Prefs.setHibernateWorksCheckEnabled(false);
    }

    _checkRequirements() {
        if (!LoginManager.haveSystemd()) {
            this._dialog = new ConfirmDialog.ConfirmDialog(ConfirmDialog.SystemdMissingDialogContent);
            this._dialog.connect('DisableExtension', this._disableExtension);
            this._dialog.open();
        }
    }

    _checkDidHibernate() {
        /* This function is called HIBERNATE_CHECK_TIMEOUT ms after
         * hibernate started. If it is successful, at that point the GS
         * process is already frozen; so when this function is actually
         * called, way more than HIBERNATE_CHECK_TIMEOUT ms are passed*/
        if (new Date() - this._hibernateStarted > HIBERNATE_CHECK_TIMEOUT + 5000) {
            // hibernate succeeded
            return;
        }
        // hibernate failed
        this._dialog = new ConfirmDialog.ConfirmDialog(ConfirmDialog.HibernateFailedDialogContent);
        this._dialog.connect('DisableExtension', this._disableExtension);
        this._dialog.connect('Cancel', this._cancelDisableExtension);
        this._dialog.open();
    }

    enable() {
        this._checkRequirements();
        this._loginManager = LoginManager.getLoginManager();
        this.systemMenu = Main.panel.statusArea.quickSettings._system;

        this._hibernateMenuItem = new PopupMenu.PopupMenuItem(__('Hibernate'));
        this._hibernateMenuItemId = this._hibernateMenuItem.connect('activate', () => this._onHibernateClicked());

        this._hybridSleepMenuItem = new PopupMenu.PopupMenuItem(__('Hybrid Sleep'));
        this._hybridSleepMenuItemId = this._hybridSleepMenuItem.connect('activate', () => this._onHybridSleepClicked());

        let afterSuspendPosition = this.systemMenu._systemItem.menu.numMenuItems - 5;

        this.systemMenu._systemItem.menu.addMenuItem(this._hybridSleepMenuItem, afterSuspendPosition);
        this.systemMenu._systemItem.menu.addMenuItem(this._hibernateMenuItem, afterSuspendPosition);

        this._menuOpenStateChangedId = this.systemMenu._systemItem.menu.connect('open-state-changed',
            (menu, open) => {
                if (!open)
                    return;
                this._updateHaveHibernate();
                this._updateHaveHybridSleep();
            });
    }

    disable() {
        if (this._menuOpenStateChangedId) {
            this.systemMenu._systemItem.menu.disconnect(this._menuOpenStateChangedId);
            this._menuOpenStateChangedId = 0;
        }

        if (this._hybridSleepMenuItemId) {
            this._hybridSleepMenuItem.disconnect(this._hybridSleepMenuItemId);
            this._hybridSleepMenuItemId = 0;
        }

        if (this._hibernateMenuItemId) {
            this._hibernateMenuItem.disconnect(this._hibernateMenuItemId);
            this._hibernateMenuItemId = 0;
        }

        if (this._hybridSleepMenuItem) {
            this._hybridSleepMenuItem.destroy();
            this._hybridSleepMenuItem = 0;
        }

        if (this._hibernateMenuItem) {
            this._hibernateMenuItem.destroy();
            this._hibernateMenuItem = 0;
        }
    }
}

let extension;
function init() {
    extension = new Extension();
}

function enable() {
    extension.enable();
}

function disable() {
    extension.disable();
}