odoo.define('mail.Chatter', function (require) {
"use strict";

var Activity = require('mail.Activity');
var ChatterComposer = require('mail.ChatterComposer');
var Followers = require('mail.Followers');
var ThreadField = require('mail.ThreadField');
var utils = require('mail.utils');

var concurrency = require('web.concurrency');
var config = require('web.config');
var core = require('web.core');
var Widget = require('web.Widget');

var QWeb = core.qweb;

// The purpose of this widget is to display the chatter area below the form view
//
// It instanciates the optional mail_thread, mail_activity and mail_followers widgets.
// It Ensures that those widgets are appended at the right place, and allows them to communicate
// with each other.
// It synchronizes the rendering of those widgets (as they may be asynchronous), to limitate
// the flickering when switching between records
var Chatter = Widget.extend({
    template: 'mail.Chatter',
    custom_events: {
        reload_mail_fields: '_onReloadMailFields',
    },
    events: {
        'click .o_chatter_button_new_message': '_onOpenComposerMessage',
        'click .o_chatter_button_log_note': '_onOpenComposerNote',
        'click .o_chatter_button_schedule_activity': '_onScheduleActivity',
    },
    supportedFieldTypes: ['one2many'],

    /**
     * @override
     * @param {widget} parent
     * @param {Object} record
     * @param {Object} mailFields
     * @param {boolean} [mailFields.mail_activity]
     * @param {boolean} [mailFields.mail_followers]
     * @param {boolean} [mailFields.mail_thread]
     */
    init: function (parent, record, mailFields, options) {
        this._super.apply(this, arguments);
        this._setState(record);

        this.dp = new concurrency.DropPrevious();

        // mention: get the prefetched partners and use them as mention suggestions
        // if there is a follower widget, the followers will be added to the
        // suggestions as well once fetched
        this.mentionPartnerSuggestions = this.call('chat_manager', 'getMentionPartnerSuggestions');
        this.mentionSuggestions = this.mentionPartnerSuggestions;

        this.fields = {};
        if (mailFields.mail_activity) {
            this.fields.activity = new Activity(this, mailFields.mail_activity, record, options);
        }
        if (mailFields.mail_followers) {
            this.fields.followers = new Followers(this, mailFields.mail_followers, record, options);
        }
        if (mailFields.mail_thread) {
            this.fields.thread = new ThreadField(this, mailFields.mail_thread, record, options);
            var nodeOptions = this.record.fieldsInfo.form[mailFields.mail_thread].options;
            this.hasLogButton = nodeOptions.display_log_button;
            this.postRefresh = nodeOptions.post_refresh || 'never';
        }
    },
    /**
     * @override
     */
    start: function () {
        this.$topbar = this.$('.o_chatter_topbar');

        // render and append the buttons
        this.$topbar.append(QWeb.render('mail.Chatter.Buttons', {
            new_message_btn: !!this.fields.thread,
            log_note_btn: this.hasLogButton,
            schedule_activity_btn: !!this.fields.activity,
            isMobile: config.device.isMobile,
        }));

        // start and append the widgets
        var fieldDefs = _.invoke(this.fields, 'appendTo', $('<div>'));
        var def = this.dp.add($.when.apply($, fieldDefs));
        this._render(def).then(this._updateMentionSuggestions.bind(this));

        return this._super.apply(this, arguments);
    },

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    /**
     * @param {Object} record
     * @param {integer} [record.res_id=undefined]
     * @param {Object[]} [fieldNames=undefined]
     */
    update: function (record, fieldNames) {
        var self = this;

        // close the composer if we switch to another record as it is record dependent
        if (this.record.res_id !== record.res_id) {
            this._closeComposer(true);
        }

        // update the state
        this._setState(record);

        // detach the thread and activity widgets (temporarily force the height to prevent flickering)
        // keep the followers in the DOM as it has a synchronous pre-rendering
        this.$el.height(this.$el.height());
        if (this.fields.activity) {
            this.fields.activity.$el.detach();
        }
        if (this.fields.thread) {
            this.fields.thread.$el.detach();
        }

        // reset and re-append the widgets (and reset 'height: auto' rule)
        // if fieldNames is given, only reset those fields, otherwise reset all fields
        var fieldsToReset;
        if (fieldNames) {
            fieldsToReset = _.filter(this.fields, function (field) {
                return _.contains(fieldNames, field.name);
            });
        } else {
            fieldsToReset = this.fields;
        }
        var fieldDefs = _.invoke(fieldsToReset, 'reset', record);
        var def = this.dp.add($.when.apply($, fieldDefs));
        this._render(def).then(function () {
            self.$el.height('auto');
            self._updateMentionSuggestions();
        });
    },

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {boolean} force
     */
    _closeComposer: function (force) {
        if (this.composer && (this.composer.is_empty() || force)) {
            this.$el.removeClass('o_chatter_composer_active');
            this.$('.o_chatter_button_new_message, .o_chatter_button_log_note').removeClass('o_active');
            this.composer.do_hide();
            this.composer.clear_composer();
        }
    },
    /**
     * @private
     */
    _disableChatter: function () {
        this.$('.btn').prop('disabled', true); // disable buttons
    },
    /**
     * @private
     */
    _enableChatter: function () {
        this.$('.btn').prop('disabled', false); // enable buttons
    },
    /**
     * @private
     * @param {Object} options
     * @param {Object[]} [options.suggested_partners=[]]
     * @param {boolean} [options.is_log]
     */
    _openComposer: function (options) {
        var self = this;
        var old_composer = this.composer;
        // create the new composer
        this.composer = new ChatterComposer(this, this.record.model, options.suggested_partners || [], {
            commands_enabled: false,
            context: this.context,
            input_min_height: 50,
            input_max_height: Number.MAX_VALUE, // no max_height limit for the chatter
            input_baseline: 14,
            is_log: options && options.is_log,
            record_name: this.record_name,
            default_body: old_composer && old_composer.$input && old_composer.$input.html(),
            default_mention_selections: old_composer && old_composer.mention_get_listener_selections(),
        });
        this.composer.on('input_focused', this, function () {
            this.composer.mention_set_prefetched_partners(this.mentionSuggestions || []);
        });
        this.composer.insertAfter(this.$('.o_chatter_topbar')).then(function () {
            // destroy existing composer
            if (old_composer) {
                old_composer.destroy();
            }
            if (!config.device.touch) {
                self.composer.focus();
            }
            self.composer.on('post_message', self, function (message) {
                self.fields.thread.postMessage(message).then(function () {
                    self._closeComposer(true);
                    if (self.postRefresh === 'always' || (self.postRefresh === 'recipients' && message.partner_ids.length)) {
                        self.trigger_up('reload');
                    }
                });
            });
            self.composer.on('need_refresh', self, self.trigger_up.bind(self, 'reload'));
            self.composer.on('close_composer', null, self._closeComposer.bind(self, true));

            self.$el.addClass('o_chatter_composer_active');
            self.$('.o_chatter_button_new_message, .o_chatter_button_log_note').removeClass('o_active');
            self.$('.o_chatter_button_new_message').toggleClass('o_active', !self.composer.options.is_log);
            self.$('.o_chatter_button_log_note').toggleClass('o_active', self.composer.options.is_log);
        });
    },
    /**
     * @private
     * @param {Deferred} def
     * @returns {Deferred}
     */
    _render: function (def) {
        // the rendering of the chatter is aynchronous: relational data of its fields needs to be
        // fetched (in some case, it might be synchronous as they hold an internal cache).
        // this function takes a deferred as argument, which is resolved once all fields have
        // fetched their data
        // this function appends the fields where they should be once the given deferred is resolved
        // and if it takes more than 500ms, displays a spinner to indicate that it is loading
        var self = this;

        var $spinner = $(QWeb.render('Spinner'));
        concurrency.rejectAfter(concurrency.delay(500), def).then(function () {
            $spinner.appendTo(self.$el);
        });

        return def.then(function () {
            if (self.fields.activity) {
                self.fields.activity.$el.appendTo(self.$el);
            }
            if (self.fields.followers) {
                self.fields.followers.$el.appendTo(self.$topbar);
            }
            if (self.fields.thread) {
                self.fields.thread.$el.appendTo(self.$el);
            }
        }).always(function () {
            // disable widgets in create mode, otherwise enable
            self.isCreateMode ? self._disableChatter() : self._enableChatter();
            $spinner.remove;
        });
    },
    /**
     * @private
     * @param {Object} record
     * @param {integer} [record.res_id]
     * @param {string} [record.model]
     * @param {string} record.data.display_name
     */
    _setState: function (record) {

        this.wasCreateMode = !!this.isCreateMode;
        this.isCreateMode = !record.res_id;

        if (!this.record || this.record.res_id !== record.res_id) {
            this.context = {
                default_res_id: record.res_id || false,
                default_model: record.model || false,
            };
            // reset the suggested_partners_def to ensure a reload of the
            // suggested partners when opening the composer on another record
            this.suggested_partners_def = undefined;
        }
        this.record = record;
        this.record_name = record.data.display_name;
    },
    /**
     * @private
     */
    _updateMentionSuggestions: function () {
        if (!this.fields.followers) {
            return;
        }
        var self = this;

        this.mentionSuggestions = [];

        // add the followers to the mention suggestions
        var follower_suggestions = [];
        var followers = this.fields.followers.getFollowers();
        _.each(followers, function (follower) {
            if (follower.res_model === 'res.partner') {
                follower_suggestions.push({
                    id: follower.res_id,
                    name: follower.name,
                    email: follower.email,
                });
            }
        });
        if (follower_suggestions.length) {
            this.mentionSuggestions.push(follower_suggestions);
        }

        // add the partners (followers filtered out) to the mention suggestions
        _.each(this.mentionPartnerSuggestions, function (partners) {
            self.mentionSuggestions.push(_.filter(partners, function (partner) {
                return !_.findWhere(follower_suggestions, { id: partner.id });
            }));
        });
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     */
    _onOpenComposerMessage: function () {
        var self = this;
        if (!this.suggested_partners_def) {
            this.suggested_partners_def = $.Deferred();
            var method = 'message_get_suggested_recipients';
            var args = [[this.context.default_res_id], this.context];
            this._rpc({model: this.record.model, method: method, args: args})
                .then(function (result) {
                    if (!self.suggested_partners_def) {
                        return; // widget has been reset (e.g. we just switched to another record)
                    }
                    var suggested_partners = [];
                    var thread_recipients = result[self.context.default_res_id];
                    _.each(thread_recipients, function (recipient) {
                        var parsed_email = recipient[1] && utils.parse_email(recipient[1]);
                        suggested_partners.push({
                            checked: true,
                            partner_id: recipient[0],
                            full_name: recipient[1],
                            name: parsed_email[0],
                            email_address: parsed_email[1],
                            reason: recipient[2],
                        });
                    });
                    self.suggested_partners_def.resolve(suggested_partners);
                });
        }
        this.suggested_partners_def.then(function (suggested_partners) {
            self._openComposer({ is_log: false, suggested_partners: suggested_partners });
        });
    },
    /**
     * @private
     */
    _onOpenComposerNote: function () {
        this._openComposer({is_log: true});
    },
    /**
     * @private
     * @param {OdooEvent} event
     * @param {string} event.name
     * @param {Object} event.data
     * @param {boolean} [event.data.activity]
     * @param {boolean} [event.data.followers]
     * @param {boolean} [event.data.thread]
     */
    _onReloadMailFields: function (event) {
        var fieldNames = [];
        if (this.fields.activity && event.data.activity) {
            fieldNames.push(this.fields.activity.name);
        }
        if (this.fields.followers && event.data.followers) {
            fieldNames.push(this.fields.followers.name);
        }
        if (this.fields.thread && event.data.thread) {
            fieldNames.push(this.fields.thread.name);
        }
        this.trigger_up('reload', {
            fieldNames: fieldNames,
            keepChanges: true,
        });
    },
    /**
     * @private
     */
    _onScheduleActivity: function () {
        this.fields.activity.scheduleActivity(false);
    },
});

return Chatter;

});
