/**
 * jQuery Mobile UI application logic.
 *
 * @author     Michael Slusarz <slusarz@horde.org>
 * @copyright  2005-2015 Horde LLC
 * @license    GPL-2 (http://www.horde.org/licenses/gpl)
 */
var ImpMobile = {

    // Vars used and defaulting to null/false:
    //
    // /* Attachment data for the current message. */
    // atc,
    //
    // /* BUID of the currently displayed message. */
    // buid,
    //
    // /* The current list of compose attachments. */
    // composeatc,
    //
    // /* The current compose type. */
    // composetype,
    //
    // /* Flag information. */
    // flags,
    //
    // /* Has the folders list been loaded? */
    // foldersLoaded,
    //
    // /* Header data for the current message. */
    // headers,
    //
    // /* Is the current message from a list? */
    // listmsg,
    //
    // /* The current mailbox. */
    // mailbox,
    //
    // /* Cache ID for the current mailbox view. */
    // mailboxCache,
    //
    // /* The current message data. */
    // message,
    //
    // /* Current handle for the refresh interval action. */
    // refresh,
    //
    // /* Current row UID of the displayed message. */
    // rowid,
    //
    // /* Search parameters for the viewPort Ajax request. */
    // search,

    // Mailbox data cache.
    cache: {},

    // The scroll location of the current mailbox.
    mailboxTop: 0,

    // Rows per mailbox slice.
    mbox_slice: 30,

    // 'INBOX' base64url encoded
    INBOX: 'SU5CT1g',


    /**
     * Event handler for the pagebeforechange event that implements loading of
     * deep-linked pages.
     *
     * @param object e     Event object.
     * @param object data  Event data.
     */
    toPage: function(e, data)
    {
        var tmp, view = data.options.parsedUrl.view;

        if ($.type(data.toPage) !== "string") {
            switch (HordeMobile.currentPage()) {
            case 'folders':
                ImpMobile.mailboxTop = 0;
                break;

            case 'mailbox':
                ImpMobile.mailboxTop = $(window).scrollTop();
                break;
            }

            return;
        }

        switch (view) {
        case 'compose':
            $('#imp-compose-to-addr,#imp-compose-cc-addr,#imp-redirect-to-addr').empty();
            if (!IMP.conf.disable_compose) {
                $('#imp-compose-cache,#imp-compose-hmac').val('');
                ImpMobile.compose(data);
            }
            e.preventDefault();
            break;

        case 'compose-attach':
            ImpMobile.composeAttachRefresh();
            $('#imp-compose-attach').popup('open');
            e.preventDefault();
            break;

        case 'compose-cancel':
        case 'compose-discard':
            HordeMobile.doAction('cancelCompose', {
                discard: ~~(view == 'compose-discard'),
                imp_compose: $('#imp-compose-cache').val()
            });
            ImpMobile.closeCompose();
            e.preventDefault();
            break;

        case 'compose-delete-addr':
            data.options.link.next().remove();
            data.options.link.remove();
            e.preventDefault();
            break;

        case 'compose-draft':
        case 'compose-submit':
            ImpMobile.uniqueSubmit(
                (view == 'compose-draft')
                    ? 'saveDraft'
                    : ($('#imp-compose-form').is(':hidden') ? 'redirectMessage' : 'smartmobileSendMessage')
            );
            e.preventDefault();
            break;

        case 'copymove':
            if (IMP.conf.allow_folders) {
                ImpMobile.copymove(data);
            }
            e.preventDefault();
            break;

        case 'copymove-submit':
            ImpMobile.copymoveSelected();
            e.preventDefault();
            break;

        case 'folders-refresh':
            $('#folders :jqmData(role=footer) a[href$="refresh"]').removeClass($.mobile.activeBtnClass).blur();
            e.preventDefault();
            // Fall-through

        case 'folders':
            if (ImpMobile.foldersLoaded) {
                HordeMobile.doAction('poll', {
                    poll: JSON.stringify([])
                });
            }
            break;

        case 'folders-showall':
        case 'folders-showpoll':
            $('#folders :jqmData(role=footer) a[href*="folders-show"]').toggle();
            ImpMobile.loadFolders();
            e.preventDefault();
            break;

        case 'mailbox':
            ImpMobile.toMailbox(data);
            e.preventDefault();
            break;

        case 'mailbox-delete':
            ImpMobile.deleteMessage(
                (data.options.data || data.options.link).jqmData('buid')
            );
            e.preventDefault();
            break;

        case 'mailbox-innocent':
        case 'mailbox-spam':
            ImpMobile.reportSpam(
                view.match(/spam$/) ? 'spam' : 'innocent',
                (data.options.data || data.options.link).jqmData('buid')
            );
            e.preventDefault();
            break;

        case 'mailbox-more':
            ImpMobile.cache[ImpMobile.mailbox].slice += ImpMobile.mbox_slice;
            $.mobile.changePage(HordeMobile.createUrl('mailbox', {
                mbox: ImpMobile.mailbox
            }), {
                data: { force: true }
            });
            e.preventDefault();
            break;

        case 'mailbox-refresh':
            ImpMobile.refreshMailbox();
            e.preventDefault();
            $('#mailbox :jqmData(role=footer) a[href$="refresh"]').removeClass($.mobile.activeBtnClass).blur();
            break;

        case 'message':
            ImpMobile.toMessage(data);
            e.preventDefault();
            break;

        case 'message-delete':
            ImpMobile.deleteMessage(ImpMobile.buid);
            $.mobile.changePage(HordeMobile.createUrl('mailbox', {
                mbox: ImpMobile.mailbox
            }), {
                data: { noajax: true }
            });
            e.preventDefault();
            return;

        case 'message-forward':
            $.mobile.changePage(HordeMobile.createUrl('compose', {
                buid: ImpMobile.buid,
                mbox: ImpMobile.mailbox,
                type: 'forward_auto'
            }));
            e.preventDefault();
            break;

        case 'message-next':
        case 'message-prev':
            ImpMobile.navigateMessage(view.match(/next$/) ? 1 : -1);
            e.preventDefault();
            break;

        case 'message-redirect':
            $.mobile.changePage(HordeMobile.createUrl('compose', {
                buid: ImpMobile.buid,
                mbox: ImpMobile.mailbox,
                type: 'forward_redirect'
            }));
            e.preventDefault();
            break;

        case 'message-reply-all':
        case 'message-reply-auto':
        case 'message-reply-list':
        case 'message-reply-sender':
            tmp = 'reply';
            switch (view) {
            case 'message-reply-all':
                tmp += '_all';
                break;

            case 'message-reply-auto':
                tmp += '_auto';
                break;

            case 'message-reply-list':
                tmp += '_list';
                break;
            }

            $.mobile.changePage(HordeMobile.createUrl('compose', {
                buid: ImpMobile.buid,
                mbox: ImpMobile.mailbox,
                type: tmp
            }));
            e.preventDefault();
            break;

        case 'message-innocent':
        case 'message-spam':
            $(view.match(/innocent/) ? '#imp-innocent-confirm' : '#imp-spam-confirm').popup('open');
            e.preventDefault();
            break;

        case 'message-innocent-confirm':
        case 'message-spam-confirm':
            $.mobile.changePage(HordeMobile.createUrl('mailbox', {
                mbox: ImpMobile.mailbox
            }), {
                data: { noajax: true }
            });

            ImpMobile.reportSpam(
                (view.match(/innocent/) ? 'innocent' : 'spam'),
                ImpMobile.buid
            );
            e.preventDefault();
            break;

        case 'search-exit':
            $('#mailbox .smartmobile-back').removeClass($.mobile.activeBtnClass).blur();
            $.mobile.changePage(HordeMobile.createUrl('mailbox', {
                mbox: data.options.parsedUrl.params.mbox
            }));
            e.preventDefault();
            break;

        case 'search-submit':
            ImpMobile.search = {
                qsearch: $('#imp-search-input').val(),
                qsearchfield: $('#imp-search-by').val(),
                qsearchmbox: (ImpMobile.search ? ImpMobile.search.qsearchmbox : ImpMobile.mailbox)
            };
            delete ImpMobile.mailboxCache;
            delete ImpMobile.cache[IMP.conf.qsearchid];
            $.mobile.changePage(HordeMobile.createUrl('mailbox', {
                mbox: IMP.conf.qsearchid
            }));
            e.preventDefault();
            break;

        case 'unblock-image':
            IMP_JS.unblockImages(data.options.link.closest('DIV.mimePartBase').find('IFRAME.htmlMsgData'));
            data.options.link.remove();
            e.preventDefault();
            break;
        }
    },

    /**
     */
    beforeShow: function(undefined, data)
    {
        switch (HordeMobile.currentPage()) {
        case 'compose':
            if (!$('#compose-more ul').children().size()) {
                $('#compose :jqmData(role=footer) a[href$="compose-more"]').hide();
            }
            break;

        case 'copymove':
            if (!$('#imp-copymove-list').children().size()) {
                HordeMobile.doAction(
                    'copyMoveMailboxList',
                    {},
                    function(r) {
                        $('#imp-copymove-list')
                            .html(r)
                            .change(function() {
                                $.fn[$(this[this.selectedIndex]).hasClass('flistCreate') ? 'show' : 'hide'].call($('#imp-copymove-newdiv'));
                            })
                            .find('.flistMailboxes OPTION[value=""]').attr('disabled', true);
                    },
                    { async: false }
                );
            }

            $('#imp-copymove')[0].reset();
            $('#imp-copymove-action,#imp-copymove-list').selectmenu('refresh', true);
            $('#imp-copymove-action').selectmenu(ImpMobile.cache[ImpMobile.mailbox].readonly ? 'disable' : 'enable');
            $('#imp-copymove-newdiv').hide();
            break;

        case 'search':
            if (!ImpMobile.search) {
                $('#imp-search-form').trigger('reset').find('select').selectmenu('refresh');
            }
            break;
        }
    },

    /**
     */
    pageShow: function()
    {
        switch (HordeMobile.currentPage()) {
        case 'folders':
            if (!ImpMobile.foldersLoaded) {
                ImpMobile.loadFolders();
            }
            break;

        case 'mailbox':
            $.mobile.silentScroll(ImpMobile.mailboxTop);

            // Need to do here since Exit Search does not trigger beforeShow.
            $.fn[ImpMobile.search ? 'hide' : 'show'].call($('#imp-mailbox-search'));
            $.fn[ImpMobile.search ? 'show' : 'hide'].call($('#imp-mailbox-searchedit'));
            break;

        case 'message':
            $('#imp-message-headers,#imp-message-atc').trigger('collapse');
            break;
        }
    },

    /**
     * Switches to the mailbox view and loads a mailbox.
     *
     * @param object data  Page change data object.
     */
    toMailbox: function(data)
    {
        var purl = data.options.parsedUrl,
            mailbox = purl.params.mbox || ImpMobile.INBOX,
            noajax = purl.params.noajax || (data.options.data && data.options.data.noajax),
            title = $('#imp-mailbox-' + mailbox).text(),
            params, ob;

        document.title = title;
        $('#mailbox .smartmobile-title').text(title);

        if (ImpMobile.mailbox != mailbox) {
            $('#imp-mailbox-list').empty();

            if (mailbox == IMP.conf.qsearchid) {
                if (!ImpMobile.search) {
                    $.mobile.changePage(HordeMobile.createUrl('mailbox', {
                        mbox: ImpMobile.INBOX
                    }));
                    return;
                }

                $('#mailbox .smartmobile-back').attr(
                    'href',
                    HordeMobile.createUrl('search-exit', {
                        mbox: ImpMobile.search.qsearchmbox
                    })
                ).find('.ui-btn-text').text(IMP.text.exitsearch);
            } else if (ImpMobile.search) {
                delete ImpMobile.search;
                $('#mailbox .smartmobile-back').attr('href', '#folders')
                    .find('.ui-btn-text').text(IMP.text.folders);
                HordeMobile.updateHash(purl);
            }

            ImpMobile.mailbox = mailbox;
        }

        params = ImpMobile.search || {};

        HordeMobile.changePage('mailbox', data);

        if ((!data.options.data || !data.options.data.force) &&
            (ob = ImpMobile.cache[mailbox])) {
            ImpMobile.refreshMailbox(ob);
            if (noajax) {
                return;
            }
            params.checkcache = 1;
        }

        HordeMobile.doAction(
            'viewport',
            ImpMobile.addViewportParams($.extend(params, {
                view: mailbox
            }))
        );
    },

    /**
     */
    addViewportParams: function(params)
    {
        params = params || {};

        var p, slice,
            ob = ImpMobile.cache[ImpMobile.mailbox];

        if (ob) {
            params.cache = ImpMobile.toUidString(ob.cachedIds());
            params.cacheid = ob.cacheid;
            slice = ob.slice;
        } else {
            params.initial = 1;
            params.force = 1;
            slice = ImpMobile.mbox_slice;
        }

        if (!params.search) {
            params.slice = '1:' + slice;
        }

        p = {
            view: params.view,
            viewport: JSON.stringify(params)
        };

        if (!ImpMobile.flags) {
            p.flag_config = 1;
        }

        return p;
    },

    /**
     * Callback method to update viewport information.
     *
     * @param object r  The Ajax response object.
     */
    viewport: function(r)
    {
        var ob;

        if (!(ob = ImpMobile.cache[r.view])) {
            ob = ImpMobile.cache[r.view] = $.extend(true, {}, ImpMobileMbox);
            ob.slice = ImpMobile.mbox_slice;
            if (r.metadata.readonly || r.metadata.nodelete) {
                ob.readonly = 1;
            }
            ob.innocent = r.metadata.innocent_show;
            ob.label = r.metadata.slabel
                ? r.metadata.slabel
                : r.label;
            ob.spam = r.metadata.spam_show;
        }
        ob.cacheid = r.cacheid;
        if (r.data_reset) {
            ob.data = {};
        }
        if (r.rowlist_reset) {
            ob.rowlist = {};
            ob.totalrows = 0;
        }
        if (r.data) {
            ob.update(r.data, r.rowlist, r.totalrows);
        }
        if (r.disappear) {
            ob.disappear(r.disappear);
        }

        ImpMobile.resetRefresh();
        ImpMobile.refreshMailbox(ob);
    },

    /**
     * Refresh the current mailbox.
     *
     * @param object ob  If ob is not present, triggers an AJAX refresh.
     *                   Otherwise, rebuilds mailbox using ob.
     */
    refreshMailbox: function(ob)
    {
        if (!ob) {
            if (HordeMobile.currentPage() != 'folders') {
                HordeMobile.doAction(
                    'viewPort',
                    ImpMobile.addViewportParams({
                        checkcache: 1,
                        view: ImpMobile.mailbox
                    })
                );
            }
            return;
        }

        if (HordeMobile.currentPage() != 'mailbox') {
            return;
        }

        var list, tmp,
            cid = ImpMobile.mailbox + '|' + ob.cacheid + '|' + ob.slice;

        if (cid == ImpMobile.mailboxCache) {
            return;
        }
        ImpMobile.mailboxCache = cid;

        document.title = ob.label;
        $('#mailbox .smartmobile-title').text(ob.label);

        list = $('#imp-mailbox-list')
            .empty()
            .append(tmp = $('<li data-role="list-divider"></li>'));

        switch (ob.totalrows) {
        case 0:
            tmp.text(IMP.text.message_0);
            break;

        case 1:
            tmp.text(IMP.text.message_1);
            break;

        default:
            tmp.text(IMP.text.message_2.replace('%d', ob.totalrows));
            break;
        }

        $.each(ob.rows(), function(key, val) {
            var c = $('<li class="imp-message"></li>')
                    .jqmData('buid', val.buid),
                labels = [],
                tmp,
                url = HordeMobile.createUrl('message', {
                    buid: val.buid,
                    mbox: ImpMobile.mailbox
                });

            if (val.data.flag) {
                $.each(val.data.flag, function(k, flag) {
                    switch (flag) {
                    case IMP.conf.flags.deleted:
                        c.addClass('imp-mailbox-deleted');
                        break;

                    case IMP.conf.flags.draft:
                        url = HordeMobile.createUrl('compose', {
                            buid: val.buid,
                            mbox: ImpMobile.mailbox,
                            type: 'resume'
                        });
                        break;

                    case IMP.conf.flags.seen:
                        c.addClass('imp-mailbox-seen');
                        break;

                    default:
                        /* All other flags are added as labels. */
                        if (ImpMobile.flags[flag]) {
                            tmp = ImpMobile.flags[flag];
                            if (tmp.s) {
                                labels.push(
                                    $('<span></span>')
                                        .addClass('imp-mailbox-label')
                                        .css({
                                            background: (tmp.b ? tmp.b : ''),
                                            color: tmp.f
                                        })
                                        .text(tmp.l)
                                );
                            }
                        }
                        break;
                    }
                });
            }

            list.append(
                c.append(
                    $('<a href="' + url + '"></a>').text(val.data.subject)
                ).append(
                    $('<div class="imp-mailbox-secondrow"></div>').append(
                        $('<span class="imp-mailbox-date"></span>').text(
                            val.data.date
                        )
                    ).append(
                        $('<span class="imp-mailbox-from"></span>').text(
                            val.data.from
                        ).append(labels)
                    )
                )
            );
        });

        if (ob.totalrows > ob.slice) {
            list.append($('<li class="imp-mailbox-more"></li>').append(
                $('<a href="#mailbox-more"></a>').text(
                    IMP.text.more_msgs)));
        }

        list.listview('refresh');
    },

    /**
     * Resets the current mailbox refresh interval.
     */
    resetRefresh: function()
    {
        if (IMP.conf.refresh_time) {
            window.clearInterval(ImpMobile.refresh);
            ImpMobile.refresh = window.setInterval(
                ImpMobile.refreshMailbox,
                IMP.conf.refresh_time * 1000
            );
        }
    },

    /**
     * Switches to the message view and loads a message.
     *
     * @param object data  Page change data object.
     */
    toMessage: function(data)
    {
        var purl = data.options.parsedUrl,
            params = {};

        if (!ImpMobile.mailbox) {
            ImpMobile.mailbox = purl.params.mbox;
        }

        HordeMobile.changePage('message', data);

        // Page is cached.
        if (ImpMobile.buid == purl.params.buid &&
            ImpMobile.mailbox == purl.params.mbox) {
            document.title = $('#message .smartmobile-title').text();
            return;
        }

        $('#message :jqmData(role=content)').hide();
        $('#message .smartmobile-title').text('');
        document.title = '';

        HordeMobile.doAction(
            'showMessage',
            $.extend(ImpMobile.addViewportParams($.extend(params, {
                view: purl.params.mbox
            })), {
                buid: purl.params.buid
            }),
            ImpMobile.messageLoaded
        );
    },

    /**
     * Navigates to the next/previous message page.
     *
     * @param integer dir  Jump length.
     */
    navigateMessage: function(dir)
    {
        var buid,
            ob = ImpMobile.cache[ImpMobile.mailbox],
            pos = ob.rowlist[ImpMobile.rowid] + dir;

        if (pos > 0 &&
            pos <= ob.totalrows &&
            (buid = ob.rowToBuid(pos))) {
            $.mobile.changePage(HordeMobile.createUrl('message', {
                buid: buid,
                mbox: ImpMobile.mailbox
            }));
        }
    },

    /**
     * Callback method after the message has been loaded.
     *
     * @param object r  The Ajax response object.
     */
    messageLoaded: function(r)
    {
        // TODO: Error handling.
        if (r.error ||
            !ImpMobile.message ||
            (r.view != ImpMobile.mailbox)) {
            return;
        }

        var cache = ImpMobile.cache[ImpMobile.mailbox],
            data = ImpMobile.message,
            tmp;

        // TODO: Remove once we can pass viewport parameters directly to the
        // showMessage request.
        if (!cache) {
            window.setTimeout(function() { ImpMobile.messageLoaded(r); }, 0);
            return;
        }

        ImpMobile.buid = r.buid;

        document.title = data.title;
        $('#message .smartmobile-title').text(data.title);

        tmp = (ImpMobile.mailbox == IMP.conf.qsearchid);
        $('#message .smartmobile-back').attr(
            'href',
            HordeMobile.createUrl('mailbox', {
                mbox: (tmp ? IMP.conf.qsearchid : ImpMobile.mailbox),
                noajax: 1
            })
        ).find('.ui-btn-text').text(
            tmp ? IMP.text.searchresults : cache.label
        );

        $('#imp-message-from').text(
            data.from
                ? (data.from.raw || data.from.addr[0].g || data.from.addr[0].p || data.from.addr[0].b)
                : IMP.text.nofrom
        );

        if (data.atc_label) {
            $('#imp-message-atc').show();
            $('#imp-message-atclabel').text(data.atc_label);

            ImpMobile.atc = data.atc_list;
        } else {
            $('#imp-message-atc').hide();
            delete ImpMobile.atc;
        }

        $('#imp-message-body').html(data.msgtext);
        $('#imp-message-date').text('');

        $.each(data.headers, function(k, v) {
            if (v.id == 'Date') {
                $('#imp-message-date').text(v.value);
            }
        });

        data.headers.push({ name: IMP.text.subject, value: data.subject });
        ImpMobile.headers = {
            Cc: data.cc,
            From: data.from,
            headers: data.headers,
            To: data.to
        };
        ImpMobile.listmsg = (data.list_info && data.list_info.exists);
        ImpMobile.rowid = r.buid;

        $.fn[cache.readonly ? 'hide' : 'show'].call($('#imp-message-delete'));

        if (data.js) {
            $.each(data.js, function(k, js) {
                $.globalEval(js);
            });
        }

        $('#message :jqmData(role=content)').show();

        tmp = $('#imp-message-headers');
        $.mobile.silentScroll(parseInt(tmp.position().top, 10) + parseInt(tmp.height(), 10) - $('#message > :jqmData(role=header)').height());

        delete ImpMobile.message;
    },

    /**
     */
    messageMorePopup: function()
    {
        var cache = ImpMobile.cache[ImpMobile.mailbox],
            list = $('#message-more :jqmData(role=listview)'),
            row = cache.rowlist[ImpMobile.rowid];

        list.children().each(function() {
            var elt = $(this),
                a = elt.find('a:first'),
                id = a.attr('id');

            if (id) {
                switch (id) {
                case 'imp-message-copymove':
                    /* Need to manually set href parameters for dialog links,
                     * since there is no way to programatically open one. */
                    a.attr('href', HordeMobile.createUrl('copymove', {
                        buid: ImpMobile.buid,
                        mbox: ImpMobile.mailbox
                    }));
                    break;

                case 'imp-message-innocent':
                    $.fn[!cache.innocent ? 'hide' : 'show'].call(elt);
                    break;

                case 'imp-message-next':
                    $.fn[(row == cache.maxRow()) ? 'hide' : 'show'].call(elt);
                    break;

                case 'imp-message-prev':
                    $.fn[(row == 1) ? 'hide' : 'show'].call(elt);
                    break;

                case 'imp-message-spam':
                    $.fn[!cache.spam ? 'hide' : 'show'].call(elt);
                    break;
                }
            }
        });

        list.listview('refresh');
    },

    /**
     */
    messageReplyPopup: function()
    {
        var list = $('#message-reply :jqmData(role=listview)');

        $.fn[ImpMobile.listmsg ? 'show' : 'hide'].call(list.find('a[href$="message-reply-list"]').parents('li'));
        list.listview('refresh');
    },

    /**
     */
    fullHeaders: function()
    {
        if (!ImpMobile.headers) {
            return;
        }

        var h = $('#imp-message-headers-full tbody');

        h.children().remove();

        $.each(ImpMobile.headers.headers, function(k, header) {
            var td, tmp;

            if (header.value) {
                td = $('<td class="imp-header-label-value"></td>');

                switch (header.id) {
                case 'Cc':
                case 'From':
                case 'To':
                    tmp = ImpMobile.headers[header.id];

                    if (tmp.raw) {
                        td.html(tmp.raw);
                    } else {
                        $.each(tmp.addr, function(k2, addr) {
                            if (addr.g) {
                            } else if (addr.p || addr.b) {
                                td.append(
                                    $('<a></a>')
                                        .attr('href', HordeMobile.createUrl('compose', { to: addr.b }))
                                        .attr('data-role', 'button')
                                        .attr('data-theme', 'b')
                                        .text(addr.p ? (addr.p + ' <' + addr.b + '>') : addr.b)
                                        .button()
                                );
                            }
                        });
                    }
                    break;

                default:
                    td.html(header.value);
                    break;
                }

                h.append($('<tr></tr>')
                    .append($('<td class="imp-header-label"></td>')
                        .html(header.name + ':'))
                    .append(td)
                );
            }
        });

        delete ImpMobile.headers;
    },

    /**
     */
    showAttachments: function()
    {
        if (!ImpMobile.atc) {
            return;
        }

        var list = $('#imp-message-atclist').empty();

        $.each(ImpMobile.atc, function(k, v) {
            list.append(
                $('<li class="imp-message-atc"></li>').append(
                    $('<a></a>').attr({
                        href: v.download_url,
                        rel: 'external'
                    }).append(
                        $(v.icon).addClass('ui-li-icon')
                    ).append(
                        document.createTextNode(v.description_raw + ' (' + v.size + ')')
                    )
                )
            );
        });

        list.listview('refresh');

        delete ImpMobile.atc;

        // TODO: Workaround bug(?) in jQuery Mobile where inset style is not
        // applied until listview is visible.
        window.setTimeout(function() { list.listview('refresh'); }, 0);
    },

    /**
     */
    deleteMessage: function(buid)
    {
        HordeMobile.doAction(
            'deleteMessages',
            $.extend(ImpMobile.addViewportParams({
                checkcache: 1,
                force: 1,
                view: ImpMobile.mailbox
            }), {
                buid: buid
            })
        );
    },

    /**
     */
    reportSpam: function(action, buid)
    {
        HordeMobile.doAction(
            'reportSpam',
            $.extend(ImpMobile.addViewportParams({
                checkcache: 1,
                force: 1,
                view: ImpMobile.mailbox
            }), {
                buid: buid,
                spam: ~~(action == 'spam')
            })
        );
    },

    /**
     * Switches to the compose view and loads a message if replying or
     * forwarding.
     *
     * @param object data  Page change data object.
     */
    compose: function(data)
    {
        var func,
            params = {},
            purl = data.options.parsedUrl;

        $('#compose .smartmobile-title').html(IMP.text.new_message);

        if (purl.params.to || purl.params.cc) {
            ImpMobile.addAddress('compose-to', purl.params.to);
            ImpMobile.addAddress('compose-cc', purl.params.cc);
        }

        $('#imp-compose-form').show();
        $('#imp-redirect-form').hide();

        ImpMobile.composeatc = [];
        ImpMobile.composetype = purl.params.type;

        switch (purl.params.type) {
        case 'reply':
        case 'reply_all':
        case 'reply_auto':
        case 'reply_list':
            func = 'getReplyData';
            params.format = 'text';
            break;

        case 'forward_auto':
            func = 'smartmobileGetForwardData';
            params.format = 'text';
            break;

        case 'forward_redirect':
            $('#imp-compose-form').hide();
            $('#imp-redirect-form').show();
            func = 'getRedirectData';
            break;

        case 'resume':
        case 'template':
            func = 'getResumeData';
            params.format = 'text';
            break;

        default:
            HordeMobile.changePage('compose', data);
            return;
        }

        HordeMobile.doAction(
            func,
            $.extend(params, {
                buid: purl.params.buid,
                type: purl.params.type,
                view: purl.params.mbox
            }),
            function(r) { ImpMobile.composeLoaded(r, data); }
        );
    },

    /**
     * Callback method after the compose content has been loaded.
     *
     * @param object r     The Ajax response object.
     * @param object data  Page change data object.
     */
    composeLoaded: function(r, data)
    {
        if (r.type != 'forward_redirect') {
            if (!r.opts) {
                r.opts = {};
            }
            r.opts.noupdate = true;

            var id = (r.identity === null)
                ? $('#imp-compose-identity').val()
                : r.identity;

            $('#imp-compose-identity,#imp-compose-last-identity').val(id);
            // The first selectmenu() call is necessary to actually create the
            // selectmenu if the compose window is opened for the first time,
            // the second call to update the menu in case the selected index
            // changed.
            $('#imp-compose-identity').selectmenu()
                .selectmenu('refresh', true);

            if (r.addr) {
                $.each(r.addr.to, function(k, v) {
                    ImpMobile.addAddress('compose-to', v);
                });
                $.each(r.addr.cc, function(k, v) {
                    ImpMobile.addAddress('compose-cc', v);
                });
            }

            $('#imp-compose-subject').val(r.subject);
            $('#imp-compose-message').val(r.body);

            $('#imp-compose-' + (r.opts.focus || 'to').replace(/composeMessage/, 'message'))[0].focus();
        }

        HordeMobile.changePage('compose', data);
    },

    /**
     */
    composeMorePopup: function()
    {
        var list = $('#compose-more :jqmData(role=listview)');

        list.children().each(function() {
            var elt = $(this),
                id = elt.find('a:first').attr('id');

            switch (id) {
            case 'imp-compose-discard':
                $.fn[(ImpMobile.composetype != 'resume') ? 'hide' : 'show'].call(elt);
                break;
            }
        });

        list.listview('refresh');
    },

    /**
     */
    composeAttachRefresh: function()
    {
        var list = $('#imp-compose-attach :jqmData(role=listview)').empty();

        $.each(ImpMobile.composeatc, function(k, v) {
            list.append(
                $('<li class="imp-compose-atc"></li>').append(
                    $('<img></img>')
                        .addClass('ui-li-icon')
                        .attr('src', v.icon)
                ).append(
                    v.name + ' (' + v.size + ')'
                )
            );
        });

        list.listview('refresh').siblings('FORM')[0].reset();
    },

    /**
     */
    addAddress: function(f, addr)
    {
        if (addr) {
            $('#imp-' + f + '-addr').prepend(
                $('<input></input>')
                    .attr('name', (f == 'redirect-to' ? 'redirect_to' : f.substr(8)) + '[]')
                    .attr('type', 'hidden')
                    .val(addr)
            ).prepend(
                $('<a></a>')
                    .attr('href', '#compose-delete-addr')
                    .attr('data-role', 'button')
                    .attr('data-icon', 'delete')
                    .attr('data-iconpos', 'right')
                    .attr('data-theme', 'b')
                    .text(addr)
                    .button()
            );
        }
    },

    /**
     */
    processComposeAddress: function(e, prev)
    {
        var chr, pos = 0, tmp,
            val = $(this).val();

        if (val.length > prev.length) {
            // Optimization when only one character was added
            if (val.length - prev.length == 1) {
                tmp = val.charAt(val.length - 1);
                if (tmp != ',' && tmp != ';') {
                    return;
                }
            }

            chr = val.charAt(pos);
            while (chr !== "") {
                switch (chr) {
                case ',':
                case ';':
                    if (!pos) {
                        val = val.substr(1);
                    } else if (val.charAt(pos - 1) != '\\') {
                        ImpMobile.addAddress($(this).attr('id').substr(4), $.trim(val.substr(0, chr == ';' ? pos + 1 : pos)));
                        val = val.substr(pos + 2);
                        pos = 0;
                    }
                    break;

                default:
                    ++pos;
                    break;
                }

                chr = val.charAt(pos);
            }

            $(this).val($.trim(val));
        }
    },

    /**
     * Load the folders list.
     */
    loadFolders: function()
    {
        ImpMobile.foldersLoaded = false;

        HordeMobile.doAction(
            'smartmobileFolderTree',
            { all: ImpMobile.showAllFolders() },
            function(r) {
                $('#imp-folders-list').html(r).listview('refresh');
                ImpMobile.foldersLoaded = true;
            }
        );
    },

    uniqueSubmit: function(action)
    {
        var form = (action == 'redirectMessage')
            ? $('#imp-redirect-form')
            : $('#imp-compose-form');

        if (action == 'sendMessage' &&
            $('#imp-compose-subject').val().empty() &&
            !window.confirm(IMP.text.nosubject)) {
            return;
        }

        HordeMobile.doAction(
            action,
            HordeJquery.formToObject(form),
            ImpMobile.uniqueSubmitCallback
        );
    },

    uniqueSubmitCallback: function(d)
    {
        if (d && d.success) {
            ImpMobile.closeCompose();
        }
    },

    closeCompose: function()
    {
        $.each($('#imp-compose-form,#imp-redirect-form'), function(k, form) {
            form.reset();
        });
        $.each($('#imp-compose-to,#imp-compose-cc,#imp-redirect-to'), function(undefined, v) {
            $(v).autocomplete('clear');
        });
        window.history.back();
    },

    /**
     * Opens a copy/move message dialog.
     *
     * @param object data  Page change data object.
     */
    copymove: function(data)
    {
        var purl = data.options.parsedUrl;

        HordeMobile.changePage('copymove');

        $('#imp-copymove-buid').val(purl.params.buid);
        $('#imp-copymove-mbox').val(purl.params.mbox);
    },

    /**
     * Moves or copies a message to a selected mailbox.
     */
    copymoveSelected: function()
    {
        var opts = {},
            cmlist = $('#imp-copymove-list'),
            source = $('#imp-copymove-mbox').val(),
            move = ($('#imp-copymove-action').val() == 'move');

        if (cmlist.find(':selected').hasClass('flistCreate')) {
            opts.newmbox = $.trim($('#imp-copymove-new').val());
            if (opts.newmbox === "") {
                window.alert(IMP.text.move_nombox);
                return;
            }
        } else {
            opts.mboxto = cmlist.val();
        }

        HordeMobile.doAction(
            move ? 'moveMessages' : 'copyMessages',
            $.extend(ImpMobile.addViewportParams({
                checkcache: 1,
                force: ~~(move),
                view: source
            }), opts, {
                buid: $('#imp-copymove-buid').val()
            })
        );

        if (IMP.conf.mailbox_return || move) {
            $.mobile.changePage(HordeMobile.createUrl('mailbox', {
                mbox: source
            }), {
                data: { noajax: true }
            });
        } else {
            $('#copymove').dialog('close');
        }
    },

    /**
     * Update message flags.
     *
     * @param object r  The Ajax response object.
     */
    updateFlags: function(r)
    {
        $.each(r, function(k, v) {
            $.each(v.buids, function(k2, v2) {
                var c = ImpMobile.cache[k2];

                if (c) {
                    $.each(ImpMobile.fromUidString(v2), function(k3, v3) {
                        if (c.data[v3]) {
                            var ob = c.data[v3].flag, tmp = [];
                            if (v.add) {
                                $.merge(ob, v.add);
                                $.each(ob, function(i, v4) {
                                    if ($.inArray(v4, tmp) === -1) {
                                        tmp.push(v4);
                                    }
                                });
                                ob = tmp;
                            }
                            if (v.remove) {
                                ob = $.grep(ob, function(n) {
                                    return $.inArray(n, v.remove) < 0;
                                });
                            }
                        }
                    });
                }
            });
        });
    },

    /**
     * Update unseen message count for folders.
     *
     * @param object r  The Ajax response object.
     */
    updateFolders: function(r)
    {
        /* If redrawing the folders via smartmobileFolderTree action, this
         * task will be executed before the list is refreshed. */
        if (!ImpMobile.foldersLoaded &&
            (HordeMobile.currentPage() == 'folders')) {
            window.setTimeout(function() { ImpMobile.updateFolders(r); }, 100);
        }

        $.each(r, function(key, value) {
            var elt = $('#imp-mailbox-' + key);
            if (value) {
                if (!elt.siblings('.ui-li-count').size()) {
                    elt.after('<span class="ui-li-count"></span>');
                }
                elt.siblings('.ui-li-count').text(value);
            } else if (!value) {
                elt.siblings('.ui-li-count').remove();
            }
        });

        if (HordeMobile.currentPage() == 'folders') {
            $('#imp-folders-list').listview('refresh');
        }
    },

    /**
     * Are all folders shown?
     *
     * @return integer  1 if all folders are shown.
     */
    showAllFolders: function()
    {
        return $('#folders :jqmData(role=footer) a[href$="folders-showpoll"]').filter(':visible').length;
    },

    /**
     * Converts an array to a UID range string.
     *
     * @param array uids  Array of UIDs.
     *
     * @return string  The UID range string.
     */
    toUidString: function(uids)
    {
        var u = uids.numericSort(),
            first = u.shift(),
            last = first,
            out = [];

        $.each(u, function(n, k) {
            if (last + 1 == k) {
                last = k;
            } else {
                out.push(first + (last == first ? '' : (':' + last)));
                first = last = k;
            }
        });

        out.push(first + (last == first ? '' : (':' + last)));

        return out.join(',');
    },

    /**
     * Converts a UID range string to an array.
     *
     * @param string str  UID range string.
     *
     * @return array  UID array.
     */
    fromUidString: function(str)
    {
        var out = [];

        $.each($.trim(str).split(','), function(n, e) {
            var i, r = e.split(':');
            if (r.length == 1) {
                out.push(Number(e));
            } else {
                for (i = Number(r[0]); i <= Number(r[1]); ++i) {
                    out.push(Number(i));
                }
            }
        });

        return out;
    },

    /**
     */
    runTasks: function(e, d)
    {
        var v;

        if ((v = d['imp:compose'])) {
            $.fn[v.atclimit ? 'hide' : 'show'].call($('#imp-compose-attach-form'));
            if (v.cacheid) {
                if ($('#imp-redirect-form').css('display') != 'none') {
                    $('#imp-redirect-cache').val(v.cacheid);
                } else {
                    $('#imp-compose-cache').val(v.cacheid);
                    $('#imp-compose-hmac').val(v.hmac);
                }
            }
        }

        if ((v = d['imp:compose-atc'])) {
            ImpMobile.composeatc = ImpMobile.composeatc.concat(v);
        }

        if ((v = d['imp:flag'])) {
            ImpMobile.updateFlags(v);
            // Force a viewport update.
            ImpMobile.mailboxCache = null;
        }

        if ((v = d['imp:flag-config'])) {
            ImpMobile.flags = {};
            $.each(v, function(undefined, value) {
                ImpMobile.flags[value.id] = value;
            });
        }

        if ((v = d['imp:message'])) {
            ImpMobile.message = v.shift().data;
        }

        if (d['imp:mailbox']) {
             ImpMobile.foldersLoaded = false;
        }

        if ((v = d['imp:poll'])) {
            ImpMobile.updateFolders(v);
        }

        if ((v = d['imp:viewport'])) {
            ImpMobile.viewport(v);
        }
    },

    /**
     */
    swipeButtons: function(e, ob)
    {
        $.each($($('#imp-mailbox-buttons').children()), function(k, v) {
            v = $(v);
            if (ImpMobile.mailboxAction(v)) {
                ob.buttons.push(v.clone(true));
            }
        });
    },

    /**
     */
    mailboxAction: function(v)
    {
        switch (v.attr('href')) {
        case '#mailbox-delete':
            return !ImpMobile.cache[ImpMobile.mailbox].readonly;

        case '#mailbox-innocent':
            return ImpMobile.cache[ImpMobile.mailbox].innocent;

        case '#mailbox-spam':
            return ImpMobile.cache[ImpMobile.mailbox].spam;

        default:
            return true;
        }
    },

    /**
     */
    mailboxTaphold: function(e)
    {
        $.each($('#imp-mailbox-taphold li'), function(k, v) {
            v = $(v);
            if (ImpMobile.mailboxAction(v.find('a'))) {
                v.show().find('a').jqmData('buid', $(e.currentTarget).jqmData('buid'));
            } else {
                v.hide();
            }
        });
        $('#imp-mailbox-taphold ul').listview('refresh');
    },

    /**
     * Event handler for the document-ready event, responsible for the initial
     * setup.
     */
    onDocumentReady: function()
    {
        $('#imp-mailbox-list')
            .swipebutton()
                .on('swipebutton', 'li.imp-message', ImpMobile.swipeButtons)
            .listviewtaphold({ popupElt: $('#imp-mailbox-taphold') })
                .on('listviewtaphold', 'li.imp-message', ImpMobile.mailboxTaphold);

        ImpMobile.resetRefresh();

        $('#message').on('swipeleft', function() {
            $.mobile.changePage('#message-next');
        }).on('swiperight', function() {
            $.mobile.changePage('#message-prev');
        }).on('popupbeforeposition', function(r) {
            switch ($(r.target).attr('id')) {
            case 'message-more':
                ImpMobile.messageMorePopup();
                break;

            case 'message-reply':
                ImpMobile.messageReplyPopup();
                break;
            }
        });
        $('#imp-message-headers').on('expand', ImpMobile.fullHeaders);
        $('#imp-message-atc').on('expand', ImpMobile.showAttachments);

        $('#compose').on('popupbeforeposition', function() {
            ImpMobile.composeMorePopup();
        });

        if (!IMP.conf.disable_compose) {
            $('#imp-compose-upload-container a').on('click', function(e) {
                $('#imp-compose-upload-container input').trigger('click');
                e.preventDefault();
            });
            $('#imp-compose-upload-container input').on('change', function() {
                HordeMobile.doAction('addAttachment', {
                    composeCache: $('#imp-compose-cache').val(),
                    json_return: true
                }, ImpMobile.composeAttachRefresh, {
                    submit: $(this).closest('FORM')
                });
            });

            $.each([ 'compose-to', 'compose-cc', 'redirect-to' ], function(undefined, v) {
                $('#imp-' + v)
                    .autocomplete({
                        callback: function(e) {
                            ImpMobile.addAddress(v, $.trim($(e.currentTarget).text()));
                            $('#imp-' + v).val('');
                        },
                        link: '#',
                        minLength: 3,
                        source: 'smartmobileAutocomplete',
                        target: $('#imp-' + v + '-suggestions')
                    })
                    // textchange plugin requires bind()
                    .bind('textchange', ImpMobile.processComposeAddress);
            });
        }
    }

};

// JQuery Mobile setup
$(ImpMobile.onDocumentReady);
$(document).on('pagebeforechange', ImpMobile.toPage)
    .on('pagebeforeshow', ImpMobile.beforeShow)
    .on('pagechange', ImpMobile.pageShow)
    .on('HordeMobile:runTasks', ImpMobile.runTasks);

var ImpMobileMbox = {
    // Vars used: cacheid, label, readonly
    data: {},
    rowlist: {},
    slice: 0,
    totalrows: 0,

    update: function(data, rowlist, totalrows)
    {
        if (data.length !== 0) {
            $.extend(this.data, data);
        }
        if (rowlist.length !== 0) {
            $.extend(this.rowlist, rowlist);
        }
        this.totalrows = totalrows;
    },

    cachedIds: function()
    {
        var ids = [];

        $.each(this.data, function(key) {
            ids.push(key);
        });

        return ids;
    },

    disappear: function(ids)
    {
        if (!ids.length) {
            return;
        }

        var t = this;

        $.each(ids, function(key, value) {
            delete t.data[value];
        });
    },

    rows: function()
    {
        var mbox_data = this.data;

        return $.map($.map(this.rowlist, function(value, key) {
            return { sort: value, buid: key };
        }).sort(function(a, b) {
            return (a.sort < b.sort) ? -1 : 1;
        }), function(value) {
            return {
                buid: value.buid,
                data: mbox_data[value.buid]
            };
        });
    },

    rowToBuid: function(row)
    {
        var buid;

        $.each(this.rowlist, function(b, p) {
            if (p == row) {
                buid = b;
                return;
            }
        });

        return buid;
    },

    maxRow: function()
    {
        return Math.min(this.slice, this.totalrows);
    }

};


var IMP_JS = {

    iframeInject: function(id, data)
    {
        id = $('#' + id);
        var d = id.get(0).contentWindow.document;

        id.one('load', function() {
            window.setTimeout(function() { IMP_JS.iframeResize(id); }, 0);
        });

        d.open();
        d.write(data);
        d.close();

        /* Catch compose links within HTML IFRAME data. */
        $(d).on('click', 'a', function(e) {
            var href = $(e.target).attr('href');
            if (href.match(/\#compose[\?$]/)) {
                $.mobile.changePage(href);
                e.preventDefault();
            }
        });

        id.show().prev().remove();
    },

    iframeResize: function(id)
    {
        id = $(id);
        id.height(id.contents().height());
    },

    /**
     * Use DOM manipulation to un-block images.
     */
    unblockImages: function(iframe)
    {
        var doc = $(iframe.get(0).contentWindow.document),
            imgload = false;

        $.each(doc.find('[htmlimgblocked]'), function(k, v) {
            v = $(v);
            var src = v.attr('htmlimgblocked');
            v.removeAttr('htmlimgblocked');

            if (v.attr('src')) {
                v.one('load', function() { IMP_JS.iframeResize(iframe); });
                v.attr('src', src);
                imgload = true;
            } else {
                if (v.attr('background')) {
                    v.attr('background', src);
                }
                if (v.css('background-image')) {
                    v.css('background-image', 'url(' + src + ')');
                }
            }
        });

        $.each(doc.find('[htmlimgblocked_srcset]'), function(k, v) {
            v = $(v);
            v.attr('srcset', v.attr('htmlimgblocked_srcset'));
            v.removeAttr('htmlimgblocked_srcset');
        });

        $.each(doc.find('[htmlcssblocked]'), function(k, v) {
            v = $(v);
            v.attr('href', v.attr('htmlcssblocked'));
            v.removeAttr('htmlcssblocked');
        });

        doc.find('STYLE[type="text/x-imp-cssblocked"]')
            .attr('type', 'text/css');

        if (!imgload) {
            IMP_JS.iframeResize(iframe);
        }
    }

};
