
/** @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 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) {};

/**
 * @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
 * @return {Object|null}
**/
ChatSystem.prototype.poll = function(knownVersion, nowTs) {};

/**
 * @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++)
        if (this.contexts[i].getChatContext().self.id === userId)
            return true;
    return false;
};

/**
 * @param {number} knownVersion
 * @param {Function} callback
 * @param {(function(number, function((Array<{expose:Function, modified: number}>|null))))=} checkConfigUpdate
**/
MultiChatManager.prototype.poll = function(knownVersion, callback, checkConfigUpdate) {
    this.contexts.forEach(function(ctx) {
        ctx.onRequest();
    });
    var configFeed = { config: null };
    setTimeout((function() {
        //FIXME polling
        var liveFeed
            ,staticFeed
            ,allTyping
            ,v = 0
            ,updated = !!configFeed
            ,now = Date.now();

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

                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 (configFeed.config) {
            v = Math.max(v, configFeed.v);
            updated = true;
        }
        if (updated)
            callback({
                "live": liveFeed,
                "static": staticFeed,
                "config": configFeed.config || undefined,
                "typing": allTyping,
                "v": Math.max(v, knownVersion)
            });
        else
            callback({
                "v": knownVersion
            });
    }).bind(this), 2000);
    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);
                });
                configFeed.config = exposedConfig;
                configFeed.v = v;
            }
        });
};

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

