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

var MailManager = require('mail.Manager');
var DocumentThread = require('mail.model.DocumentThread');

var session = require('web.session');

/**
 * Document Thread Manager
 *
 * This part of the mail manager extends the core functionnalities of mail
 * manager with document threads, in addition to enable cross tab
 * synchronization of document thread windows, through the local storage.
 *
 * Note that it assumes that the Mail Window Manager is loaded
 */
MailManager.include({
    DOCUMENT_THREAD_MESSAGE_KEY: 'mail.document_threads_last_message',
    DOCUMENT_THREAD_STATE_KEY: 'mail.document_threads_state',

    start: function () {
        this._super.apply(this, arguments);

        // retrieve the open DocumentThreads from the localStorage
        var state = this.call('local_storage', 'getItem', this.DOCUMENT_THREAD_STATE_KEY);
        if (!state) {
            this.call('local_storage', 'setItem', this.DOCUMENT_THREAD_STATE_KEY, {});
        } else {
            this.isReady().then(this._updateDocumentThreadWindows.bind(this, state));
        }
        // listen to localStorage changes to synchronize DocumentThread's
        // windows between tabs
        window.addEventListener('storage', this._onStorage.bind(this));
    },

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

    /**
     * For new posted message of current user in a document thread: store it in
     * the localStorage to make it appear on the other tabs.
     *
     * Note: 'addMessage' modifies the message in place (by setting the channel_id,
     * so we must store the message in the localStorage *before* calling
     * 'addMessage')
     *
     * @override
     * @param {Object} data
     * @param {Array} [data.author_id] [int, string] where int is server ID of
     *   the author, and string is the name of the author.
     * @param {integer} data.id server ID of the message
     * @param {string} [data.model] the model name of the document that this
     *   message is related to.
     * @param {integer} [data.res_id] the ID of the document that this message
     *   is related to.
     *
     */
    addMessage: function (data) {
        var message = this.getMessage(data.id);
        if (
            !message &&
            data.res_id &&
            data.model &&
            data.author_id &&
            data.author_id[0] === session.partner_id
        ) {
            var key = this.DOCUMENT_THREAD_MESSAGE_KEY;
            this.call('local_storage', 'setItem', key, data);
        }
        return this._super.apply(this, arguments);
    },
    /**
     * Returns a document thread corresponding to the given model and resID.
     *
     * @param  {string} model of the document thread, if it exists
     * @return {integer} resID of the document thread, if it exists
     */
    getDocumentThread: function (model, resID) {
        return this.getThread(model + '_' + resID);
    },
    /**
     * Add a new document thread, or get if it exists already.
     *
     * Also, if a name is provided for the document thread, it overwrites the
     * previous one.
     *
     * @param {Object} params
     * @param {interger[]} [params.message_ids] the list of message ids linked
     *   to the document (if not given, they will be fetched before fetching the
     *   messages)
     * @param {string} [params.name] if provided, overwrites the name of the
     *   existing document thread
     * @param {integer} params.resID
     * @param {string} params.resModel
     * @return {mail.model.DocumentThread}
     */
    getOrAddDocumentThread: function (params) {
        var thread = this.getDocumentThread(params.resModel, params.resID);
        if (!thread) {
            thread = new DocumentThread({
                parent: this,
                data: {
                    messageIDs: params.messageIDs,
                    name: params.name,
                    resID: params.resID,
                    resModel: params.resModel,
                },
            });
            this._threads.push(thread);
        } else {
            if ('messageIDs' in params) {
                thread.setMessageIDs(params.messageIDs);
            }
            if ('name' in params && params.name) {
                // document thread may have a change of name
                thread.setName(params.name);
            }
        }
        return thread;
    },
    /**
     * Open the document thread form if discuss is open
     *
     * @override
     * @param {integer|string} threadID
     */
    openThread: function (threadID) {
        var thread = this.getThread(threadID);
        if (
            thread &&
            thread.getType() === 'document_thread' &&
            this._isDiscussOpen()
        ) {
            var resModel = thread.getDocumentModel();
            var resID = thread.getDocumentID();
            this._redirectToDocument(resModel, resID);
        } else {
            this._super.apply(this, arguments);
        }
    },
    /**
     * Updates the state of a given document thread (stored in localStorage).
     * Garbage collects windows previously marked as 'closed' (assuming that the
     * info has already been processed by the other tabs).
     *
     * @param {string} threadID ID of a document thread
     * @param {Object} state
     * @param {string} state.name name of the document thread
     * @param {string} state.windowState ('closed', 'folded' or 'open')
     */
    updateDocumentThreadState: function (threadID, state) {
        var states = this.call('local_storage', 'getItem', this.DOCUMENT_THREAD_STATE_KEY);
        states = _.omit(states, function (state) {
            return state.windowState === 'closed';
        });
        states[threadID] = state;
        this.call('local_storage', 'setItem', this.DOCUMENT_THREAD_STATE_KEY, states);
    },

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

    /**
     * For new messages, postprocess document thread linked to this message.
     * If there is no such document thread, create it and register its ID to
     * the message's thread IDs.
     *
     * @override
     * @private
     * @param {mail.model.Message} message
     */
    _addNewMessagePostprocessThread: function (message) {
        var resModel = message.getDocumentModel();
        var resID = message.getDocumentID();
        if (resModel && resModel !== 'mail.channel' && resID) {
            this.getOrAddDocumentThread({
                name: message.getDocumentName(),
                resID: resID,
                resModel: resModel,
            });
        }
        this._super.apply(this, arguments);
    },
    /**
     * Updates the thread windows related to document threads.
     *
     * @param {Object} documentThreadStates - keys are strings 'resModel_resID',
     *   and values are the state of the corresponding document thread ('open'
     *   or 'folded')
     */
    _updateDocumentThreadWindows: function (documentThreadStates) {
        var self = this;
        _.each(documentThreadStates, function (state, key) {
            var info = key.split('_');
            var documentThread = self.getOrAddDocumentThread({
                name: state.name,
                resID: parseInt(info[1]),
                resModel: info[0],
            });
            if (state.windowState === 'closed') {
                documentThread.close();
            } else {
                documentThread.fold(state.windowState === 'folded');
                self.openThreadWindow(documentThread.getID(), { keepFoldState: true });
            }
        });
    },

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

    /**
     * Called each time a localStorage key is updated.
     *
     * @private
     * @param {StorageEvent} ev
     */
    _onStorage: function (ev) {
        if (ev.key === this.DOCUMENT_THREAD_STATE_KEY) {
            var state = this.call('local_storage', 'getItem', this.DOCUMENT_THREAD_STATE_KEY);
            this._updateDocumentThreadWindows(state);
        } else if (ev.key === this.DOCUMENT_THREAD_MESSAGE_KEY) {
            var message = this.call('local_storage', 'getItem', this.DOCUMENT_THREAD_MESSAGE_KEY);
            if (message) {
                this.addMessage(message);
            }
        }
    },

});

return MailManager;

});
