// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const AccountsService = imports.gi.AccountsService;
const Clutter = imports.gi.Clutter;
const Gdm  = imports.gi.Gdm;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GnomeDesktop = imports.gi.GnomeDesktop;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Signals = imports.signals;
const Shell = imports.gi.Shell;
const St = imports.gi.St;

const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const Panel = imports.ui.panel;
const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener;
const UserMenu = imports.ui.userMenu;

const Batch = imports.gdm.batch;
const GdmUtil = imports.gdm.util;
const LoginDialog = imports.gdm.loginDialog;

// The timeout before going back automatically to the lock screen (in seconds)
const IDLE_TIMEOUT = 2 * 60;

function versionCompare(required, reference) {
    required = required.split('.');
    reference = reference.split('.');

    for (let i = 0; i < required.length; i++) {
        if (required[i] != reference[i])
            return required[i] < reference[i];
    }

    return true;
}

function isSupported() {
    try {
        let params = GLib.Variant.new('(ss)', ['org.gnome.DisplayManager.Manager', 'Version']);
        let result = Gio.DBus.system.call_sync('org.gnome.DisplayManager',
                                               '/org/gnome/DisplayManager/Manager',
                                               'org.freedesktop.DBus.Properties',
                                               'Get', params, null,
                                               Gio.DBusCallFlags.NONE,
                                               -1, null);

        let version = result.deep_unpack()[0].deep_unpack();
        return versionCompare('3.5.91', version);
    } catch(e) {
        return false;
    }
}

// A widget showing the user avatar and name
const UserWidget = new Lang.Class({
    Name: 'UserWidget',

    _init: function(user) {
        this._user = user;

        this.actor = new St.BoxLayout({ style_class: 'unlock-dialog-user-name-container',
                                        vertical: false });

        this._avatar = new UserMenu.UserAvatarWidget(user);
        this.actor.add(this._avatar.actor,
                       { x_fill: true, y_fill: true });

        this._label = new St.Label({ style_class: 'login-dialog-username' });
        this.actor.add(this._label,
                       { expand: true,
                         x_fill: true,
                         y_fill: false,
                         y_align: St.Align.MIDDLE });

        this._userLoadedId = this._user.connect('notify::is-loaded',
                                                Lang.bind(this, this._updateUser));
        this._userChangedId = this._user.connect('changed',
                                                 Lang.bind(this, this._updateUser));
        if (this._user.is_loaded)
            this._updateUser();
    },

    destroy: function() {
        if (this._userLoadedId != 0) {
            this._user.disconnect(this._userLoadedId);
            this._userLoadedId = 0;
        }

        if (this._userChangedId != 0) {
            this._user.disconnect(this._userChangedId);
            this._userChangedId = 0;
        }

        this.actor.destroy();
    },

    _updateUser: function() {
        if (this._user.is_loaded)
            this._label.text = this._user.get_real_name();
        else
            this._label.text = '';

        this._avatar.update();
    }
});

const UnlockDialog = new Lang.Class({
    Name: 'UnlockDialog',
    Extends: ModalDialog.ModalDialog,

    _init: function(parentActor) {
        this.parent({ shellReactive: true,
                      styleClass: 'login-dialog',
                      keybindingMode: Main.KeybindingMode.UNLOCK_SCREEN,
                      parentActor: parentActor
                    });

        this._userManager = AccountsService.UserManager.get_default();
        this._userName = GLib.get_user_name();
        this._user = this._userManager.get_user(this._userName);

        this._failCounter = 0;
        this._firstQuestion = true;

        this._greeterClient = new Gdm.Client();
        this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient, { reauthenticationOnly: true });

        this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion));
        this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage));
        this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
        this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed));
        this._userVerifier.connect('reset', Lang.bind(this, this._onReset));

        this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint));
        this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint));

        this._userWidget = new UserWidget(this._user);
        this.contentLayout.add_actor(this._userWidget.actor);

        this._promptLayout = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
                                                vertical: true });

        this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' });
        this._promptLayout.add(this._promptLabel,
                               { x_align: St.Align.START });

        this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
                                           can_focus: true });
        this._promptEntry.clutter_text.set_password_char('\u25cf');
        ShellEntry.addContextMenu(this._promptEntry, { isPassword: true });
        this.setInitialKeyFocus(this._promptEntry);
        this._promptEntry.clutter_text.connect('text-changed', Lang.bind(this, function() {
            this._updateOkButtonSensitivity(this._promptEntry.text.length > 0);
        }));

        this._promptLayout.add(this._promptEntry,
                               { expand: true,
                                 x_fill: true });

        this.contentLayout.add_actor(this._promptLayout);

        this._promptMessage = new St.Label({ visible: false });
        this.contentLayout.add(this._promptMessage, { x_fill: true });

        this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint' });
        this._promptLoginHint.hide();
        this.contentLayout.add_actor(this._promptLoginHint);

        this._workSpinner = new Panel.AnimatedIcon('process-working.svg', LoginDialog.WORK_SPINNER_ICON_SIZE);
        this._workSpinner.actor.opacity = 0;

        this.allowCancel = false;
        this.buttonLayout.visible = true;
        this.addButton({ label: _("Cancel"),
                         action: Lang.bind(this, this._escape),
                         key: Clutter.KEY_Escape },
                       { expand: true,
                         x_fill: false,
                         y_fill: false,
                         x_align: St.Align.START,
                         y_align: St.Align.MIDDLE });
        this.buttonLayout.add(this._workSpinner.actor,
                              { expand: false,
                                x_fill: false,
                                y_fill: false,
                                x_align: St.Align.END,
                                y_align: St.Align.MIDDLE });
        this._okButton = this.addButton({ label: _("Unlock"),
                                          action: Lang.bind(this, this._doUnlock),
                                          default: true },
                                        { expand: false,
                                          x_fill: false,
                                          y_fill: false,
                                          x_align: St.Align.END,
                                          y_align: St.Align.MIDDLE });

        let otherUserLabel = new St.Label({ text: _("Log in as another user"),
                                            style_class: 'login-dialog-not-listed-label' });
        this._otherUserButton = new St.Button({ style_class: 'login-dialog-not-listed-button',
                                                can_focus: true,
                                                child: otherUserLabel,
                                                reactive: true,
                                                x_align: St.Align.START,
                                                x_fill: true });
        this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked));
        this.dialogLayout.add(this._otherUserButton,
                              { x_align: St.Align.START,
                                x_fill: false });

        this._updateSensitivity(true);

        let batch = new Batch.Hold();
        this._userVerifier.begin(this._userName, batch);

        GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() {
            this.emit('loaded');
            return false;
        }));

        Main.ctrlAltTabManager.addGroup(this.dialogLayout, _("Unlock Window"), 'dialog-password-symbolic');

        this._idleMonitor = new GnomeDesktop.IdleMonitor();
        this._idleWatchId = this._idleMonitor.add_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape));
    },

    _updateSensitivity: function(sensitive) {
        this._promptEntry.reactive = sensitive;
        this._promptEntry.clutter_text.editable = sensitive;
        this._updateOkButtonSensitivity(sensitive && this._promptEntry.text.length > 0);
        this._otherUserButton.reactive = sensitive;
        this._otherUserButton.can_focus = sensitive;
    },

    _updateOkButtonSensitivity: function(sensitive) {
        this._okButton.reactive = sensitive;
        this._okButton.can_focus = sensitive;
    },

    _setWorking: function(working) {
        if (working) {
            this._workSpinner.play();
            Tweener.addTween(this._workSpinner.actor,
                             { opacity: 255,
                               delay: LoginDialog.WORK_SPINNER_ANIMATION_DELAY,
                               time: LoginDialog.WORK_SPINNER_ANIMATION_TIME,
                               transition: 'linear'
                             });
        } else {
            Tweener.addTween(this._workSpinner.actor,
                             { opacity: 0,
                               time: LoginDialog.WORK_SPINNER_ANIMATION_TIME,
                               transition: 'linear',
                               onCompleteScope: this,
                               onComplete: function() {
                                   this._workSpinner.stop();
                               }
                             });
        }
    },

    _showMessage: function(userVerifier, message, styleClass) {
        if (message) {
            this._promptMessage.text = message;
            this._promptMessage.styleClass = styleClass;
            GdmUtil.fadeInActor(this._promptMessage);
        } else {
            GdmUtil.fadeOutActor(this._promptMessage);
        }
    },

    _onAskQuestion: function(verifier, serviceName, question, passwordChar) {
        if (this._firstQuestion && this._firstQuestionAnswer) {
            this._userVerifier.answerQuery(serviceName, this._firstQuestionAnswer);
            this._firstQuestionAnswer = null;
            this._firstQuestion = false;
            return;
        }

        this._promptLabel.text = question;

        if (!this._firstQuestion)
            this._promptEntry.text = '';
        else
            this._firstQuestion = false;

        this._promptEntry.clutter_text.set_password_char(passwordChar);
        this._promptEntry.menu.isPassword = passwordChar != '';

        this._currentQuery = serviceName;
        this._updateSensitivity(true);
        this._setWorking(false);
    },

    _showLoginHint: function(verifier, message) {
        this._promptLoginHint.set_text(message)
        GdmUtil.fadeInActor(this._promptLoginHint);
    },

    _hideLoginHint: function() {
        GdmUtil.fadeOutActor(this._promptLoginHint);
    },

    _doUnlock: function() {
        if (this._firstQuestion) {
            // we haven't received a query yet, so stash the answer
            // and make ourself non-reactive
            // the actual reply to GDM will be sent as soon as asked
            this._firstQuestionAnswer = this._promptEntry.text;
            this._updateSensitivity(false);
            this._setWorking(true);
            return;
        }

        if (!this._currentQuery)
            return;

        let query = this._currentQuery;
        this._currentQuery = null;

        this._updateSensitivity(false);
        this._setWorking(true);

        this._userVerifier.answerQuery(query, this._promptEntry.text);
    },

    _onVerificationComplete: function() {
        this._userVerifier.clear();
        this.emit('unlocked');
    },

    _onReset: function() {
        this.emit('failed');
    },

    _onVerificationFailed: function() {
        this._currentQuery = null;
        this._firstQuestion = true;

        this._promptEntry.text = '';
        this._promptEntry.clutter_text.set_password_char('\u25cf');
        this._promptEntry.menu.isPassword = true;

        this._updateSensitivity(false);
        this._setWorking(false);
    },

    _escape: function() {
        if (this.allowCancel) {
            this._userVerifier.cancel();
            this.emit('failed');
        }
    },

    _otherUserClicked: function(button, event) {
        Gdm.goto_login_session_sync(null);

        this._userVerifier.cancel();
        this.emit('failed');
    },

    destroy: function() {
        this._userVerifier.clear();

        if (this._idleWatchId) {
            this._idleMonitor.remove_watch(this._idleWatchId);
            this._idleWatchId = 0;
        }

        this.parent();
    },

    cancel: function() {
        this._userVerifier.cancel(null);

        this.destroy();
    },
});