/**
 * Kolab groupware utilities
 *
 * @author Thomas Bruederli <bruederli@kolabsys.com>
 *
 * @licstart  The following is the entire license notice for the
 * JavaScript code in this file.
 *
 * Copyright (C) 2015-2018, Kolab Systems AG <contact@kolabsys.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * @licend  The above is the entire license notice
 * for the JavaScript code in this file.
 */

var libkolab_audittrail = {}, libkolab = {};

libkolab_audittrail.quote_html = function(str)
{
    return String(str).replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
};

// show object changelog in a dialog
libkolab_audittrail.object_history_dialog = function(p)
{
    // render dialog
    var $dialog = $(p.container);

    // close show dialog first
    if ($dialog.is(':ui-dialog'))
        $dialog.dialog('close');

    // hide and reset changelog table
    $dialog.find('div.notfound-message').remove();
    $dialog.find('.changelog-table').show().children('tbody')
        .html('<tr><td colspan="4"><span class="loading">' + rcmail.gettext('loading') + '</span></td></tr>');

    // open jquery UI dialog
    $dialog.dialog({
        modal: true,
        resizable: true,
        closeOnEscape: true,
        title: p.title,
        open: function() {
            $dialog.attr('aria-hidden', 'false');
        },
        close: function() {
            $dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
        },
        buttons: [
            {
                text: rcmail.gettext('close'),
                click: function() { $dialog.dialog('close'); },
                'class': 'cancel',
                autofocus: true
            }
        ],
        minWidth: 450,
        width: 650,
        height: 350,
        minHeight: 200
    })
    .show().children('.compare-button').hide();

    // initialize event handlers for history dialog UI elements
    if (!$dialog.data('initialized')) {
      // compare button
      $dialog.find('.compare-button input').click(function(e) {
        var rev1 = $dialog.find('.changelog-table input.diff-rev1:checked').val(),
          rev2 = $dialog.find('.changelog-table input.diff-rev2:checked').val();

          if (rev1 && rev2 && rev1 != rev2) {
            // swap revisions if the user got it wrong
            if (rev1 > rev2) {
              var tmp = rev2;
              rev2 = rev1;
              rev1 = tmp;
            }

            if (p.comparefunc) {
                p.comparefunc(rev1, rev2);
            }
          }
          else {
              alert('Invalid selection!')
          }

          if (!rcube_event.is_keyboard(e) && this.blur) {
              this.blur();
          }
          return false;
      });

      // delegate handlers for list actions
      $dialog.find('.changelog-table tbody').on('click', 'td.actions a', function(e) {
          var link = $(this),
            action = link.hasClass('restore') ? 'restore' : 'show',
            event = $('#eventhistory').data('event'),
            rev = link.attr('data-rev');

            // ignore clicks on first row (current revision)
            if (link.closest('tr').hasClass('first')) {
                return false;
            }

            // let the user confirm the restore action
            if (action == 'restore' && !confirm(rcmail.gettext('revisionrestoreconfirm', p.module).replace('$rev', rev))) {
                return false;
            }

            if (p.listfunc) {
                p.listfunc(action, rev);
            }

            if (!rcube_event.is_keyboard(e) && this.blur) {
                this.blur();
            }
            return false;
      })
      .on('click', 'input.diff-rev1', function(e) {
          if (!this.checked) return true;

          var rev1 = this.value, selection_valid = false;
          $dialog.find('.changelog-table input.diff-rev2').each(function(i, elem) {
              $(elem).prop('disabled', elem.value <= rev1);
              if (elem.checked && elem.value > rev1) {
                  selection_valid = true;
              }
          });
          if (!selection_valid) {
              $dialog.find('.changelog-table input.diff-rev2:not([disabled])').last().prop('checked', true);
          }
      });

      $dialog.addClass('changelog-dialog').data('initialized', true);
    }

    return $dialog;
};

// callback from server with changelog data
libkolab_audittrail.render_changelog = function(data, object, folder)
{
    var Q = libkolab_audittrail.quote_html;

    var $dialog = $('.changelog-dialog')
    if (data === false || !data.length) {
        return false;
    }

    var i, change, accessible, op_append,
      first = data.length - 1, last = 0,
      is_writeable = !!folder.editable,
      op_labels = {
          RECEIVE:   'actionreceive',
          APPEND:    'actionappend',
          MOVE:      'actionmove',
          DELETE:    'actiondelete',
          READ:      'actionread',
          FLAGSET:   'actionflagset',
          FLAGCLEAR: 'actionflagclear'
      },
      actions = '<a href="#show" class="iconbutton preview" title="'+ rcmail.gettext('showrevision','libkolab') +'" data-rev="{rev}" /> ' +
          (is_writeable ? '<a href="#restore" class="iconbutton restore" title="'+ rcmail.gettext('restore','libkolab') + '" data-rev="{rev}" />' : ''),
      tbody = $dialog.find('.changelog-table tbody').html('');

    for (i=first; i >= 0; i--) {
        change = data[i];
        accessible = change.date && change.user;

        if (change.op == 'MOVE' && change.mailbox) {
            op_append = ' ⇢ ' + change.mailbox;
        }
        else if ((change.op == 'FLAGSET' || change.op == 'FLAGCLEAR') && change.flags) {
            op_append = ': ' + change.flags;
        }
        else {
            op_append = '';
        }

        $('<tr class="' + (i == first ? 'first' : (i == last ? 'last' : '')) + (accessible ? '' : 'undisclosed') + '">')
            .append('<td class="diff">' + (accessible && change.op != 'DELETE' ? 
                '<input type="radio" name="rev1" class="diff-rev1" value="' + change.rev + '" title="" '+ (i == last ? 'checked="checked"' : '') +' /> '+
                '<input type="radio" name="rev2" class="diff-rev2" value="' + change.rev + '" title="" '+ (i == first ? 'checked="checked"' : '') +' /></td>'
                : ''))
            .append('<td class="revision">' + Q(i+1) + '</td>')
            .append('<td class="date">' + Q(change.date || '') + '</td>')
            .append('<td class="user">' + Q(change.user || 'undisclosed') + '</td>')
            .append('<td class="operation" title="' + op_append + '">' + Q(rcmail.gettext(op_labels[change.op] || '', 'libkolab') + op_append) + '</td>')
            .append('<td class="actions">' + (accessible && change.op != 'DELETE' ? actions.replace(/\{rev\}/g, change.rev) : '') + '</td>')
            .appendTo(tbody);
    }

    if (first > 0) {
        $dialog.find('.compare-button').fadeIn(200);
        $dialog.find('.changelog-table tr.last input.diff-rev1').click();
    }

    // set dialog size according to content
    libkolab_audittrail.dialog_resize($dialog.get(0), $dialog.height() + 15, 600);

    return $dialog;
};

// resize and reposition (center) the dialog window
libkolab_audittrail.dialog_resize = function(id, height, width)
{
    var win = $(window), w = win.width(), h = win.height();
    $(id).dialog('option', { height: Math.min(h-20, height+130), width: Math.min(w-20, width+50) });
};

/**
 * Open an attachment either in a browser window for inline view or download it
 */
libkolab.load_attachment = function(query, attachment)
{
    query._frame = 1;

    // open attachment in frame if it's of a supported mimetype similar as in app.js
    if (attachment.id && attachment.mimetype && $.inArray(attachment.mimetype, rcmail.env.mimetypes) >= 0) {
        if (rcmail.open_window(rcmail.url('get-attachment', query), true, true)) {
            return;
        }
    }

    query._frame = null;
    query._download = 1;
    rcmail.goto_url('get-attachment', query, false);
};

/**
 * Build attachments list element
 */
libkolab.list_attachments = function(list, container, edit, data, ondelete, onload)
{
    var ul = $('<ul>').addClass('attachmentslist');

    $.each(list || [], function(i, elem) {
        var li = $('<li>').addClass(elem.classname);

        // name/link
        $('<a>').attr({href: '#load', 'class': 'filename'})
            .append($('<span class="attachment-name">').text(elem.name))
            .click({record: data, attachment: elem}, function(e) {
                if (onload) {
                    onload(e.data);
                }
                return false;
            })
            .appendTo(li);

        if (edit) {
            rcmail.env.attachments[elem.id] = elem;
            // delete link
            $('<a>').attr({href: '#delete', title: rcmail.gettext('delete'), 'class': 'delete'})
                .click({id: elem.id}, function(e) {
                    $(this.parentNode).hide();
                    delete rcmail.env.attachments[e.data.id];
                    if (ondelete) {
                        ondelete(e.data.id);
                    }
                    return false;
                })
                .appendTo(li);
        }

        ul.append(li);
    });

    if (edit && rcmail.gui_objects.attachmentlist) {
        ul.id = rcmail.gui_objects.attachmentlist.id;
        rcmail.gui_objects.attachmentlist = ul.get(0);
    }

    container.empty().append(ul);
};


function kolab_folderlist(node, p)
{
    // extends treelist.js
    rcube_treelist_widget.call(this, node, p);

    // private vars
    var me = this;
    var search_results;
    var search_results_widget;
    var search_results_container;
    var listsearch_request;
    var search_messagebox;

    var Q = rcmail.quote_html;

    // render the results for folderlist search
    function render_search_results(results)
    {
        if (results.length) {
          // create treelist widget to present the search results
          if (!search_results_widget) {
              var list_id = (me.container.attr('id') || p.id_prefix || '0');

              search_results_container = $('<div class="searchresults"></div>')
                  .html(p.search_title ? '<h2 class="boxtitle" id="st:' + list_id + '">' + p.search_title + '</h2>' : '')
                  .insertAfter(me.container);

              search_results_widget = new rcube_treelist_widget('<ul>', {
                  id_prefix: p.id_prefix,
                  id_encode: p.id_encode,
                  id_decode: p.id_decode,
                  selectable: false
              });

              // copy classes from main list
              search_results_widget.container.addClass(me.container.attr('class')).attr('aria-labelledby', 'st:' + list_id);

              // register click handler on search result's checkboxes to select the given item for listing
              search_results_widget.container
                  .appendTo(search_results_container)
                  .on('click', 'input[type=checkbox], a.subscribed, span.subscribed', function(e) {
                      var node, has_children, li = $(this).closest('li'),
                          id = li.attr('id').replace(new RegExp('^'+p.id_prefix), '');

                      if (p.id_decode)
                          id = p.id_decode(id);
                      node = search_results_widget.get_node(id);
                      has_children = node.children && node.children.length;

                      e.stopPropagation();
                      e.bubbles = false;

                      // activate + subscribe
                      if ($(e.target).hasClass('subscribed')) {
                          search_results[id].subscribed = true;
                          $(e.target).attr('aria-checked', 'true');
                          li.children().first()
                              .toggleClass('subscribed')
                              .find('input[type=checkbox]').get(0).checked = true;

                          if (has_children && search_results[id].group == 'other user') {
                              li.find('ul li > div').addClass('subscribed')
                                  .find('a.subscribed').attr('aria-checked', 'true');;
                          }
                      }
                      else if (!this.checked) {
                          return;
                      }

                      // copy item to the main list
                      add_result2list(id, li, true);

                      if (has_children) {
                          li.find('input[type=checkbox]').first().prop('disabled', true).prop('checked', true);
                          li.find('a.subscribed, span.subscribed').first().hide();
                      }
                      else {
                          li.remove();
                      }

                      // set partial subscription status
                      if (search_results[id].subscribed && search_results[id].parent && search_results[id].group == 'other') {
                          parent_subscription_status($(me.get_item(id, true)));
                      }

                      // set focus to cloned checkbox
                      if (rcube_event.is_keyboard(e)) {
                          $(me.get_item(id, true)).find('input[type=checkbox]').first().focus();
                      }
                  })
                  .on('click', function(e) {
                      var prop, id = String($(e.target).closest('li').attr('id')).replace(new RegExp('^'+p.id_prefix), '');
                      if (p.id_decode)
                          id = p.id_decode(id);

                      if (!rcube_event.is_keyboard(e) && e.target.blur)
                        e.target.blur();

                      // forward event
                      if (prop = search_results[id]) {
                        e.data = prop;
                        if (me.triggerEvent('click-item', e) === false) {
                          e.stopPropagation();
                          return false;
                        }
                      }
                  });
          }

          // add results to list
          for (var prop, item, i=0; i < results.length; i++) {
              prop = results[i];
              item = $(prop.html);
              search_results[prop.id] = prop;
              search_results_widget.insert({
                  id: prop.id,
                  classes: [ prop.group || '' ],
                  html: item,
                  collapsed: true,
                  virtual: prop.virtual
              }, prop.parent);

              // disable checkbox if item already exists in main list
              if (me.get_node(prop.id) && !me.get_node(prop.id).virtual) {
                  item.find('input[type=checkbox]').first().prop('disabled', true).prop('checked', true);
                  item.find('a.subscribed, span.subscribed').hide();
              }

              prop.li = item.parent().get(0);
              me.triggerEvent('add-item', prop);
          }

          search_results_container.show();
        }
    }

    // helper method to (recursively) add a search result item to the main list widget
    function add_result2list(id, li, active)
    {
        var node = search_results_widget.get_node(id),
            prop = search_results[id],
            parent_id = prop.parent || null,
            has_children = node.children && node.children.length,
            dom_node = has_children ? li.children().first().clone(true, true) : li.children().first(),
            childs = [];

        // find parent node and insert at the right place
        if (parent_id && me.get_node(parent_id)) {
            dom_node.children('span,a').first().html(Q(prop.editname || prop.listname));
        }
        else if (parent_id && search_results[parent_id]) {
            // copy parent tree from search results
            add_result2list(parent_id, $(search_results_widget.get_item(parent_id)), false);
        }
        else if (parent_id) {
            // use full name for list display
            dom_node.children('span,a').first().html(Q(prop.name));
        }

        // replace virtual node with a real one
        if (me.get_node(id)) {
            $(me.get_item(id, true)).children().first()
                .replaceWith(dom_node)
                .removeClass('virtual');
        }
        else {
            // copy childs, too
            if (has_children && prop.group == 'other user') {
                for (var cid, j=0; j < node.children.length; j++) {
                    if ((cid = node.children[j].id) && search_results[cid]) {
                        childs.push(search_results_widget.get_node(cid));
                    }
                }
            }

            // move this result item to the main list widget
            me.insert({
                id: id,
                classes: [ prop.group || '' ],
                virtual: prop.virtual,
                html: dom_node,
                level: node.level,
                collapsed: true,
                children: childs
            }, parent_id, prop.group);
        }

        delete prop.html;
        prop.active = active;
        me.triggerEvent('insert-item', { id: id, data: prop, item: li });

        // register childs, too
        if (childs.length) {
            for (var cid, j=0; j < node.children.length; j++) {
                if ((cid = node.children[j].id) && search_results[cid]) {
                    prop = search_results[cid];
                    delete prop.html;
                    prop.active = false;
                    me.triggerEvent('insert-item', { id: cid, data: prop });
                }
            }
        }
    }

    // update the given item's parent's (partial) subscription state
    function parent_subscription_status(li)
    {
        var top_li = li.closest(me.container.children('li')),
            all_childs = $('li > div:not(.treetoggle)', top_li),
            subscribed = all_childs.filter('.subscribed').length;

        if (subscribed == 0) {
            top_li.children('div:first').removeClass('subscribed partial');
        }
        else {
            top_li.children('div:first')
                .addClass('subscribed')[subscribed < all_childs.length ? 'addClass' : 'removeClass']('partial');
        }
    }

    // do some magic when search is performed on the widget
    this.addEventListener('search', function(search) {
        // hide search results
        if (search_results_widget) {
            search_results_container.hide();
            search_results_widget.reset();
        }
        search_results = {};

        if (search_messagebox)
            rcmail.hide_message(search_messagebox);

        // send search request(s) to server
        if (search.query && search.execute) {
            // require a minimum length for the search string
            if (rcmail.env.autocomplete_min_length && search.query.length < rcmail.env.autocomplete_min_length && search.query != '*') {
                search_messagebox = rcmail.display_message(
                    rcmail.get_label('autocompletechars').replace('$min', rcmail.env.autocomplete_min_length));
                return;
            }

            if (listsearch_request) {
                // ignore, let the currently running request finish
                if (listsearch_request.query == search.query) {
                    return;
                }
                else { // cancel previous search request
                    rcmail.multi_thread_request_abort(listsearch_request.id);
                    listsearch_request = null;
                }
            }

            var sources = p.search_sources || [ 'folders' ];
            var reqid = rcmail.multi_thread_http_request({
                items: sources,
                threads: rcmail.env.autocomplete_threads || 1,
                action:  p.search_action || 'listsearch',
                postdata: { action:'search', q:search.query, source:'%s' },
                lock: rcmail.display_message(rcmail.get_label('searching'), 'loading'),
                onresponse: render_search_results,
                whendone: function(data){
                  listsearch_request = null;
                  me.triggerEvent('search-complete', data);
                }
            });

            listsearch_request = { id:reqid, query:search.query };
        }
        else if (!search.query && listsearch_request) {
            rcmail.multi_thread_request_abort(listsearch_request.id);
            listsearch_request = null;
        }
    });

    this.container.on('click', 'a.subscribed, span.subscribed', function(e) {
        var li = $(this).closest('li'),
            id = li.attr('id').replace(new RegExp('^'+p.id_prefix), ''),
            div = li.children().first(),
            is_subscribed;

        if (me.is_search()) {
            id = id.replace(/--xsR$/, '');
            li = $(me.get_item(id, true));
            div = $(div).add(li.children().first());
        }

        if (p.id_decode)
            id = p.id_decode(id);

        div.toggleClass('subscribed');
        is_subscribed = div.hasClass('subscribed');
        $(this).attr('aria-checked', is_subscribed ? 'true' : 'false');
        me.triggerEvent('subscribe', { id: id, subscribed: is_subscribed, item: li });

        // update subscribe state of all 'virtual user' child folders
        if (li.hasClass('other user')) {
            $('ul li > div', li).each(function() {
                $(this)[is_subscribed ? 'addClass' : 'removeClass']('subscribed');
                $('.subscribed', div).attr('aria-checked', is_subscribed ? 'true' : 'false');
            });
            div.removeClass('partial');
        }
        // propagate subscription state to parent  'virtual user' folder
        else if (li.closest('li.other.user').length) {
            parent_subscription_status(li);
        }

        e.stopPropagation();
        return false;
    });

    this.container.on('click', 'a.remove', function(e) {
        var li = $(this).closest('li'),
            id = li.attr('id').replace(new RegExp('^'+p.id_prefix), '');

        if (me.is_search()) {
            id = id.replace(/--xsR$/, '');
            li = $(me.get_item(id, true));
        }

        if (p.id_decode)
            id = p.id_decode(id);

        me.triggerEvent('remove', { id: id, item: li });

        e.stopPropagation();
        return false;
    });
}

// link prototype from base class
if (window.rcube_treelist_widget) {
    kolab_folderlist.prototype = rcube_treelist_widget.prototype;
}


window.rcmail && rcmail.addEventListener('init', function(e) {
    var loading_lock;

    if (rcmail.env.task == 'mail') {
        rcmail.register_command('kolab-mail-history', function() {
            var dialog, uid = rcmail.get_single_uid(), rec = { uid: uid, mbox: rcmail.get_message_mailbox(uid) };
            if (!uid || !window.libkolab_audittrail) {
                return false;
            }

            // render dialog
            $dialog = libkolab_audittrail.object_history_dialog({
                module: 'libkolab',
                container: '#mailmessagehistory',
                title: rcmail.gettext('objectchangelog','libkolab')
            });

            $dialog.data('rec', rec);

            // fetch changelog data
            loading_lock = rcmail.set_busy(true, 'loading', loading_lock);
            rcmail.http_post('plugin.message-changelog', { _uid: rec.uid, _mbox: rec.mbox }, loading_lock);

        }, rcmail.env.action == 'show');

        rcmail.addEventListener('plugin.message_render_changelog', function(data) {
            var $dialog = $('#mailmessagehistory'),
                rec = $dialog.data('rec');

            if (data === false || !data.length || !rec) {
              // display 'unavailable' message
              $('<div class="notfound-message dialog-message warning">' + rcmail.gettext('objectchangelognotavailable','libkolab') + '</div>')
                  .insertBefore($dialog.find('.changelog-table').hide());
              return;
            }

            data.module = 'libkolab';
            libkolab_audittrail.render_changelog(data, rec, {});
        });

        rcmail.env.message_commands.push('kolab-mail-history');
    }

    if (rcmail.env.action == 'get-attachment') {
        if (rcmail.gui_objects.attachmentframe) {
            rcmail.gui_objects.messagepartframe = rcmail.gui_objects.attachmentframe;
            rcmail.enable_command('image-scale', 'image-rotate', !!/^image\//.test(rcmail.env.mimetype));
            rcmail.register_command('print-attachment', function() {
                var frame = rcmail.get_frame_window(rcmail.gui_objects.attachmentframe.id);
                if (frame) frame.print();
            }, true);
        }

        if (rcmail.env.attachment_download_url) {
            rcmail.register_command('download-attachment', function() {
                rcmail.location_href(rcmail.env.attachment_download_url, window);
            }, true);
        }
    }
});