// Initialize Java.
XPS('@passpet.org/java;1', XPI.IPasspetJava).wrappedJSObject.java = java;
var dummy = new java.math.BigInteger('0');

const passpet = XPS('@passpet.org/passpet;1', XPI.IPasspet);

function passpet_add() {
    window.openDialog('chrome://passpet/content/setup.xul', '',
                      'centerscreen,modal,resizable=no');
}

function passpet_about() {
    window.openDialog('chrome://passpet/content/about.xul', '',
                      'centerscreen,modal,resizable=no');
}

const PersonaView = function(parent, persona) {
    const MAX_DY = 40;
    const MIN_WIDTH = 40;

    // If there are any personas, don't show the "empty personas" button.
    dom.hide($('passpet-empty-button'));

    // Add a resize grip on the left.
    var leftGrip = dom.add(parent, 'toolbarseparator', 'passpet-leftgrip');
    dom.listen(leftGrip, 'mousedown',
               function(event) { startResize(event, false); });

    // Create the persona widgets.
    var personaButton = dom.create('toolbarbutton', 'passpet-button');
    dom.set(personaButton, 'context', 'passpet-menu');
    dom.listen(personaButton, 'command', personaClick);
    personaButton.oncontextmenu = prepareMenu;

    var box = dom.add(personaButton, 'hbox');
    var personaIcon = dom.add(box, 'image');
    dom.set(personaIcon, 'src', persona.icon);
    var statusIcon = dom.add(box, 'image');
    dom.hide(statusIcon);
    dom.append(parent, personaButton);

    // Create the site label widgets.
    var labelStack = dom.add(parent, 'stack');

    var labelTextbox = dom.add(labelStack, 'textbox');
    dom.set(labelTextbox, 'site', 'none');
    dom.set(labelTextbox, 'flex', '1');
    labelTextbox.width = persona.width;
    dom.listen(labelTextbox, 'input', labelInput);
    dom.listen(labelTextbox, 'focus', labelFocus);
    dom.listen(labelTextbox, 'mouseup', labelMouseUp);
    dom.listen(labelTextbox, 'keypress', labelKeypress);

    var labelOutline = dom.add(labelStack, 'box', 'passpet-textbox-outline');
    dom.hide(labelOutline);

    var labelAcceptButton = dom.add(parent, 'toolbarbutton');
    dom.set(labelAcceptButton, 'icon', 'accept');
    dom.listen(labelAcceptButton, 'command', labelAccept);
    labelAcceptButton.label = 'Accept';
    dom.tip(labelAcceptButton, 'Accept your changes to the label.');
    var labelCancelButton = dom.add(parent, 'toolbarbutton');
    dom.set(labelCancelButton, 'icon', 'cancel');
    dom.listen(labelCancelButton, 'command', labelCancel);
    labelCancelButton.label = 'Cancel';
    dom.tip(labelCancelButton, 'Cancel your changes to the label.');

    // Create the login widgets.
    var loginBox = dom.add(parent, 'hbox', 'passpet-login');
    dom.hide(loginBox);
    var loginPromptLabel = dom.add(
        loginBox, 'label', 'passpet-login-prompt');
    var loginNameSpan = dom.add(loginPromptLabel, 'span', 'passpet-name');
    dom.write(loginNameSpan, persona.name);
    dom.write(loginPromptLabel, ' wants your secret:');
    var loginTextbox = dom.add(
        loginBox, 'textbox', 'passpet-login-textbox');
    dom.listen(loginTextbox, 'keypress', loginKeypress);
    dom.set(loginTextbox, 'type', 'password');
    var loginAcceptButton = dom.add(loginBox, 'toolbarbutton');
    dom.set(loginAcceptButton, 'icon', 'accept');
    dom.listen(loginAcceptButton, 'command', loginAccept);
    loginAcceptButton.label = 'Accept';
    dom.tip(loginAcceptButton,
            'Awaken ' + persona.name + ' with this secret.');
    var loginCancelButton = dom.add(loginBox, 'toolbarbutton');
    dom.set(loginCancelButton, 'icon', 'cancel');
    dom.listen(loginCancelButton, 'command', loginCancel);
    loginCancelButton.label = 'Cancel';
    dom.tip(loginCancelButton,
            'Put ' + persona.name + ' back to sleep.');
    var loginAddressLabel = dom.add(
        loginBox, 'label', 'passpet-login-address');
    dom.write(loginAddressLabel, '(');
    var loginAddressSpan = dom.add(loginAddressLabel, 'span');
    dom.write(loginAddressSpan, persona.address);
    dom.write(loginAddressLabel, ')');

    // Add a resize grip on the right.
    var rightGrip = dom.add(parent, 'toolbarseparator', 'passpet-rightgrip');
    dom.listen(rightGrip, 'mousedown',
               function(event) { startResize(event, true); });

    // Handler for security change events (private member object).
    var listener = new function() {
        this.onSecurityChange = function(progress, request, state) {
            siteid = passpet.getSiteIdentifier(window.getBrowser().docShell);
            petname = persona.getPetname(siteid);
            updateWidgets(true);
        }
        this.onLocationChange = function() {}
        this.onProgressChange = function() {}
        this.onStateChange = function() {}
        this.onStatusChange = function() {}
    }
    window.getBrowser().addProgressListener(
        listener, XPI.nsIWebProgress.NOTIFY_SECURITY);

    // Instance variables.
    var siteid = null;
    var petname = null;
    var pendingLabels = {};
    var collidingSiteid = null;
    var enteringSecret = false;
    var processingLogin = false;

    updateWidgets(true);

    // -------------------------------------------------- appearance updates

    // Update the visibility of various toolbar item parts.
    function updateWidgets(textbox) {
        if (persona.awake && textbox) updateLabelTextbox();

        var editingLabel = false;
        if (enteringSecret) {
            dom.set(statusIcon, 'icon', 'request');
            dom.hide(labelTextbox, leftGrip, rightGrip);
            dom.show(statusIcon, loginBox);
            // hide everything to the right?
            dom.tip(personaButton,
                    persona.name + ' wants your master secret.');
            // flash_name()
        } else if (processingLogin) {
            dom.set(statusIcon, 'icon', 'busy');
            dom.hide(labelTextbox, leftGrip, rightGrip, loginBox);
            dom.show(statusIcon);
        } else {
            if (persona.awake) {
                dom.hide(loginBox, statusIcon);
                dom.show(labelTextbox, leftGrip, rightGrip);
                if (siteid in pendingLabels) editingLabel = true;
                if (siteid) {
                    dom.tip(personaButton,
                            'Click to have ' + persona.name +
                            ' fill in a password for you.');
                } else {
                    dom.tip(personaButton,
                            persona.name + ' the ' + persona.iconName);
                }
            } else {
                dom.set(statusIcon, 'icon', 'sleeping');
                dom.hide(labelTextbox, loginBox, leftGrip, rightGrip);
                dom.show(statusIcon);
                dom.tip(personaButton,
                        persona.name + ' the ' + persona.iconName +
                        ' is sleeping.  Click to wake up.');
            }
            // redisplay everything to the right?
        }
        if (editingLabel) {
            dom.show(labelAcceptButton, labelCancelButton);
        } else {
            dom.hide(labelAcceptButton, labelCancelButton);
        }
    }

    // Update the appearance of the site label textbox.
    function updateLabelTextbox() {
        print('updateLabelTextbox', siteid, siteid in pendingLabels);
        if (siteid == 'local') {
            // Setting disabled to true also eliminates the tooltip, so we
            // set readonly and use CSS to emulate a disabled appearance.
            labelTextbox.readonly = true;
            labelTextbox.value = 'local';
            dom.set(labelTextbox, 'site', 'local');
            dom.tip(labelTextbox, localize('local-tooltip'));
        } else if (siteid && persona.awake) {
            var labelText = siteid in pendingLabels ?
                            pendingLabels[siteid] : petname;
            var ssl = (siteid.charAt(0) != '/');
            dom.set(labelTextbox, 'ssl', ssl);
            labelTextbox.readonly = false;
            if (labelText) {
                print('named', labelText);
                labelTextbox.value = labelText;
                var focus = document.commandDispatcher.focusedElement;
                dom.set(labelTextbox, 'site', 'named');
                dom.tip(labelTextbox,
                        localize('named-tooltip' + (ssl ? '-ssl' : '')));
            } else {
                print('unnamed');
                labelTextbox.value = localize('unnamed');
                dom.set(labelTextbox, 'site', 'unnamed');
                dom.tip(labelTextbox,
                        localize('unnamed-tooltip' + (ssl ? '-ssl' : '')));
            }
        } else {
            print('none');
            labelTextbox.readonly = true;
            labelTextbox.value = '';
            dom.set(labelTextbox, 'site', 'none');
            dom.tip(labelTextbox, null);
        }
    }

    // -------------------------------------------------------- context menu

    function personaAwaken() {
        print('awaken', persona.pid);
        enteringSecret = true;
        updateWidgets(true);
        loginTextbox.value = '';
        loginTextbox.select();
        loginTextbox.focus();
    }

    function personaSleep() {
        print('sleep', persona.pid);
        persona.sleep();
        updateWidgets(true);
    }

    function personaRemove() {
        print('remove', persona.pid);
        alert('Are you sure?');
    }

    // Set up the menu just before it appears.
    function prepareMenu() {
        print('prepareMenu');
        $('passpet-menu-awaken').label =
            'Wake up ' + persona.name + ' the ' + persona.iconName;
        $('passpet-menu-sleep').label =
            'Put ' + persona.name + ' the ' + persona.iconName + ' to sleep';
        $('passpet-menu-remove').label =
            'Remove ' + persona.name + ' the ' + persona.iconName;
        if (persona.awake) {
            dom.hide($('passpet-menu-awaken'));
            dom.show($('passpet-menu-sleep'));
        } else {
            dom.show($('passpet-menu-awaken'));
            dom.hide($('passpet-menu-sleep'));
        }
        dom.listen($('passpet-menu-awaken'), 'command', personaAwaken);
        dom.listen($('passpet-menu-sleep'), 'command', personaSleep);
        dom.listen($('passpet-menu-remove'), 'command', personaRemove);
        dom.listen($('passpet-menu-create'), 'command', passpet_add);
        dom.listen($('passpet-menu-about'), 'command', passpet_about);
    }

    // -------------------------------------------------- transient messages

    function popMessage(text1, text2) {
        var tooltip = $('passpet-tooltip');
        var description1 = $('passpet-tooltip-description-1');
        var description2 = $('passpet-tooltip-description-2');
        dom.set(description1, 'value', text1);
        dom.set(description2, 'value', text2);
        (text2 ? dom.show : dom.hide)(description2);
        var box = personaButton.boxObject;
        var x = box.screenX, y = box.screenY, h = box.height;
        tooltip.showPopup(personaButton, x, y + h - 15, 'tooltip');
        dom.tip(personaButton, '');
        dom.listen(personaButton, 'mouseout', hideMessage);
        dom.listen(tooltip, 'popuphidden', updateWidgets);
    }

    function hideMessage(event) {
        var tooltip = $('passpet-tooltip');
        tooltip.hidePopup();
        dom.unlisten(personaButton, 'mouseout', hideMessage);
    }

    // ------------------------------------------------ label event handlers

    // Handle any change to the contents of the label text field.
    function labelInput(event) {
        if (siteid) {
            var labelText = labelTextbox.value;
            pendingLabels[siteid] = labelText;
            collidingSiteid = (labelText == petname) ?
                              null : persona.findPetname(labelText);
            print('labelText', labelText, 'collider', collidingSiteid);
            dom.set(labelAcceptButton, 'icon',
                    collidingSiteid ? 'accept-warning' : 'accept');
            dom.tip(labelAcceptButton, collidingSiteid ?
                    'Save the edited label.  It duplicates an existing label.' :
                    'Save the edited label.');
            updateWidgets(false);
        }
    }

    // When the site is "unknown", select the text box so that typing
    // in a petname replaces the word "unknown" instead of editing it.
    // TODO: just make the word "unknown" disappear when you click.
    function labelFocus(event) {
        print('labelFocus', siteid);
        if (!petname && !pendingLabels[siteid]) labelTextbox.select();
    }

    function labelMouseUp(event) {
        print('labelMouseUp', siteid);
        if (!petname && !pendingLabels[siteid]) labelTextbox.select();
    }

    function labelKeypress(event) {
        if (event.keyCode == 13) labelAccept(event);
        if (event.keyCode == 27) labelCancel(event);
    }

    function labelAccept(event) {
        var newPetname = pendingLabels[siteid];
        var url = window.content.document.location.href;
        var proceed = !collidingSiteid;

        if (collidingSiteid) {
            result = {};
            window.openDialog('chrome://passpet/content/collision.xul', '',
                              'centerscreen,modal,resizable=no',
                              newPetname, siteid, collidingSiteid, result);
            proceed = result.accepted;
        }

        if (proceed) {
            delete pendingLabels[siteid];
            persona.setPetname(siteid, newPetname);
        }
        updateWidgets(true);
    }

    function labelCancel(event) {
        delete pendingLabels[siteid];
        updateWidgets(true);
    }

    // ------------------------------------------------ login event handlers

    function loginCancel(event) {
        // clearTimeout(flash_name_timeout)
        loginTextbox.value = '';
        enteringSecret = false;
        updateWidgets(true);
    }

    function loginAccept(event) {
        // clearTimeout(flash_name_timeout)
        var secret = loginTextbox.value;
        loginTextbox.value = '';
        enteringSecret = false;
        processingLogin = true;
        updateWidgets(true);
        if (persona.initialized) {
            persona.awaken(secret, new Cont('loginAccept1',
                function(result) {
                    processingLogin = false;
                    updateWidgets(true);
                },
                function(error) {
                    processingLogin = false;
                    updateWidgets(true);
                    popMessage('The secret you entered is incorrect.');
                }
            ));
        } else {
            persona.initialize(secret, new Cont('loginAccept2',
                function(result) {
                    processingLogin = false;
                    updateWidgets(true);
                },
                function(error) {
                    processingLogin = false;
                    updateWidgets(true);
                    popMessage('The secret you entered is incorrect.');
                }
            ));
        }
    }

    function loginKeypress(event) {
        if (event.keyCode == 13) loginAccept(event);
        if (event.keyCode == 27) loginCancel(event);
    }

    // Handle a click on the Passpet persona button.
    function personaClick(event) {
        if (persona.awake) fillPassword();
        else {
            enteringSecret = true;
            updateWidgets(true);
            loginTextbox.value = '';
            loginTextbox.select();
            loginTextbox.focus();
        }
    }

    function hasType(type) { 
        return function(element) { return element.type == type; }
    }

    function isLoginForm(form) {
        return filter(form, hasType('text')).length == 1 &&
               filter(form, hasType('password')).length == 1;
    }

    function isPasswordField(element) {
        return element && element.nodeType == element.ELEMENT_NODE &&
               element.tagName == 'INPUT' && element.type == 'password';
    }

    function fillPassword() {
        if (!siteid) return;

        if (!petname) {
            popMessage('This is not a site you labelled.  If you intended',
                       'to log in here, look out: it might be an impostor!');
            return;
        }

        var page = window.getBrowser().contentDocument;
        print('fillPage for', page.title);
        var focus = document.commandDispatcher.focusedElement;
        var usernameField = null, passwordField = null;

        if (isPasswordField(focus)) {
            passwordField = focus;
        } else {
            var inputFields = page.getElementsByTagName('INPUT');
            var passwordFields = filter(inputFields, isPasswordField);
            if (passwordFields.length == 1) passwordField = passwordFields[0];
        }

        var loginForms = filter(page.forms, isLoginForm);
        if (passwordField) {
            for (var i = 0; i < loginForms.length; i++) {
                var form = loginForms[i];
                var formUsername = filter(form, hasType('text'))[0];
                var formPassword = filter(form, hasType('password'))[0];
                if (passwordField == formPassword) {
                    usernameField = formUsername;
                    break;
                }
            }
        } else {
            if (passwordFields.length == 0) {
                popMessage("I don't see any password fields on this page.");
                return;
            } else if (loginForms.length == 0) {
                popMessage("I don't see a login form on this page.",
                           'To fill a password field, click there first.');
                return;
            } else if (loginForms.length == 1) {
                var form = loginForms[0];
                usernameField = filter(form, hasType('text'))[0];
                passwordField = filter(form, hasType('password'))[0];
            } else {
                popMessage('I see multiple login forms on this page.',
                           'To fill a password field, click there first.');
                return;
            }
        }

        var username = persona.getUsername(siteid);
        if (usernameField && usernameField.value) {
            if (usernameField.value != username) {
                persona.setUsername(siteid, usernameField.value);
            }
        }

        function setOutline(elements, style, color, width) {
            for (var i = 0; i < elements.length; i++) {
                if (elements[i]) {
                    elements[i].style.outlineStyle = style;
                    elements[i].style.outlineColor = color;
                    elements[i].style.outlineWidth = width;
                }
            }
        }

        // Scroll the browser window to show the password field.
        passwordField.focus();
        passwordField.blur();

        // Make a visual association between the petname and the password.
        var box = labelTextbox.boxObject;
        labelOutline.style.left = '-2px';
        labelOutline.style.top = '0px';
        labelOutline.style.width = (box.width + 4) + 'px';
        labelOutline.style.height = (box.height + 4) + 'px';
        dom.show(labelOutline);
        setOutline([usernameField, passwordField], 'solid', '#f00', '2px');

        setTimeout(function() {
            if (usernameField) {
                usernameField.value = persona.getUsername(siteid);
            }
            passwordField.value = persona.getPassword(siteid);
            dom.hide(labelOutline);
            setOutline([usernameField, passwordField], 'none', '', '0');
            passwordField.focus();
            passwordField.select();
        }, 100);
    }

    // ------------------------------------------------------------ resizing

    function startResize(event, anchorLeft) {
        var startX = event.clientX;
        var startY = event.clientY;
        var startWidth = persona.width;
        window.addEventListener('mousemove', drag, true);
        window.addEventListener('mouseup', release, true);

        function drag(event) {
            var dx = event.clientX - startX;
            var dy = event.clientY - startY;
            if (dy < -MAX_DY || dy > MAX_DY) {
                release(event);
                labelTextbox.width = startWidth;
                persona.width = startWidth;
            } else {
                var newWidth = startWidth + (anchorLeft ? dx : -dx);
                if (newWidth > MIN_WIDTH) {
                    labelTextbox.width = newWidth;
                    persona.width = newWidth;
                }
            }
        }

        function release(event) {
            window.removeEventListener('mousemove', drag, true);
            window.removeEventListener('mouseup', release, true);
        }
    }

    var tick = 0, goal = null;

    // Advance the current animation one frame.
    function advance() {
        if (!goal) return
        tick++
        switch (goal) {
            case 'secret':
                $('passpet-secret-textbox').focus()
                switch (tick) {
                    case 1: fade(0.81, urlbar_box); break
                    case 2: fade(0.64, urlbar_box); break
                    case 3: fade(0.49, urlbar_box); break
                    case 4: fade(0.36, urlbar_box); break
                    case 5: fade(0.25, urlbar_box); break
                    case 6: fade(0.16, urlbar_box); break
                    case 7: fade(0.09, urlbar_box); break
                    case 8: fade(0.04, urlbar_box); break
                    case 9: display('none', urlbar_box)
                            fade(1, urlbar_box)
                            fade(0.1, secret_box)
                            secret_box.style.paddingLeft = '222px'
                            display('-moz-box', secret_box); break
                    case 10: secret_box.style.paddingLeft = '182px'
                             fade(0.2, secret_box); break
                    case 11: secret_box.style.paddingLeft = '142px'
                             fade(0.3, secret_box); break
                    case 12: secret_box.style.paddingLeft = '102px'
                             fade(0.4, secret_box); break
                    case 13: secret_box.style.paddingLeft = '62px'
                             fade(0.5, secret_box); break
                    case 14: secret_box.style.paddingLeft = '30px'
                             fade(0.6, secret_box); break
                    case 15: secret_box.style.paddingLeft = '14px'
                             fade(0.7, secret_box); break
                    case 16: secret_box.style.paddingLeft = '6px'
                             fade(0.8, secret_box); break
                    case 17: secret_box.style.paddingLeft = '2px'
                             fade(0.9, secret_box); break
                    case 18: secret_box.style.paddingLeft = '0px'
                             fade(1, secret_box)
                             mode = goal
                             goal = null
                             update_widgets()
                             break
                }
                break
        }
        setTimeout(advance, 30)
    }

    function animate(newgoal) {
        goal = newgoal
        tick = 0
        advance()
    }

    // Flashery.
    var flash_name_timeout = 0
    var flash_count = 0
    var flash_colors = ['#800000', '#804000', '#808000', '#008000',
                        '#008080', '#000080', '#800080']
    function flash_name() {
        function hex(n) {
            return '0123456789abcdef'[n >> 4] + '0123456789abcdef'[n & 0xf]
        }
        var ri = flash_count, gi = (ri + 15) % 45, bi = (gi + 15) % 45
        var r = (ri < 15) ? ri*17 : (bi < 15) ? 255-bi*17 : 0
        var g = (gi < 15) ? gi*17 : (ri < 15) ? 255-ri*17 : 0
        var b = (bi < 15) ? bi*17 : (gi < 15) ? 255-gi*17 : 0
        color = '#' + hex(r) + hex(g) + hex(b)
        $('passpet-name').style.color = color
        flash_count = (flash_count + 1) % 45
        flash_name_timeout = setTimeout(flash_name, 100)
    }
}

function passpet_init() {
    print('passpet_init');
    var parent = $('passpet-toolbaritem');
    var button = $('passpet-empty-button');
    if (parent != null) {
        dom.listen(button, 'click', passpet_add);
        dom.tip(button, 'Click to create a Passpet to manage your passwords.');
        var pids = passpet.pidList.split(' ');
        for (var i = 0; i < pids.length; i++) {
            if (pids[i].length) {
                var persona = passpet.getPersona(pids[i]);
                new PersonaView(parent, persona);
            }
        }
    }
}

window.addEventListener('load', passpet_init, false);
