
/** @const */
var POLL_TIMEOUT = 55000;

/** @interface */
function ChatSystem() {
};

/** */
ChatSystem.prototype.onRequest = function() {};

/**
 * @return {string|null}
**/
ChatSystem.prototype.getId = function() {};

/**
 * @return {ChatContext}
**/
ChatSystem.prototype.getChatContext = function() {};

/**
 * Async fetch history
 * @param {Room} chan
 * @param {string} text
**/
ChatSystem.prototype.sendMeMsg = function(chan, text) {};

/**
 * Async fetch history
 * @param {Room} chan
 * @param {string} text
 * @param {Object=} attachments
**/
ChatSystem.prototype.sendMsg = function(chan, text, attachments) {};

/**
 * Async star msg
 * @param {Room} chan
 * @param {string} msgId
**/
ChatSystem.prototype.starMsg = function(chan, msgId) {};

/**
 * Async remove star from msg
 * @param {Room} chan
 * @param {string} msgId
**/
ChatSystem.prototype.unstarMsg = function(chan, msgId) {};

/**
 * Async fetch history
 * @param {Room} chan
 * @param {number} ts
**/
ChatSystem.prototype.removeMsg = function(chan, ts) {};

/**
 * Edit msg
 * @param {Room} chan
 * @param {number} ts
 * @param {string} newText
**/
ChatSystem.prototype.editMsg = function(chan, ts, newText) {};

/**
 * Add reaction
 * @param {Room} chan
 * @param {number} ts
 * @param {string} reaction
**/
ChatSystem.prototype.addReaction = function(chan, ts, reaction) {};

/**
 * Add reaction
 * @param {Room} chan
 * @param {number} ts
 * @param {string} reaction
**/
ChatSystem.prototype.removeReaction = function(chan, ts, reaction) {};

/**
 * Add reaction
 * @param {Room} chan
 * @param {string} contentType
 * @param {function((string|null))} callback
**/
ChatSystem.prototype.openUploadFileStream = function(chan, contentType, callback) {};

/**
 * Async fetch history
 * @param {Room} chan
**/
ChatSystem.prototype.fetchHistory = function(chan, cb) {};

/**
 * @param {Room} chan
**/
ChatSystem.prototype.sendTyping = function(chan) {};

/**
 * @param {Room} chan
 * @param {number} ts
**/
ChatSystem.prototype.markRead = function(chan, ts) {};

/**
 * @param {Room} chan
 * @param {string} cmd
 * @param {string} args
**/
ChatSystem.prototype.sendCommand = function(chan, cmd, args) {};

/**
 * @param {number} knownVersion
 * @param {number} nowTs
 * @param {boolean} withTyping
 * @return {Object|null}
**/
ChatSystem.prototype.poll = function(knownVersion, nowTs, withTyping) {};

/**
 * @constructor
**/
function MultiChatManager() {
    /** @type {Array<ChatSystem>} */
    this.contexts = [];
}

/**
 * @param {ChatSystem} chatSystem
**/
MultiChatManager.prototype.push = function(chatSystem) {
    this.contexts.push(chatSystem);
};

/**
 * @param {string} id
 * @return {ChatSystem|null}
**/
MultiChatManager.prototype.getById = function(id) {
    for (var i = 0, nbContexts = this.contexts.length; i < nbContexts; i++)
        if (id === this.contexts[i].getId())
            return this.contexts[i];
    return null;
};

/**
 * You may want to return true to break the loop
**/
MultiChatManager.prototype.foreachChannels = function(cb) {
    for (var i =0, nbCtx =this.contexts.length; i < nbCtx; i++) {
        var ctx = this.contexts[i].getChatContext();
        for (var chanId in ctx.channels)
            if (cb(ctx.channels[chanId], chanId) === true)
                return;
    }
};

/**
 * You may want to return true to break the loop
**/
MultiChatManager.prototype.foreachContext = function(cb) {
    for (var i =0, nbCtx =this.contexts.length; i < nbCtx; i++)
        if (cb(this.contexts[i].getChatContext()) === true)
            return;
};

/**
 * @param {string} chanId
 * @return {ChatSystem|null}
**/
MultiChatManager.prototype.getChannelContext = function(chanId) {
    for (var i =0, nbCtx = this.contexts.length; i < nbCtx; i++) {
        var ctx = this.contexts[i].getChatContext();
        if (ctx.channels[chanId])
            return this.contexts[i];
    }
    return null;
};

/**
 * @param {string} userId
 * @return {ChatSystem|null}
**/
MultiChatManager.prototype.getUserContext = function(userId) {
    for (var i =0, nbCtx = this.contexts.length; i < nbCtx; i++) {
        var ctx = this.contexts[i].getChatContext();
        if (ctx.users[userId])
            return this.contexts[i];
    }
    return null;
};

/**
 * @param {string} chanId
 * @return {Room|null}
**/
MultiChatManager.prototype.getChannel = function(chanId) {
    for (var i =0, nbCtx = this.contexts.length; i < nbCtx; i++) {
        var chan = this.contexts[i].getChatContext().channels[chanId];
        if (chan)
            return chan;
    }
    return null;
};

/**
 * @param {function(Room, ChatSystem, string):boolean=} filter (room, roomId)
 * @return {Array<string>}
**/
MultiChatManager.prototype.getChannelIds = function(filter) {
    var ids = [];

    for (var i =0, nbCtx = this.contexts.length; i < nbCtx; i++) {
        var channels = this.contexts[i].getChatContext().channels;
        for (var chanId in channels) {
            if (!filter || filter(channels[chanId], this.contexts[i], chanId)) {
                ids.push(chanId);
            }
        }
    }
    return ids;
};

/**
 * @param {string} userId
 * @return {Chatter|null}
**/
MultiChatManager.prototype.getUser = function(userId) {
    for (var i =0, nbCtx = this.contexts.length; i < nbCtx; i++) {
        var user = this.contexts[i].getChatContext().users[userId];
        if (user)
            return user;
    }
    return null;
};

/**
 * @param {string} userId
 * @return {boolean}
**/
MultiChatManager.prototype.isMe = function(userId) {
    for (var i =0, nbCtx = this.contexts.length; i < nbCtx; i++) {
        var ctx = this.contexts[i].getChatContext();
        if (ctx.self && ctx.self.id === userId)
            return true;
    }
    return false;
};

MultiChatManager.prototype.pollPeriodical = function(knownVersion, withTyping) {
    var liveFeed
        ,staticFeed
        ,allTyping
        ,v = 0
        ,now = Date.now()
        ,updated = false;

    this.contexts.forEach(function(ctx) {
        var id = ctx.getId();
        if (id) {
            var res = ctx.poll(knownVersion, now, withTyping === undefined ? true : withTyping);

            if (res) {
                if (res["static"]) {
                    if (!staticFeed)
                        staticFeed = {};
                    staticFeed[id] = res["static"];
                }
                if (res["live"])
                    if (!liveFeed)
                        liveFeed = {};
                    for (var i in res["live"]) {
                        liveFeed[i] = res["live"][i];
                    }
                if (res["typing"]) {
                    if (!allTyping)
                        allTyping = {};
                    for (var i in res["typing"])
                        allTyping[i] = res["typing"][i];
                }
                v = Math.max(v, (res["v"] || 0));
                updated = true;
            }
        }
    });
    if (updated)
        return ({
            "live": liveFeed,
            "static": staticFeed,
            "typing": allTyping,
            "v": Math.max(v, knownVersion)
        });
};

MultiChatManager.polling = [];

/**
 * @param {number} knownVersion
 * @param {(function(number, function((Array<{expose:Function, modified: number}>|null))))=} checkConfigUpdate
 * @param {boolean=} withTyping default true
**/
MultiChatManager.prototype.poll = function(knownVersion, reqT, checkConfigUpdate, withTyping) {
    var _this = this;
    _this.contexts.forEach(function(ctx) {
        ctx.onRequest();
    });
    var p = new Promise(function(succ, err) {
        function launchPolling() {
            var currentState = _this.pollPeriodical(knownVersion, withTyping);
            if (currentState) {
                succ(currentState);
            } else {
                var timeo = setTimeout(function() {
                    for (var i =0, nbCtx = MultiChatManager.polling.length; i < nbCtx; i++) {
                        if (MultiChatManager.polling[i].reqT === reqT &&
                            MultiChatManager.polling[i].ctx === _this &&
                            MultiChatManager.polling[i].knownVersion === knownVersion) {
                            MultiChatManager.polling.splice(i, 1);
                            break;
                        }
                    }
                    err();
                }, 55000);
                MultiChatManager.polling.push({ ctx: _this, knownVersion: knownVersion, reqT: reqT, checkConfigUpdate: checkConfigUpdate, withTyping: withTyping, promise: succ, timeo: timeo });
            }
        };
        if (checkConfigUpdate) {
            checkConfigUpdate(knownVersion, function(config) {
                if (config) {
                    var exposedConfig = [],
                        v = 0;

                    config.forEach(function(conf) {
                        exposedConfig.push(conf.expose());
                        v = Math.max(v, conf.modified);
                    });

                    var res = _this.pollPeriodical(knownVersion, withTyping);
                    if (res) {
                        res["config"] = exposedConfig;
                        succ(res);
                    } else {
                        succ({
                            "config": exposedConfig,
                            "v": v
                        });
                    }
                } else {
                    launchPolling();
                }
            });
        } else {
            launchPolling();
        }
    });
    return p;
};

MultiChatManager.prototype.containsCtx = function(ctx) {
    for (var i =0, nbCtx = this.contexts.length; i < nbCtx; i++)
        if (ctx === this.contexts[i])
            return true;
    return false;
};

/** @param {ChatContext} ctx */
MultiChatManager.onCtxUpdated = function(ctx) {
    var i =0;
    while (MultiChatManager.polling[i]) {
        var pollingObj = MultiChatManager.polling[i];
        if (pollingObj.ctx.containsCtx(ctx)) {
            var res = pollingObj.ctx.pollPeriodical(pollingObj.knownVersion, pollingObj.withTyping);
            if (res) {
                clearTimeout(pollingObj.timeo);
                pollingObj.promise(res);
                MultiChatManager.polling.splice(i, 1);
            } else {
                i++;
            }
        }
    }
};

/** @suppress {undefinedVars,checkTypes} */
(function() {
    if (typeof module !== "undefined") {
        module.exports.MultiChatManager = MultiChatManager;
    }
})();

