/** @const */
var TYPING_DELAY = 6500;

/**
 * @constructor
**/
function ChatInfo(id) {
    /** @const @type {string} */
    this.id = id;
    /** @type {string} */
    this.name;
    /** @type {number} */
    this.version = 0;
};

ChatInfo.prototype.toStatic = function(t) {
    return t >= this.version ? undefined : {
        "id": this.id
        ,"name": this.name
    };
};

ChatInfo.prototype.update = function(data, t) {
    if (data["name"] !== undefined) this.name = data["name"];
    this.version = Math.max(this.version, t);
};

/**
 * @constructor
 * @param {*} data
**/
function Command(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} */
    this.category = data["category"];
};

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

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

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

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

/**
 * @param {*} prefs
**/
SelfPreferences.prototype.update = function(prefs, t) {
    if (prefs["emoji_use"])
        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);
};

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

/**
 * @constructor
**/
function ChatContext() {
    /** @type {ChatInfo} */
    this.team = null;

    /** @type {Object.<string, Room>} */
    this.channels = {};

    /** @type {Object.<string, Chatter>} */
    this.users = {};

    /** @type {Chatter} */
    this.self = null;

    /** @type {{version: number, data: Object.<string, string>}} */
    this.emojis = { version: 0, data: {} };

    /** @type {{version: number, data: Object.<string, Command>}} */
    this.commands = {version: 0, data: {} };

    /** @type {Object.<string, Object.<string, number>>} */
    this.typing = {};

    /** @type {Object<string, boolean>!} capacities */
    this.capacities = {};

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

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

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

ChatContext.prototype.userFactory = function(userData) {
    return new Chatter(userData["id"]);
};

ChatContext.prototype.roomFactory = function(roomData) {
    return roomData["pv"] ?
        new PrivateMessageRoom(roomData["id"], this.users[roomData["user"]]) :
        new Room(roomData["id"]);
};

ChatContext.prototype.teamFactory = function(id) {
    return new ChatInfo(id);
};

ChatContext.prototype.commandFactory = function(data) {
    return new Command(data);
};

/**
 * @param {*} data
 * @param {number} t
 * @param {string=} idPrefix
**/
ChatContext.prototype.updateStatic = function(data, t, idPrefix) {
    idPrefix = idPrefix || "";
    if (data["team"]) {
        if (!this.team) this.team = this.teamFactory(data["team"]["id"]);
        this.team.update(data["team"], t);
    }
    if (data["users"]) for (var i =0, nbUsers = data["users"].length; i < nbUsers; i++) {
        var userObj = this.users[idPrefix +data["users"][i]["id"]];
        if (!userObj)
            userObj = this.users[idPrefix +data["users"][i]["id"]] = this.userFactory(data["users"][i]);
        userObj.update(data["users"][i], t);
    }
    if (data["channels"]) for (var i =0, nbChan = data["channels"].length; i < nbChan; i++) {
        var chanObj = this.channels[idPrefix +data["channels"][i]["id"]];
        if (!chanObj)
            chanObj = this.channels[idPrefix +data["channels"][i]["id"]] = this.roomFactory(data["channels"][i]);
        chanObj.update(data["channels"][i], this, t, idPrefix);
    }
    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] = this.commandFactory(data["commands"][i]);
        }
        this.commands.version = t;
    }
    if (data["self"]) {
        this.self = this.users[idPrefix +data["self"]["id"]] || null;
        if (!this.self.prefs)
            this.self.prefs = new SelfPreferences();
        if (data["self"]["prefs"]) {
            this.self.prefs.update(data["self"]["prefs"], t);
        }
    }
    if (data["capacities"]) {
        this.capacities = {};
        data["capacities"].forEach(function(i) {
            this.capacities[i] = true;
        }, this);
    }
    this.staticV = Math.max(this.staticV, t);
};

ChatContext.prototype.updateTyping = function(typing, now) {
    var updated = false;

    if (this.typing)
        for (var i in this.typing)
            if (typing && !typing[i]) {
                delete (this.typing[i]);
                updated = true;
            }
    if (typing) {
        for (var i in typing) {
            if (this.channels[i]) {
                if (!this.typing[i])
                    this.typing[i] = {};
                for (var j in typing[i]) {
                    if (!this.typing[i][j])
                        updated = true;
                    this.typing[i][j] = now;
                }
            }
        }
    }
    if (updated)
        this.typingVersion = now;
    return updated;
};

/**
 * @param {number} t
**/
ChatContext.prototype.toStatic = function(t) {
    var channels = []
        ,users = [];
    var res = {
        "team": this.team.toStatic(t)
        ,"self": {
            "id": this.self.id
            ,"prefs": this.self.prefs ? this.self.prefs.toStatic(t) : undefined
        }
        ,"emojis": this.emojis.version > t ? this.emojis.data : undefined
    };
    if (this.staticV > t) {
        res["capacities"] = Object.keys(this.capacities);
    }
    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)
            channels.push(chan);
    }
    if (channels.length)
        res["channels"] = channels;
    for (var userId in this.users) {
        var user = this.users[userId].toStatic(t);

        if (user)
            users.push(user);
    }
    if (users.length)
        res["users"] = users;
    return res;
};

/**
 * @param {number} version
 * @return {Object<Object<string, number>>|undefined}
**/
ChatContext.prototype.getWhoIsTyping = function(version, now) {
    var res;

    this.cleanTyping(now);
    if (this.typingVersion > version) {
        res = {};
        for (var typingChan in this.typing) {
            res[typingChan] = {};
            for (var typingUser in this.typing[typingChan])
                res[typingChan][typingUser] = 1;
        }
    }
    return res;
};

/**
 * @param {string} chanNameOrUserName
 * @return {Room|null}
**/
ChatContext.prototype.getRoom = function(chanNameOrUserName) {
    var chans = this.getChannelsWithName(chanNameOrUserName);
    if (chans.length)
        return chans[0];
    var users = this.getUsersWithName(chanNameOrUserName);
    if (users.length)
        for (var i =0, nbUsers = users.length; i < nbUsers; i++)
            if (users[i].privateRoom)
                return users[i].privateRoom;
    return null;
};

ChatContext.prototype.getChannelsWithName = function(name) {
    var chans = [];
    if (name[0] === '#')
        name = name.substr(1);
    for (var i in this.channels)
        if (this.channels[i].name === name)
            chans.push(this.channels[i]);
    return chans;
};

ChatContext.prototype.getUsersWithName = function(name) {
    var users = [];
    if (name[0] === '@')
        name = name.substr(1);
    for (var i in this.users)
        if (this.users[i].getName() === name)
            users.push(this.channels[i]);
    return users;
};

/**
 * @param {number} now
**/
ChatContext.prototype.cleanTyping = function(now) {
    var updated = false;
    for (var typingChan in this.typing) {
        var chanEmpty = true;
        for (var typingUser in this.typing[typingChan]) {
            if (this.typing[typingChan][typingUser] +TYPING_DELAY < now) {
                delete this.typing[typingChan][typingUser];
                updated = true;
            } else {
                chanEmpty = false;
            }
        }
        if (chanEmpty) {
            delete this.typing[typingChan];
            updated = true;
        }
    }
    if (updated)
        this.typingVersion = now;
    return updated;
};

ChatContext.prototype.stopTyping = function(chanId, userId, t) {
    if (this.typing[chanId] && this.typing[chanId][userId]) {
        delete this.typing[chanId][userId];
        var empty = true;
        for (var i in this.typing[chanId]) {
            empty = false;
            break;
        }
        if (empty)
            delete this.typing[chanId];
        this.typingVersion = t;
    }
};

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

