
/**
 * @constructor
 * @param {*} e
 * @param {number} ts
**/
function SlackMessage(e, ts) {
    /** @const @type {string} **/
    this.userId = e["user"] || e["bot_id"];

    /** @type {string} */
    this.username = e["username"];

    /** @const @type {string} **/
    this.id = e["ts"];

    /** @const @type {number} **/
    this.ts = parseFloat(e["ts"]);

    /** @type {string} */
    this.text = e["text"] || "";

    /** @type {Array.<*>} */
    this.attachments = e["attachments"] || [];

    /** @type {boolean} */
    this.starred = !!e["is_starred"];

    /** @type {boolean} */
    this.pinned = false; // TODO

    /** @type {boolean} */
    this.edited = !!e["edited"];

    /** @type {boolean} */
    this.removed = e["removed"] || false;

    /** @const @type {boolean} */
    this.isMeMessage = e["subtype"] === "me_message" || e["isMeMessage"];

    /**
     * for each emoji code, an array of user id
     * @type {Object.<string, Array.<string>>}
    **/
    this.reactions = {};

    /** @type {number} */
    this.version = ts;

    var _this = this;
    e["reactions"] && e["reactions"].forEach(function(r) {
        _this.reactions[r["name"]] = [];
        r["users"].forEach(function(userId) {
            _this.reactions[r["name"]].push(userId);
        });
    });
}

SlackMessage.prototype.update = function(e, ts) {
    if (e) {
        this.text = e["text"] || "";
        if (e["attachments"]) this.attachments = e["attachments"];
        this.starred = !!e["is_starred"];
        this.pinned = false; // TODO
        this.edited = !!e["edited"];
        this.removed = !!e["removed"];

        var _this = this;
        if (e["reactions"]) {
            this.reactions = {};
            e["reactions"].forEach(function(r) {
                _this.reactions[r["name"]] = [];
                r["users"].forEach(function(userId) {
                    _this.reactions[r["name"]].push(userId);
                });
            });
        }
    } else {
        this.removed = true;
    }
    this.version = ts;
}

/**
 * @constructor
 * @param {SlackChan|SlackGroup|SlackIms|string} room or roomId
 * @param {number} keepMessages number of messages to keep in memory
 * @param {Array|undefined} evts
 * @param {number|undefined} now
**/
function SlackHistory(room, keepMessages, evts, now) {
    /** @type {string} */
    this.id = typeof room === "string" ? room : room.id;
    /** @type {Array.<SlackMessage>} */
    this.messages = [];
    /** @type number */
    this.v = 0;

    /** @const @type {number} */
    this.keepMessages = keepMessages;

    if (evts) this.pushAll(evts, now);
}

/** @return {*} */
SlackMessage.prototype.toStatic = function() {
    var reactions = [];
    for (var reaction in this.reactions) {
        reactions.push({
            "name": reaction
            ,"users": this.reactions[reaction]
        });
    }
    return {
        "user": this.userId
        ,"username": this.username
        ,"ts": this.id
        ,"text": this.text
        ,"attachments": this.attachments
        ,"is_starred": this.is_starred || undefined
        ,"edited": this.edited
        ,"removed": this.removed
        ,"reactions": reactions
        ,"isMeMessage": this.isMeMessage
    };
}

/**
 * @param {string} reaction
 * @param {string} userId
 * @param {number} ts
**/
SlackMessage.prototype.addReaction = function(reaction, userId, ts) {
    if (!this.reactions[reaction])
        this.reactions[reaction] = [];
    this.reactions[reaction].push(userId);
    this.version = ts;
}

/**
 * @param {string} reaction
 * @param {string} userId
 * @param {number} ts
**/
SlackMessage.prototype.removeReaction = function(reaction, userId, ts) {
    var updated = false;
    if (this.reactions[reaction]) {
        if (this.reactions[reaction].length === 1 && this.reactions[reaction][0] === userId) {
            delete this.reactions[reaction];
            updated = true;
        }
        else {
            this.reactions[reaction] = this.reactions[reaction].filter(function(i) {
                if (i !== userId)
                    return false;
                updated = true;
                return true;
            });
        }
    }
    if (updated)
        this.version = ts;
}

SlackMessage.prototype.hasReactionForUser = function(reaction, userId) {
    if (this.reactions[reaction])
        return this.reactions[reaction].indexOf(userId) !== -1;
    return false;
}

/**
 * @param {Array} evts
 * @return {number} biggest ts
**/
SlackHistory.prototype.pushAll = function(evts, t) {
    var result = 0;

    evts.forEach(function(e) {
        result = Math.max(this.push(e, t), result);
    }.bind(this));
    this.resort();
    return result;
}

/**
 * @return {number} ts
**/
SlackHistory.prototype.push = function(ev, t) {
    var msg;
    if (!ev["type"] || ev["type"] === "message") {
        var exists = false
            ,targetId = ev["ts"]
            ,modifArg = ev;

        if (ev["subtype"] === "message_changed" && ev["previous_message"]) {
            targetId = ev["previous_message"]["ts"];
            modifArg = ev["message"]
        } else if (ev["subtype"] === "message_deleted" && ev["previous_message"]) {
            targetId = ev["previous_message"]["ts"];
            modifArg = null;
        }
        for (var i =0, nbMsg = this.messages.length; i < nbMsg; i++) {
            if (this.messages[i].id === targetId) {
                this.messages[i].update(modifArg, t);
                exists = true;
                break;
            }
        }
        if (!exists) {
            msg = new SlackMessage(ev, t);
            this.messages.push(msg);
        }
    } else if (ev["type"] === "reaction_added") {
        msg = this.addReaction(ev["reaction"], ev["user"], ev["item"]["ts"], t);
    } else if (ev["type"] === "reaction_removed") {
        msg = this.removeReaction(ev["reaction"], ev["user"], ev["item"]["ts"], t);
    } else {
        return 0;
    }
    while (this.messages.length > this.keepMessages)
        this.messages.shift();
    return msg ? msg.ts : 0;
}

SlackHistory.prototype.lastMessage = function() {
    return this.messages[this.messages.length -1];
}

/**
 * @param {string} reaction
 * @param {string} userId
 * @param {string} msgId
 * @param {number} ts
 * @return {SlackMessage|null}
**/
SlackHistory.prototype.addReaction = function(reaction, userId, msgId, ts) {
    var msg = this.getMessageById(msgId);
    if (msg)
        msg.addReaction(reaction, userId, ts);
    return msg;
}

/**
 * @param {string} reaction
 * @param {string} userId
 * @param {string} msgId
 * @param {number} ts
 * @return {SlackMessage|null}
**/
SlackHistory.prototype.removeReaction = function(reaction, userId, msgId, ts) {
    var msg = this.getMessageById(msgId);
    if (msg)
        msg.removeReaction(reaction, userId, ts);
    return msg;
}

/**
 * @param {number} ts
 * @return {SlackMessage|null}
**/
SlackHistory.prototype.getMessage = function(ts) {
    for (var i =0, nbMessages = this.messages.length; i < nbMessages && ts >= this.messages[i].ts; i++) {
        if (this.messages[i].ts === ts)
            return this.messages[i];
    }
    return null;
}

/**
 * @param {string} id
 * @return {SlackMessage|null}
**/
SlackHistory.prototype.getMessageById = function(id) {
    for (var i =0, nbMessages = this.messages.length; i < nbMessages; i++) {
        if (this.messages[i].id == id)
            return this.messages[i];
    }
    return null;
}

/**
 * @return {Array.<*>}
**/
SlackHistory.prototype.toStatic = function(knownVersion) {
    var result = [];

    for (var i = this.messages.length -1; i >= 0; i--) {
        if (this.messages[i].version > knownVersion)
            result.push(this.messages[i].toStatic());
    }
    return result;
}

SlackHistory.prototype.resort = function() {
    this.messages.sort(function(a, b) {
        return a.ts -b.ts;
    });
};

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

