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

const { Clutter, GLib, GObject, Pango, Shell, St } = imports.gi;

const Animation = imports.ui.animation;
const AuthList = imports.gdm.authList;
const Batch = imports.gdm.batch;
const GdmUtil = imports.gdm.util;
const Params = imports.misc.params;
const ShellEntry = imports.ui.shellEntry;
const UserWidget = imports.ui.userWidget;
const Util = imports.misc.util;

var DEFAULT_BUTTON_WELL_ICON_SIZE = 16;
var DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1000;
var DEFAULT_BUTTON_WELL_ANIMATION_TIME = 300;

var MESSAGE_FADE_OUT_ANIMATION_TIME = 500;

var AuthPromptMode = {
    UNLOCK_ONLY: 0,
    UNLOCK_OR_LOG_IN: 1,
};

var AuthPromptStatus = {
    NOT_VERIFYING: 0,
    VERIFYING: 1,
    VERIFICATION_FAILED: 2,
    VERIFICATION_SUCCEEDED: 3,
    VERIFICATION_CANCELLED: 4,
    VERIFICATION_IN_PROGRESS: 5,
};

var BeginRequestType = {
    PROVIDE_USERNAME: 0,
    DONT_PROVIDE_USERNAME: 1,
    REUSE_USERNAME: 2,
};

var PinUnlockKeyboard = GObject.registerClass({
    Signals: {
        'char': { param_types: [GObject.TYPE_STRING] },
        'delete-last': {},
        'delete-all': {},
        'show-full-keyboard': {},
    },
}, class PinUnlockKeyboard extends St.Widget {
    _init(gdmClient, mode) {
        super._init({
            style_class: 'pin-unlock-keyboard',
        });

        this.layout_manager = new Clutter.GridLayout({
            orientation: Clutter.Orientation.HORIZONTAL,
            column_homogeneous: true,
            row_homogeneous: true,
        });

        this._createGrid();
    }

    _createGrid() {
        let number = 1;

        for (let row = 0; row < 4; row++) {
            for (let col = 0; col < 3; col++) {
                let label;

                const button = new St.Button({
                    style_class: 'pin-button-container',
                    x_expand: true,
                    y_expand: true,
                });

                const isSpecialKey = number === 10 || number === 12;
                const numericKeyval = number === 11 ? 0 : number;

                if (isSpecialKey) {
                    label = new St.Icon({
                        x_align: Clutter.ActorAlign.CENTER,
                        y_align: Clutter.ActorAlign.CENTER,
                        icon_name: number === 10 ? 'input-keyboard-symbolic' : 'edit-clear-symbolic',
                        style_class: 'pin-button icon',
                    });

                    if (number === 10) {
                        button.connect('clicked',
                            () => this.emit('show-full-keyboard'));
                    } else {
                        button.connect('clicked',
                            () => this.emit('delete-last'));

                        const longPressGesture = new Clutter.LongPressGesture();
                        longPressGesture.connect('long-press-begin',
                            () => this.emit('delete-all'));

                        button.add_action(longPressGesture);
                    }
                } else {
                    label = new St.Label({
                        x_align: Clutter.ActorAlign.CENTER,
                        y_align: Clutter.ActorAlign.CENTER,
                        text: `${numericKeyval}`,
                        style_class: 'pin-button number',
                    });

                    label.clutter_text.x_align = Clutter.ActorAlign.CENTER;
                    label.clutter_text.y_align = Clutter.ActorAlign.CENTER;

                    button.connect('clicked',
                        () => this.emit('char', `${numericKeyval}`));
                }

                button.child = label;

                this.layout_manager.attach(button, col, row, 1, 1);

                number++;
            }
        }
    }
});

var PinEntryIndicator = GObject.registerClass({
}, class PinEntryIndicator extends St.BoxLayout {
    _init(nDigits) {
        super._init({
            style_class: 'pin-entry-indicator',
            vertical: false,
        });

        this._totalNDigits = nDigits;

        for (let i = 0; i < nDigits; i++) {
            const digitWidget = new St.Widget({
                style_class: 'digit',
            });

            this.add_child(digitWidget);
        }
    }

    setActiveDigits(nDigits) {
        for (let i = 0; i < this._totalNDigits; i++) {
            const digit = this.get_child_at_index(i);

            if (i < nDigits)
                digit.add_style_class_name('filled');
            else
                digit.remove_style_class_name('filled');
        }
    }
});

var AuthPrompt = GObject.registerClass({
    Properties: {
        'user-info-visible': GObject.ParamSpec.boolean(
            'user-info-visible', 'user-info-visible', 'user-info-visible',
            GObject.ParamFlags.READWRITE,
            true),
    },
    Signals: {
        'cancelled': {},
        'failed': {},
        'next': {},
        'prompted': {},
        'reset': { param_types: [GObject.TYPE_UINT] },
    },
}, class AuthPrompt extends St.BoxLayout {
    _init(gdmClient, mode) {
        super._init({
            style_class: 'login-dialog-prompt-layout',
            vertical: true,
            x_expand: true,
            x_align: Clutter.ActorAlign.CENTER,
            reactive: true,
        });

        this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;

        this._gdmClient = gdmClient;
        this._mode = mode;
        this._defaultButtonWellActor = null;
        this._cancelledRetries = 0;
        this._userInfoVisible = true;
        this._mayShowEntry = true;

        let reauthenticationOnly;
        if (this._mode == AuthPromptMode.UNLOCK_ONLY)
            reauthenticationOnly = true;
        else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN)
            reauthenticationOnly = false;

        this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly });

        this._userVerifier.connect('ask-question', this._onAskQuestion.bind(this));
        this._userVerifier.connect('show-message', this._onShowMessage.bind(this));
        this._userVerifier.connect('show-choice-list', this._onShowChoiceList.bind(this));
        this._userVerifier.connect('verification-failed', this._onVerificationFailed.bind(this));
        this._userVerifier.connect('verification-complete', this._onVerificationComplete.bind(this));
        this._userVerifier.connect('reset', this._onReset.bind(this));
        this._userVerifier.connect('smartcard-status-changed', this._onSmartcardStatusChanged.bind(this));
        this._userVerifier.connect('credential-manager-authenticated', this._onCredentialManagerAuthenticated.bind(this));
        this.smartcardDetected = this._userVerifier.smartcardDetected;

        this.connect('destroy', this._onDestroy.bind(this));

        this._userWell = new St.Bin({
            visible: this._userInfoVisible,
            x_expand: true,
            y_expand: true,
        });
        this.add_child(this._userWell);

        this._hasCancelButton = this._mode === AuthPromptMode.UNLOCK_OR_LOG_IN;

        this._initInputRow();

       // let capsLockPlaceholder = new St.Label();
      //  this.add_child(capsLockPlaceholder);

        this._capsLockWarningLabel = new ShellEntry.CapsLockWarning({
            x_expand: true,
            x_align: Clutter.ActorAlign.CENTER,
            visible: false,
        });
        this.add_child(this._capsLockWarningLabel);

      /*  this._capsLockWarningLabel.bind_property('visible',
            capsLockPlaceholder, 'visible',
            GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN);
*/
        this._message = new St.Label({
            opacity: 0,
            styleClass: 'login-dialog-message',
            y_align: Clutter.ActorAlign.START,
            x_align: Clutter.ActorAlign.CENTER,
        });
     //   this._message.clutter_text.line_wrap = true;
        this._message.clutter_text.ellipsize = Pango.EllipsizeMode.END;
        this.add_child(this._message);
    }

    _onDestroy() {
        this._inactiveEntry.destroy();
        this._inactiveEntry = null;
        this._userVerifier.destroy();
        this._userVerifier = null;
    }

    vfunc_key_press_event(keyPressEvent) {
        if (keyPressEvent.keyval == Clutter.KEY_Escape)
            this.cancel();
        return super.vfunc_key_press_event(keyPressEvent);
    }

    _initInputRow() {
        this._mainBox = new St.BoxLayout({
            style_class: 'login-dialog-button-box',
            vertical: false,
        });
        this.add_child(this._mainBox);

        this.cancelButton = new St.Button({
            style_class: 'login-dialog-button cancel-button',
            accessible_name: _('Cancel'),
            button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
            reactive: this._hasCancelButton,
            can_focus: this._hasCancelButton,
            x_align: Clutter.ActorAlign.START,
            y_align: Clutter.ActorAlign.CENTER,
            icon_name: 'go-previous-symbolic',
        });
        if (this._hasCancelButton)
            this.cancelButton.connect('clicked', () => this.cancel());
        else
            this.cancelButton.opacity = 0;
        this._mainBox.add_child(this.cancelButton);

        this._authList = new AuthList.AuthList();
        this._authList.set({
            visible: false,
        });
        this._authList.connect('activate', (list, key) => {
            this._authList.reactive = false;
            this._authList.ease({
                opacity: 0,
                duration: MESSAGE_FADE_OUT_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => {
                    this._authList.clear();
                    this._authList.hide();
                    this._userVerifier.selectChoice(this._queryingService, key);
                },
            });
        });
        this._mainBox.add_child(this._authList);

        let entryParams = {
            style_class: 'login-dialog-prompt-entry',
            can_focus: true,
            x_expand: true,
        };

        this._entry = null;

        this._textEntry = new St.Entry(entryParams);
        ShellEntry.addContextMenu(this._textEntry, { actionMode: Shell.ActionMode.NONE });

        this._passwordEntry = new St.PasswordEntry(entryParams);
        ShellEntry.addContextMenu(this._passwordEntry, { actionMode: Shell.ActionMode.NONE });

        this._entry = this._passwordEntry;
        this._mainBox.add_child(this._entry);
        if (this._entry.visible)
            this._entry.grab_key_focus();
        this._inactiveEntry = this._textEntry;

        this._timedLoginIndicator = new St.Bin({
            style_class: 'login-dialog-timed-login-indicator',
            scale_x: 0,
        });

        this.add_child(this._timedLoginIndicator);

        [this._textEntry, this._passwordEntry].forEach(entry => {
            entry.clutter_text.connect('text-changed', () => {
                if (!this._userVerifier.hasPendingMessages)
                    this._fadeOutMessage();
            });

            entry.clutter_text.connect('activate', () => {
                let shouldSpin = entry === this._passwordEntry;
                if (entry.reactive)
                    this._activateNext(shouldSpin);
            });
        });

        this._defaultButtonWell = new St.Widget({
            layout_manager: new Clutter.BinLayout(),
            x_align: Clutter.ActorAlign.END,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this._defaultButtonWell.add_constraint(new Clutter.BindConstraint({
            source: this.cancelButton,
            coordinate: Clutter.BindCoordinate.WIDTH,
        }));
        this._mainBox.add_child(this._defaultButtonWell);

        this._spinner = new Animation.Spinner(DEFAULT_BUTTON_WELL_ICON_SIZE);
        this._defaultButtonWell.add_child(this._spinner);
    }

    showTimedLoginIndicator(time) {
        let hold = new Batch.Hold();

        this.hideTimedLoginIndicator();

        const startTime = GLib.get_monotonic_time();

        this._timedLoginTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 33,
            () => {
                const currentTime = GLib.get_monotonic_time();
                const elapsedTime = (currentTime - startTime) / GLib.USEC_PER_SEC;
                this._timedLoginIndicator.scale_x = elapsedTime / time;
                if (elapsedTime >= time) {
                    this._timedLoginTimeoutId = 0;
                    hold.release();
                    return GLib.SOURCE_REMOVE;
                }

                return GLib.SOURCE_CONTINUE;
            });

        GLib.Source.set_name_by_id(this._timedLoginTimeoutId, '[gnome-shell] this._timedLoginTimeoutId');

        return hold;
    }

    hideTimedLoginIndicator() {
        if (this._timedLoginTimeoutId) {
            GLib.source_remove(this._timedLoginTimeoutId);
            this._timedLoginTimeoutId = 0;
        }
        this._timedLoginIndicator.scale_x = 0.;
    }

    _activateNext(shouldSpin) {
        this.verificationStatus = AuthPromptStatus.VERIFICATION_IN_PROGRESS;
        this.updateSensitivity(false);

        if (shouldSpin)
            this.startSpinning();

        if (this._queryingService)
            this._userVerifier.answerQuery(this._queryingService, this._entry.text);
        else
            this._preemptiveAnswer = this._entry.text;

        this.emit('next');
    }

    _updateEntry(secret) {
        if (secret && this._entry !== this._passwordEntry) {
            this._mainBox.replace_child(this._entry, this._passwordEntry);
            this._entry = this._passwordEntry;
            this._inactiveEntry = this._textEntry;
        } else if (!secret && this._entry !== this._textEntry) {
            this._mainBox.replace_child(this._entry, this._textEntry);
            this._entry = this._textEntry;
            this._inactiveEntry = this._passwordEntry;
        }
        this._entry.visible = false;
        this._capsLockWarningLabel.visible = secret;
    }

    _onAskQuestion(verifier, serviceName, question, secret) {
        if (this._queryingService)
            this.clear();

        this._queryingService = serviceName;
        if (this._preemptiveAnswer) {
            this._userVerifier.answerQuery(this._queryingService, this._preemptiveAnswer);
            this._preemptiveAnswer = null;
            return;
        }

        this._updateEntry(secret);

        // Hack: The question string comes directly from PAM, if it's "Password:"
        // we replace it with our own to allow localization, if it's something
        // else we remove the last colon and any trailing or leading spaces.
        if (question === 'Password:' || question === 'Password: ')
            this.setQuestion(_('Password'));
        else
            this.setQuestion(question.replace(/[:：] *$/, '').trim());

        this.updateSensitivity(true);
        this.emit('prompted');
    }

    _onShowChoiceList(userVerifier, serviceName, promptMessage, choiceList) {
        if (this._queryingService)
            this.clear();

        this._queryingService = serviceName;

        if (this._preemptiveAnswer)
            this._preemptiveAnswer = null;

        this.setChoiceList(promptMessage, choiceList);
        this.updateSensitivity(true);
        this.emit('prompted');
    }

    _onCredentialManagerAuthenticated() {
        if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED)
            this.reset();
    }

    _onSmartcardStatusChanged() {
        this.smartcardDetected = this._userVerifier.smartcardDetected;

        // Most of the time we want to reset if the user inserts or removes
        // a smartcard. Smartcard insertion "preempts" what the user was
        // doing, and smartcard removal aborts the preemption.
        // The exceptions are: 1) Don't reset on smartcard insertion if we're already verifying
        //                        with a smartcard
        //                     2) Don't reset if we've already succeeded at verification and
        //                        the user is getting logged in.
        if (this._userVerifier.serviceIsDefault(GdmUtil.SMARTCARD_SERVICE_NAME) &&
            this.verificationStatus == AuthPromptStatus.VERIFYING &&
            this.smartcardDetected)
            return;

        if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED)
            this.reset();
    }

    _onShowMessage(_userVerifier, serviceName, message, type) {
        let wiggleParameters = {duration: 0};

        if (type === GdmUtil.MessageType.ERROR &&
            this._userVerifier.serviceIsFingerprint(serviceName)) {
            // TODO: Use Await for wiggle to be over before unfreezing the user verifier queue
            wiggleParameters = {
                duration: 65,
                wiggleCount: 3,
            };
            this._userVerifier.increaseCurrentMessageTimeout(
                wiggleParameters.duration * (wiggleParameters.wiggleCount + 2));
        }

        this.setMessage(message, type, wiggleParameters);
        this.emit('prompted');
    }

    _onVerificationFailed(userVerifier, serviceName, canRetry) {
        const wasQueryingService = this._queryingService === serviceName;

        if (wasQueryingService) {
            this._queryingService = null;
            this.clear();
        }

        this.updateSensitivity(canRetry);
        this.setActorInDefaultButtonWell(null);

        if (!canRetry)
            this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED;

        if (wasQueryingService) {
            this.emit('failed');
            Util.wiggle(this._entry);
}
    }

    _onVerificationComplete() {
        this.setActorInDefaultButtonWell(null);
        this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED;
        this.cancelButton.reactive = false;
        this.cancelButton.can_focus = false;
    }

    _onReset() {
        this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
        this.reset();
    }

    setActorInDefaultButtonWell(actor, animate) {
        if (!this._defaultButtonWellActor &&
            !actor)
            return;

        let oldActor = this._defaultButtonWellActor;

        if (oldActor)
            oldActor.remove_all_transitions();

        let wasSpinner;
        if (oldActor == this._spinner)
            wasSpinner = true;
        else
            wasSpinner = false;

        let isSpinner;
        if (actor == this._spinner)
            isSpinner = true;
        else
            isSpinner = false;

        if (this._defaultButtonWellActor != actor && oldActor) {
            if (!animate) {
                oldActor.opacity = 0;

                if (wasSpinner) {
                    if (this._spinner)
                        this._spinner.stop();
                }
            } else {
                oldActor.ease({
                    opacity: 0,
                    duration: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
                    delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
                    mode: Clutter.AnimationMode.LINEAR,
                    onComplete: () => {
                        if (wasSpinner) {
                            if (this._spinner)
                                this._spinner.stop();
                        }
                    },
                });
            }
        }

        if (actor) {
            if (isSpinner)
                this._spinner.play();

            if (!animate) {
                actor.opacity = 255;
            } else {
                actor.ease({
                    opacity: 255,
                    duration: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
                    delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
                    mode: Clutter.AnimationMode.LINEAR,
                });
            }
        }

        this._defaultButtonWellActor = actor;
    }

    startSpinning() {
        this.setActorInDefaultButtonWell(this._spinner, true);
    }

    stopSpinning() {
        this.setActorInDefaultButtonWell(null, false);
    }

    clear() {
        this._entry.text = '';
        this.stopSpinning();
        this._authList.clear();
        this._authList.hide();
    }

    setQuestion(question) {
        this._entry.hint_text = question;

        this._authList.hide();
        this._entryShouldShow = true;
        if (this._mayShowEntry)
            this._entry.show();
        if (this._entry.visible)
            this._entry.grab_key_focus();
    }

    _fadeInChoiceList() {
        this._authList.set({
            opacity: 0,
            visible: true,
            reactive: false,
        });
        this._authList.ease({
            opacity: 255,
            duration: MESSAGE_FADE_OUT_ANIMATION_TIME,
            transition: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => (this._authList.reactive = true),
        });
    }

    setChoiceList(promptMessage, choiceList) {
        this._authList.clear();
        this._authList.label.text = promptMessage;
        for (let key in choiceList) {
            let text = choiceList[key];
            this._authList.addItem(key, text);
        }

        this._entryShouldShow = false;
        this._entry.hide();
        if (this._message.text === '')
            this._message.hide();
        this._fadeInChoiceList();
    }

    getAnswer() {
        let text;

        if (this._preemptiveAnswer) {
            text = this._preemptiveAnswer;
            this._preemptiveAnswer = null;
        } else {
            text = this._entry.get_text();
        }

        return text;
    }

    _fadeOutMessage() {
        if (this._message.opacity == 0)
            return;
        this._message.remove_all_transitions();
        this._message.ease({
            opacity: 0,
            duration: MESSAGE_FADE_OUT_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

    setMessage(message, type, wiggleParameters = {duration: 0}) {
        if (type == GdmUtil.MessageType.ERROR)
            this._message.add_style_class_name('login-dialog-message-warning');
        else
            this._message.remove_style_class_name('login-dialog-message-warning');

        if (type == GdmUtil.MessageType.HINT)
            this._message.add_style_class_name('login-dialog-message-hint');
        else
            this._message.remove_style_class_name('login-dialog-message-hint');

        this._message.show();
        if (message) {
            this._message.remove_all_transitions();
            this._message.text = message;
            this._message.opacity = 255;
        } else {
            this._message.opacity = 0;
        }

        Util.wiggle(this._message, wiggleParameters);
    }

    updateSensitivity(sensitive) {
        if (this._entry.reactive === sensitive)
            return;

        this._entry.reactive = sensitive;

        if (sensitive) {
            if (this._entry.visible)
                this._entry.grab_key_focus();
        } else {
            this.grab_key_focus();

            if (this._entry === this._passwordEntry)
                this._entry.password_visible = false;
        }
    }

    vfunc_hide() {
        this.setActorInDefaultButtonWell(null, true);
        super.vfunc_hide();
        this._message.opacity = 0;

        this.setUser(null);

        this.updateSensitivity(true);
        this._entry.set_text('');
    }

    setUser(user) {
        let oldChild = this._userWell.get_child();
        if (oldChild)
            oldChild.destroy();

        let userWidget = new UserWidget.UserWidget(user, Clutter.Orientation.VERTICAL);
        this._userWell.set_child(userWidget);

        if (!user)
            this._updateEntry(false);
    }

    reset() {
        let oldStatus = this.verificationStatus;
        this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
        this.cancelButton.reactive = this._hasCancelButton;
        this.cancelButton.can_focus = this._hasCancelButton;
        this._preemptiveAnswer = null;

        if (this._userVerifier)
            this._userVerifier.cancel();

        this._queryingService = null;
        this.clear();
        this._message.opacity = 0;
        this.setUser(null);
        this._updateEntry(true);
        this.stopSpinning();

        if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED)
            this.emit('failed');
        else if (oldStatus === AuthPromptStatus.VERIFICATION_CANCELLED)
            this.emit('cancelled');

        let beginRequestType;

        if (this._mode == AuthPromptMode.UNLOCK_ONLY) {
            // The user is constant at the unlock screen, so it will immediately
            // respond to the request with the username
            if (oldStatus === AuthPromptStatus.VERIFICATION_CANCELLED)
                return;
            beginRequestType = BeginRequestType.PROVIDE_USERNAME;
        } else if (this._userVerifier.foregroundServiceDeterminesUsername()) {
            // We don't need to know the username if the user preempted the login screen
            // with a smartcard or with preauthenticated oVirt credentials
            beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME;
        } else if (oldStatus === AuthPromptStatus.VERIFICATION_IN_PROGRESS) {
            // We're going back to retry with current user
            beginRequestType = BeginRequestType.REUSE_USERNAME;
        } else {
            // In all other cases, we should get the username up front.
            beginRequestType = BeginRequestType.PROVIDE_USERNAME;
        }

        this.emit('reset', beginRequestType);
    }

    addCharacter(unichar) {
        if (this._entry.visible)
            this._entry.grab_key_focus();
        this._entry.clutter_text.insert_unichar(unichar);
    }

    deleteLastCharacter() {
        const len = this._entry.clutter_text.buffer.get_length();
        this._entry.clutter_text.delete_text(len - 1, len);
    }

    begin(params) {
        params = Params.parse(params, {
            userName: null,
            hold: null,
        });

        this.updateSensitivity(false);

        let hold = params.hold;
        if (!hold)
            hold = new Batch.Hold();

        this._userVerifier.begin(params.userName, hold);
        this.verificationStatus = AuthPromptStatus.VERIFYING;
    }

    finish(onComplete) {
        if (!this._userVerifier.hasPendingMessages) {
            this._userVerifier.clear();
            onComplete();
            return;
        }

        let signalId = this._userVerifier.connect('no-more-messages', () => {
            this._userVerifier.disconnect(signalId);
            this._userVerifier.clear();
            onComplete();
        });
    }

    cancel() {
        if (this.verificationStatus == AuthPromptStatus.VERIFICATION_SUCCEEDED)
            return;

        if (this.verificationStatus === AuthPromptStatus.VERIFICATION_IN_PROGRESS) {
            this._cancelledRetries++;
            if (this._cancelledRetries > this._userVerifier.allowedFailures)
                this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED;
        } else {
            this.verificationStatus = AuthPromptStatus.VERIFICATION_CANCELLED;
        }

        this.reset();
    }

    set user_info_visible(visible) {
        if (this._userInfoVisible === visible)
            return;

        this._userInfoVisible = visible;
        this._userWell.visible = this._userInfoVisible;

        this.notify('user-info-visible');
    }

    set mayShowEntry(mayShow) {
        this._mayShowEntry = mayShow;

        if (mayShow && this._entryShouldShow) {
            this._entry.show();
            this._entry.grab_key_focus();
this._mainBox.show();
        }

        if (!mayShow) {
this._mainBox.hide();
            this._entry.hide();
}
    }
});
