
/**
 * @constructor
**/
function SlackTeam(teamId) {
    /** @const @type {string} */
    this.id = teamId;
    /** @type {string} */
    this.name;
    /** @type {string} */
    this.domain;
    /** @type {string} */
    this.callApp;
    /** @type {string} */
    this.callAppName;
    /** @type {boolean} */
    this.fileUploadPermission;
    /** @type {boolean} */
    this.fileEditPermission;
    /** @type {boolean} */
    this.fileDeletePermission;
    /** @type {Object.<string, string>} */
    this.icons = {
        image_34: ""
        ,image_44: ""
        ,image_68: ""
        ,image_88: ""
        ,image_102: ""
        ,image_132: ""
        ,image_230: ""
        ,image_default: ""
    };
    /** @type {number} */
    this.version = 0;
}

SlackTeam.prototype.update = function(teamData, t) {
    if (teamData["name"] !== undefined) this.name = teamData["name"];
    if (teamData["domain"] !== undefined) this.domain = teamData["domain"];
    if (teamData["prefs"]) {
        this.callApp = teamData["prefs"]["calling_app_id"];
        this.callAppName = teamData["prefs"]["calling_app_name"];
        this.fileUploadPermission = teamData["prefs"]["disable_file_uploads"];
        this.fileEditPermission = teamData["prefs"]["disable_file_editing"];
        this.fileDeletePermission = teamData["prefs"]["disable_file_deleting"];
    }
    if (teamData["icon"]) {
        this.icons.image_34 = teamData["icon"]["image_34"];
        this.icons.image_44 = teamData["icon"]["image_44"];
        this.icons.image_68 = teamData["icon"]["image_68"];
        this.icons.image_88 = teamData["icon"]["image_88"];
        this.icons.image_102 = teamData["icon"]["image_102"];
        this.icons.image_132 = teamData["icon"]["image_132"];
        this.icons.image_230 = teamData["icon"]["image_230"];
        this.icons.image_default = teamData["icon"]["image_default"];
    }
    this.version = Math.max(this.version, t);
}

/**
 * @constructor
**/
function SlackChan(chanId) {
    /** @const @type {string} */
    this.id = chanId;
    /** @type {string} */
    this.name;
    /** @type {string} */
    this.created;
    /** @type {SlackUser|SlackBot} */
    this.creator;
    /** @type {boolean} */
    this.archived;
    /** @type {boolean} */
    this.isMember;
    /** @type {number} */
    this.lastRead = 0;
    /** @type {number} */
    this.lastMsg = 0;
    /** @type {Object.<string, SlackBot|SlackUser>} */
    this.members = {};
    /** @type {string|undefined} */
    this.topic;
    /** @type {number|undefined} */
    this.topicTs;
    /** @type {SlackUser|SlackBot|undefined} */
    this.topicCreator;
    /** @type {string|undefined} */
    this.purpose;
    /** @type {number|undefined} */
    this.purposeTs;
    /** @type {SlackUser|SlackBot|undefined} */
    this.purposeCreator;
    /** @type {number} */
    this.version = 0;
}

/**
 * @param {*} chanData
 * @param {SlackData} slackData
**/
SlackChan.prototype.update = function(chanData, slackData, t) {
    if (chanData["name"] !== undefined) this.name = chanData["name"];
    if (chanData["created"] !== undefined) this.created = chanData["created"];
    if (chanData["creator"] !== undefined) this.creator = slackData.getMember(chanData["creator"]);
    if (chanData["is_archived"] !== undefined) this.archived = chanData["is_archived"];
    if (chanData["is_member"] !== undefined) this.isMember = chanData["is_member"];
    if (chanData["last_read"] !== undefined) this.lastRead = Math.max(parseFloat(chanData["last_read"]), this.lastRead);
    if (chanData["last_msg"] !== undefined) this.lastMsg = parseFloat(chanData["last_msg"]);
    if (chanData["latest"]) this.lastMsg = parseFloat(chanData["latest"]["ts"]);
    if (chanData["members"]) {
        this.members = {};
        if (chanData["members"]) for (var i =0, nbMembers = chanData["members"].length; i < nbMembers; i++) {
            var member = slackData.getMember(chanData["members"][i]);
            this.members[member.id] = member;
            member.channels[this.id] = this;
        }
    }
    if (chanData["topic"]) {
        this.topic = chanData["topic"]["value"];
        this.topicCreator = slackData.getMember(chanData["topic"]["creator"]);
        this.topicTs = chanData["topic"]["last_set"];
    }
    if (chanData["purpose"]) {
        this.purpose = chanData["purpose"]["value"];
        this.purposeCreator = slackData.getMember(chanData["purpose"]["creator"]);
        this.purposeTs = chanData["purpose"]["last_set"];
    }
    this.version = Math.max(this.version, t);
}

SlackChan.prototype.setLastMsg = function(lastMsg, t) {
    if (this.lastMsg < lastMsg) {
        this.lastMsg = lastMsg;
        this.version = t;
        return true;
    }
    return false;
}

/**
 * @constructor
 * @param {string} id
**/
function SlackGroup(id) {
    /** @const @type {string} */
    this.id = id;
    /** @type {Object.<string, SlackUser|SlackBot>} */
    this.members = {};
    /** @type {number} */
    this.nbMembers = 0;
    /** @type {string} */
    this.name;
    /** @type {number} */
    this.created;
    /** @type {SlackUser|SlackBot} */
    this.creator;
    /** @type {boolean} */
    this.archived;
    /** @type {number} */
    this.lastRead = 0;
    /** @type {number} */
    this.lastMsg = 0;
    /** @type {string|undefined} */
    this.topic;
    /** @type {number|undefined} */
    this.topicTs;
    /** @type {SlackUser|SlackBot|undefined} */
    this.topicCreator;
    /** @type {string|undefined} */
    this.purpose;
    /** @type {number|undefined} */
    this.purposeTs;
    /** @type {SlackUser|SlackBot|undefined} */
    this.purposeCreator;
    /** @type {number} version */
    this.version = 0;
}

/**
 * @param {SlackData} slack
 * @param {*} groupData
**/
SlackGroup.prototype.update = function(slack, groupData, t) {
    var memberNames = [];
    if (groupData["members"]) {
        /** @type {Object.<string, SlackUser|SlackBot>} */
        this.members = {};
        this.nbMembers = 0;
        for (var i =0, nbMembers = groupData["members"].length; i < nbMembers; i++) {
            var member = slack.getMember(groupData["members"][i]);
            this.members[groupData["members"][i]] = member;
            member.channels[this.id] = this;
            memberNames.push(member.name);
            this.nbMembers++;
        }
        this.name = memberNames.join(", ");
    }
    if (groupData["created"] !== undefined) this.created = groupData["created"];
    if (groupData["creator"] !== undefined) this.creator = slack.getMember(groupData["creator"]);
    if (groupData["is_archived"] !== undefined) this.archived = groupData["is_archived"] || groupData["is_open"] === false;
    if (groupData["last_read"] !== undefined) this.lastRead = Math.max(parseFloat(groupData["last_read"]), this.lastRead);
    if (groupData["last_msg"] !== undefined) this.lastMsg = parseFloat(groupData["last_msg"]);
    else if (groupData["latest"]) this.lastMsg = parseFloat(groupData["latest"]["ts"]);
    if (groupData["topic"]) {
        this.topic = groupData["topic"]["value"];
        this.topicCreator = slack.getMember(groupData["topic"]["creator"]);
        this.topicTs = groupData["topic"]["last_set"];
    }
    if (groupData["purpose"]) {
        this.purpose = groupData["purpose"]["value"];
        this.purposeCreator = slack.getMember(groupData["purpose"]["creator"]);
        this.purposeTs = groupData["purpose"]["last_set"];
    }
    this.version = Math.max(this.version, t);
}

SlackGroup.prototype.setLastMsg = function(lastMsg, t) {
    if (this.lastMsg < lastMsg) {
        this.lastMsg = lastMsg;
        this.version = t;
        return true;
    }
    return false;
}

/**
 * @constructor
 * @param {string} id
 * @param {SlackUser|SlackBot} user
**/
function SlackIms(id, user) {
    /** @const @type {string} */
    this.id = id;
    /** @type {number} */
    this.created;
    /** @type {SlackUser|SlackBot} */
    this.user = user;
    /** @type {number} */
    this.lastRead = 0;
    /** @type {number} */
    this.lastMsg = 0;
    /** @type {boolean} */
    this.archived;
    /** @type {number} */
    this.version = 0;
}

/**
 * @param {SlackUser|SlackBot} user
 * @param {*} imsData
**/
SlackIms.prototype.update = function(user, imsData, t) {
    if (imsData["created"] !== undefined) this.created = imsData["created"];
    if (imsData["last_read"] !== undefined) this.lastRead = Math.max(parseFloat(imsData["last_read"]), this.lastRead);
    if (imsData["last_msg"] !== undefined) this.lastMsg = parseFloat(imsData["last_msg"]);
    if (imsData["latest"]) this.lastMsg = parseFloat(imsData["latest"]["ts"]);
    this.archived = user.deleted;
    this.version = Math.max(this.version, t);
}

SlackIms.prototype.setLastMsg = function(lastMsg, t) {
    if (this.lastMsg < lastMsg) {
        this.lastMsg = lastMsg;
        this.version = t;
        return true;
    }
    return false;
}

/**
 * @constructor
**/
function SlackUser(id) {
    /** @const @type {string} */
    this.id = id;
    /** @type {string} */
    this.name;
    /** @type {boolean} */
    this.deleted;
    /** @type {string} */
    this.status;
    /** @type {string} */
    this.realName;
    /** @type {boolean} */
    this.presence;
    /** @type {Object.<string, string>} */
    this.icons = {
        image_24: ""
        ,image_32: ""
        ,image_48: ""
        ,image_72: ""
        ,image_192: ""
        ,image_512: ""
    };
    /** @type {string} */
    this.email;
    /** @type {string} */
    this.firstName;
    /** @type {string} */
    this.lastName;
    /** @type {!Object.<string, SlackChan|SlackGroup>} */
    this.channels = {};
    /** @type {SlackIms} */
    this.ims = null;
    /** @type {SelfPreferences|null} */
    this.prefs = null;
    /** @type {number} */
    this.version = 0;
}

/**
 * @param {*} userData
**/
SlackUser.prototype.update = function(userData, t) {
    if (userData["name"] !== undefined) this.name = userData["name"];
    if (userData["deleted"] !== undefined) this.deleted = userData["deleted"];
    if (userData["status"] !== undefined) this.status = userData["status"];
    if (userData["real_name"] !== undefined)
        this.realName = userData["real_name"];
    else if (userData["profile"] && userData["profile"]["real_name"] !== undefined)
        this.realName = userData["profile"]["real_name"];
    if (userData["presence"] !== undefined)
        this.presence = userData["presence"] !== 'away';
    if (userData["isPresent"] !== undefined)
        this.presence = userData["isPresent"];
    if (userData["profile"]) {
        this.icons.image_24 = userData["profile"]["image_24"];
        this.icons.image_32 = userData["profile"]["image_32"];
        this.icons.image_48 = userData["profile"]["image_48"];
        this.icons.image_72 = userData["profile"]["image_72"];
        this.icons.image_192 = userData["profile"]["image_192"];
        this.icons.image_512 = userData["profile"]["image_512"];
        this.email = userData["profile"]["email"];
        this.firstName = userData["profile"]["first_name"];
        this.lastName = userData["profile"]["last_name"];
    }
    this.version = Math.max(this.version, t);
}

SlackUser.prototype.setPresence = function(presenceStr, t) {
    this.presence = presenceStr !== 'away';
    this.version = Math.max(t, this.version);
}

/**
 * @constructor
**/
function SelfPreferences() {
    /** @type {Object.<string, number>} */
    this.favoriteEmojis = {};

    /** @type {Array.<string>} */
    this.highlights = [];
}

/**
 * @param {*} prefs
**/
SelfPreferences.prototype.update = function(prefs, t) {
    this.favoriteEmojis = /** @type {Object<string,number>} */ (JSON.parse(prefs["emoji_use"]));
    if (prefs["highlight_words"])
        this.highlights = (prefs["highlight_words"]||"").split(',').filter(function(i) {
            return i.trim() !== '';
        });
    else if (prefs["highlights"])
        this.highlights = prefs["highlights"];
    this.version = Math.max(this.version, t);
}

/**
 * @constructor
**/
function SlackBot(id) {
    /** @const @type {string} */
    this.id = id;
    /** @type {boolean} */
    this.deleted;
    /** @type {string} */
    this.name;
    /** @type {string} */
    this.appId;
    /** @type {Object.<string, string>} */
    this.icons = {
        image_36: ""
        ,image_48: ""
        ,image_72: ""
    };
    /** @type {!Object.<string, SlackGroup|SlackChan>} */
    this.channels;
    /** @type {SlackIms|null} */
    this.ims = null;
    /** @type {SelfPreferences|null} */
    this.prefs = null;
    /** @type {number} */
    this.version = 0;
    /** @type {boolean} */
    this.presence = false;
}

/** @param {*} botData */
SlackBot.prototype.update = function(botData, t) {
    if (botData["deleted"] !== undefined) this.deleted = botData["deleted"];
    if (botData["name"] !== undefined) this.name = botData["name"];
    if (botData["app_id"] !== undefined) this.appId = botData["app_id"];
    if (botData["icons"]) {
        this.icons.image_36 = botData["icons"]["image_36"]
        this.icons.image_48 = botData["icons"]["image_48"]
        this.icons.image_72 = botData["icons"]["image_72"]
    }
    if (botData["presence"] !== undefined)
        this.presence = botData["presence"] !== 'away';
    if (botData["isPresent"] !== undefined)
        this.presence = botData["isPresent"];
    this.version = Math.max(this.version, t);
}

SlackBot.prototype.setPresence = function(presenceStr, t) {
    this.presence = presenceStr !== 'away';
    this.version = Math.max(t, this.version);
}

/**
 * @constructor
 * @param {*} data
**/
function SlackCommand(slack, data) {
    /** @const @type {string} */
    this.desc = data["desc"];
    /** @const @type {string} */
    this.name = data["name"];
    /** @const @type {string} */
    this.type = data["type"];
    /** @const @type {string} */
    this.usage = data["usage"];
    /** @const @type {string|null} */
    this.serviceName = SlackCommand.getServiceName(slack, data);
}

/**
 * @param {SlackData} slack
 * @param {*} data
 * @return {string|null}
**/
SlackCommand.getServiceName = function(slack, data) {
    if (data["service_name"])
        return data["service_name"];
    else if (data["app"]) {
        var bots = slack.getBotsByAppId(data["app"]);
        if (bots)
            for (var i =0; i < bots.length; i++)
                if (bots[i].name)
                    return bots[i].name;
        console.log("Unknown app " +data["app"]);
        return "";
    }
    return "Slack";
}

/**
 * @param {number} t
 * @return {Object}
**/
SlackCommand.prototype.toStatic = function(t) {
    return {
        "desc": this.desc
        ,"name": this.name
        ,"type": this.type
        ,"usage": this.usage
        ,"service_name": this.serviceName
    };
}

/**
 * @constructor
**/
function SlackData(slack) {
    /** @type {SlackTeam} */
    this.team = null;
    /** @type {Object.<string, SlackChan>} */
    this.channels = {};
    /** @type {Object.<string, SlackGroup>} */
    this.groups = {};
    /** @type {Object.<string, SlackIms>} */
    this.ims = {};
    /** @type {Object.<string, SlackUser>} */
    this.users = {};
    /** @type {SlackUser|SlackBot} */
    this.self = null;
    /** @type {Object.<string, SlackBot>} */
    this.bots = {};
    /** @type {{version: number, data: Object.<string, string>}} */
    this.emojis = { version: 0, data: {} };
    /** @type {{version: number, data: Object.<string, SlackCommand>}} */
    this.commands = {version: 0, data: {} };
    /** @type {Object.<string, Object.<string, number>>} */
    this.typing = {};

    /**
     * Node serv handler
     * @type {*}
    **/
    this.slack = slack;

    /** @type {number} */
    this.staticV = 0;
    /** @type {number} */
    this.liveV = 0;
}

/**
 * @return {Object}
**/
SlackTeam.prototype.toStatic = function(t) {
    return t >= this.version ? {} : {
        "id": this.id
        ,"name": this.name
        ,"domain": this.domain
        ,"prefs": {
            "calling_app_id": this.callApp
            ,"calling_app_name": this.callAppName
            ,"disable_file_uploads": this.fileUploadPermission
            ,"disable_file_editing": this.fileEditPermission
            ,"disable_file_deleting": this.fileDeletePermission
        }
        ,"icon": this.icons
    };
};

/**
 * @return {Object}
**/
SlackChan.prototype.toStatic = function(t) {
    if (t >= this.version)
        return null;
    var res = {
        "id": this.id
        ,"name": this.name
        ,"created": this.created
        ,"creator": this.creator.id
        ,"is_archived": this.archived
        ,"is_member": this.isMember
        ,"last_read": this.lastRead
        ,"last_msg": this.lastMsg
    };
    if (this.isMember) {
        res["members"] = this.members ? Object.keys(this.members) : [];
        res["topic"] = {
            "value": this.topic
            ,"creator": this.topicCreator ? this.topicCreator.id : null
            ,"last_set": this.topicTs
        }
        res["purpose"] = {
            "value": this.purpose
            ,"creator": this.purposeCreator ? this.purposeCreator.id : null
            ,"last_set": this.purposeTs
        }
    }
    return res;
};

/**
 * @return {Object}
**/
SlackUser.prototype.toStatic = function(t) {
    return t >= this.version ? null : {
        "id": this.id
        ,"name": this.name
        ,"deleted": this.deleted
        ,"status": this.status
        ,"real_name": this.realName
        ,"isPresent": this.presence
        ,"profile": {
            "email": this.email
            ,"first_name": this.firstName
            ,"last_name": this.lastName

            ,"image_24": this.icons.image_24
            ,"image_32": this.icons.image_32
            ,"image_48": this.icons.image_48
            ,"image_72": this.icons.image_72
            ,"image_192": this.icons.image_192
            ,"image_512": this.icons.image_512
        }
    };
};

SlackGroup.prototype.toStatic = function(t) {
    if (t >= this.version)
        return null;
    var res = {
        "id": this.id
        ,"members": []
        ,"created": this.created
        ,"creator": this.creator.id
        ,"is_archived": this.archived
        ,"last_read": this.lastRead
        ,"last_msg": this.lastMsg
    };
    for (var mId in this.members) {
        res["members"].push(mId);
    }
    if (this.topic) {
        res["topic"] = {
            "value": this.topic
            ,"creator": this.topicCreator.id
            ,"last_set": this.topicTs
        };
    }
    if (this.purpose) {
        res["purpose"] = {
            "value": this.purpose
            ,"creator": this.purposeCreator.id
            ,"last_set": this.purposeTs
        };
    }
    return res;
};

SlackIms.prototype.toStatic = function(t) {
    return t >= this.version ? null: {
        "id": this.id
        ,"created": this.created
        ,"user": this.user.id
        ,"last_read": this.lastRead
        ,"last_msg": this.lastMsg
    };
}

SlackBot.prototype.toStatic = function(t) {
    return t >= this.version ? null : {
        "id": this.id
        ,"deleted": this.deleted
        ,"name": this.name
        ,"app_id": this.appId
        ,"isPresent": this.presence
        ,"icons": {
            "image_36": this.icons.image_36
            ,"image_48": this.icons.image_48
            ,"image_72": this.icons.image_72
        }
    };
}

/**
 * @param {*} data
**/
SlackData.prototype.updateStatic = function(data, t) {
    if (data["bots"]) for (var i =0, nbBots = data["bots"].length; i < nbBots; i++) {
        var botObj = this.bots[data["bots"][i]["id"]];
        if (!botObj)
            botObj = this.bots[data["bots"][i]["id"]] = new SlackBot(data["bots"][i]["id"]);
        botObj.update(data["bots"][i], t);
    }
    if (data["users"]) for (var i =0, nbUsers = data["users"].length; i < nbUsers; i++) {
        var userObj = this.users[data["users"][i]["id"]];
        if (!userObj)
            userObj = this.users[data["users"][i]["id"]] = new SlackUser(data["users"][i]["id"]);
        userObj.update(data["users"][i], t);
    }
    if (data["ims"]) for (var i =0, nbIms = data["ims"].length; i < nbIms; i++) {
        var user = this.getMember(data["ims"][i]["user"]);
        if (user) {
            if (!user.ims)
                this.ims[data["ims"][i]["id"]] = user.ims = new SlackIms(data["ims"][i]["id"], user);
            user.ims.update(user, data["ims"][i], t);
        }
    }
    if (data["channels"]) for (var i =0, nbChan = data["channels"].length; i < nbChan; i++) {
        var chanObj = this.channels[data["channels"][i]["id"]];
        if (!chanObj)
            chanObj = this.channels[data["channels"][i]["id"]] = new SlackChan(data["channels"][i]["id"]);
        chanObj.update(data["channels"][i], this, t);
    }
    for (var i =0, nbGroups = data["groups"].length; i < nbGroups; i++) {
        var groupObj = this.groups[data["groups"][i]["id"]];
        if (!groupObj)
            groupObj = this.groups[data["groups"][i]["id"]] = new SlackGroup(data["groups"][i]["id"]);
        groupObj.update(this, data["groups"][i], t);
    }
    if (data["emojis"]) {
        this.emojis.data = data["emojis"];
        this.emojis.version = t;
    }
    if (data["commands"] !== undefined){
        this.commands.data = {};
        for (var i in data["commands"]) {
            this.commands.data[i] = new SlackCommand(this, data["commands"][i]);
        }
        this.commands.version = t;
    }
    if (data["team"]) {
        if (!this.team) this.team = new SlackTeam(data["team"]["id"]);
        this.team.update(data["team"], t);
    }
    this.staticV = Math.max(this.staticV, t);
    if (data["self"]) {
        this.self = this.getMember(data["self"]["id"]);
        if (!this.self.prefs) this.self.prefs = new SelfPreferences();
        this.self.prefs.update(data["self"]["prefs"], t);
    }
    if (data["typing"] !== undefined) {
        this.typing = {};
        for (var i in data["typing"]) {
            this.typing[i] = {};
            for (var j in data["typing"][i])
                this.typing[i][j] = t;
        }
    }
};

SelfPreferences.prototype.toStatic = function(t) {
    return this.version > t ? null : {
        "emoji_use": JSON.stringify(this.favoriteEmojis)
        ,"highlights": this.highlights
    };
}

/**
 * @param {string} appId
 * @return {Array.<SlackBot>}
**/
SlackData.prototype.getBotsByAppId = function(appId) {
    var bots = [];

    for (var botId in this.bots) {
        if (this.bots[botId].appId === appId) {
            bots.push(this.bots[botId]);
        }
    }
    return bots;
};

/**
 * @param {string} mId
 * @return {SlackUser|SlackBot|null}
**/
SlackData.prototype.getMember = function(mId) {
    return this.users[mId] || this.bots[mId] || null;
};

/**
 * @param {string} chanId
 * @return {SlackChan|SlackGroup|SlackIms|null}
**/
SlackData.prototype.getChannel = function(chanId) {
    return this.channels[chanId] || this.ims[chanId] || this.groups[chanId] || null;
};

/**
 * @param {function((SlackGroup|SlackIms|SlackChan)):(boolean|undefined)} cb should return false to stop iterating
 * @return {boolean} true if iterated through all entities
**/
SlackData.prototype.forEachChans = function(cb) {
    for (var i in this.channels) {
        if (cb(this.channels[i]) === false) return false;
    }
    for (var i in this.ims) {
        if (!cb(this.ims[i]) === false) return false;
    }
    for (var i in this.groups) {
        if (!cb(this.groups[i]) === false) return false;
    }
    return true;
}

/** @return {Object} */
SlackData.prototype.buildStatic = function(t, now) {
    var res = {
        "team": this.team.toStatic(t)
        ,"channels": []
        ,"groups": []
        ,"ims": []
        ,"users": []
        ,"bots": []
        ,"self": {
            "id": this.self.id
            ,"prefs": this.self.prefs.toStatic(t)
        }
        ,"emojis": this.emojis.version > t ? this.emojis.data : undefined
        ,"commands": undefined
        ,"typing": undefined
    };
    if (this.commands.version > t) {
        res["commands"] = {};
        for (var i in this.commands.data)
            res["commands"][i] = this.commands.data[i].toStatic(t);
    }
    for (var chanId in this.channels) {
        var chan = this.channels[chanId].toStatic(t);
        if (chan)
            res["channels"].push(chan);
    }
    for (var userId in this.users) {
        var user = this.users[userId].toStatic(t)
            ,ims = this.users[userId].ims.toStatic(t);

        if (user)
            res["users"].push(user);
        if (ims)
            res["ims"].push(ims);
    }
    for (var botId in this.bots) {
        var bot = this.bots[botId].toStatic(t);
        if (bot)
            res["bots"].push(bot);
    }
    for (var groupId in this.groups) {
        var group = this.groups[groupId].toStatic(t);
        if (group)
            res["groups"].push(group);
    }
    for (var typingChan in this.typing) {
        var tChan = null;
        for (var typingUser in this.typing[typingChan]) {
            if (this.typing[typingChan][typingUser] +3000 >= now) {
                if (!tChan) tChan = {};
                tChan[typingUser] = 1;
            } else {
                delete this.typing[typingChan][typingUser];
            }
        }
        if (tChan) {
            if (res["typing"] === undefined)
                res["typing"] = {};
            res["typing"][typingChan] = tChan;
        }
        else
            delete this.typing[typingChan];
    }
    return res;
};

SlackData.prototype.cleanTyping = function(t) {
    var updated = false;
    for (var typingChan in this.typing) {
        var chanEmpty = true;
        for (var typingUser in this.typing[typingChan]) {
            if (this.typing[typingChan][typingUser] +3000 < t) {
                delete this.typing[typingChan][typingUser];
                updated = true;
            } else {
                chanEmpty = false;
            }
        }
        if (chanEmpty) {
            delete this.typing[typingChan];
            updated = true;
        }
    }
    return updated;
}

/**
 * @param {*} msg
 * @param {number} t
**/
SlackData.prototype.onMessage = function(msg, t) {
    if (msg["type"] === "presence_change") {
        var member = this.getMember(msg["user"]);
        if (member)
            member.setPresence(msg["presence"], t);
        this.staticV = Math.max(this.staticV, t);
    } else if (msg["type"] === "user_typing") {
        this.typing[msg["channel"]] = this.typing[msg["channel"]] || {};
        this.typing[msg["channel"]][msg["user"]] = t;
        this.staticV = Math.max(this.staticV, t);
    } else if (msg["type"] === "im_marked" || msg["type"] === "channel_marked" || msg["type"] === "group_marked") {
        var channel = this.getChannel(msg["channel"]);
        if (channel) {
            channel.lastRead = parseFloat(msg["ts"]);
            this.staticV = channel.version = t;
        }
    }
}

/**
 * @param {number} knownVersion
 * @param {number} now time at update check
 * @return {Object|undefined}
**/
SlackData.prototype.getUpdates = function(knownVersion, now) {
    if (this.staticV > knownVersion)
        return this.buildStatic(knownVersion, now);
    return undefined;
};

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

