var
    /**
     * Minimum time between 2 notifications (ms)
     * @const
     * @type {number}
    **/
    NOTIFICATION_COOLDOWN = 30 * 1000, //30 sec

    /**
     * Maximum time the notification will stay visible (ms)
     * @const
     * @type {number}
    **/
    NOTIFICATION_DELAY = 5 * 1000, // 5 sec

    MSG_GROUPS = [],

    /** @type {number} */
    lastNotificationSpawn = 0;

function onContextUpdated() {
    var chanListFram = document.createDocumentFragment(),
        sortedChans = DATA.context.getChannelIds(function(chan) {
            return !chan.archived && chan.isMember !== false;
        }),
        starred = [],
        channels = [],
        privs = [],
        priv = [];

    sortedChans.sort(function(a, b) {
        if (a[0] !== b[0]) {
            return a[0] - b[0];
        }
        return DATA.context.getChannel(a).name.localeCompare(DATA.context.getChannel(b).name);
    });
    sortedChans.forEach(function(chanId) {
        var chan = DATA.context.getChannel(chanId),
            chanListItem;
        if (chan instanceof PrivateMessageRoom) {
            if (!chan.user.deleted) {
                if ((chanListItem = createImsListItem(chan))) {
                    if (chan.starred)
                        starred.push(chanListItem);
                    else
                        priv.push(chanListItem);
                }
            }
            //FIXME else remove
        } else {
            if ((chanListItem = createChanListItem(chan))) {
                if (chan.starred)
                    starred.push(chanListItem);
                else if (chan.isPrivate)
                    privs.push(chanListItem);
                else
                    channels.push(chanListItem);
            }
        }
    });
    if (starred.length)
        chanListFram.appendChild(createChanListHeader(locale.starred));
    starred.forEach(function(dom) {
        chanListFram.appendChild(dom);
    });
    if (channels.length)
        chanListFram.appendChild(createChanListHeader(locale.channels));
    channels.forEach(function(dom) {
        chanListFram.appendChild(dom);
    });
    privs.forEach(function(dom) {
        chanListFram.appendChild(dom);
    });
    if (priv.length)
        chanListFram.appendChild(createChanListHeader(locale.privateMessageRoom));
    priv.forEach(function(dom) {
        chanListFram.appendChild(dom);
    });
    document.getElementById(R.id.chanList).textContent = "";
    document.getElementById(R.id.chanList).appendChild(chanListFram);
    setRoomFromHashBang();
    updateTitle();
    if (SELECTED_CONTEXT) {
        createContextBackground(SELECTED_CONTEXT.getChatContext().team.id, SELECTED_CONTEXT.getChatContext().users, function(imgData) {
            document.getElementById(R.id.context).style.backgroundImage = 'url(' +imgData +')';
        });
    }
}

function onTypingUpdated() {
    DATA.context.foreachContext(function(ctx) {
        var typing = ctx.typing;
        for (var chanId in ctx.self.channels) {
            if (!ctx.self.channels[chanId].archived) {
                var chanDom = document.getElementById("room_" +chanId);
                if (typing[chanId])
                    chanDom.classList.add(R.klass.chatList.typing);
                else
                    chanDom.classList.remove(R.klass.chatList.typing);
            }
        }
        for (var userId in ctx.users) {
            var ims = ctx.users[userId].privateRoom;
            if (ims && !ims.archived) {
                var userDom = document.getElementById("room_" +ims.id);
                if (userDom) {
                    if (typing[ims.id])
                        userDom.classList.add(R.klass.chatList.typing);
                    else
                        userDom.classList.remove(R.klass.chatList.typing);
                }
            }
        }
    });
    updateTypingChat();
}

function updateTypingChat() {
    var typing;

    document.getElementById(R.id.typing).textContent = "";
    if (SELECTED_CONTEXT && SELECTED_ROOM && (typing = SELECTED_CONTEXT.getChatContext().typing[SELECTED_ROOM.id])) {
        var areTyping = document.createDocumentFragment(),
            isOutOfSync = false;
        for (var i in typing) {
            var member = DATA.context.getUser(i);
            if (member)
                areTyping.appendChild(makeUserIsTypingDom(member));
            else
                isOutOfSync = true;
        }
        if (isOutOfSync)
            outOfSync();
        document.getElementById(R.id.typing).appendChild(areTyping);
    }
}

function onNetworkStateUpdated(isNetworkWorking) {
    if (isNetworkWorking)
        document.body.classList.remove(R.klass.noNetwork);
    else
        document.body.classList.add(R.klass.noNetwork);
    updateTitle();
}

function onRoomSelected() {
    var name = SELECTED_ROOM.name || (SELECTED_ROOM.user ? SELECTED_ROOM.user.name : undefined);
    if (!name) {
        /** @type {Array.<string>} */
        var members = [];
        SELECTED_ROOM.users.forEach(function(i) {
            members.push(i.name);
        });
        name = members.join(", ");
    }
    var roomLi = document.getElementById("room_" +SELECTED_ROOM.id);
    document.getElementById(R.id.currentRoom.title).textContent = name;
    onRoomUpdated();
    focusInput();

    document.getElementById(R.id.message.file.formContainer).classList.add(R.klass.hidden);
    markRoomAsRead(SELECTED_ROOM);
    if (REPLYING_TO) {
        REPLYING_TO = null;
        onReplyingToUpdated();
    }
    if (EDITING) {
        EDITING = null;
        onReplyingToUpdated();
    }
    updateTypingChat();
}

function onReplyingToUpdated() {
    if (REPLYING_TO) {
        document.body.classList.add(R.klass.replyingTo);
        var domParent = document.getElementById(R.id.message.replyTo),
            closeLink = document.createElement("a");
        closeLink.addEventListener("click", function() {
            REPLYING_TO = null;
            onReplyingToUpdated();
        });
        closeLink.className = R.klass.msg.replyTo.close;
        closeLink.textContent = 'x';
        domParent.textContent = "";
        domParent.appendChild(closeLink);
        domParent.appendChild(REPLYING_TO.duplicateDom());
        focusInput();
    } else {
        document.body.classList.remove(R.klass.replyingTo);
        document.getElementById(R.id.message.replyTo).textContent = "";
        focusInput();
    }
}

function onEditingUpdated() {
    if (EDITING) {
        document.body.classList.add(R.klass.replyingTo);
        var domParent = document.getElementById(R.id.message.replyTo),
            closeLink = document.createElement("a");
        closeLink.addEventListener("click", function() {
            EDITING = null;
            onEditingUpdated();
        });
        closeLink.className = R.klass.msg.replyTo.close;
        closeLink.textContent = 'x';
        domParent.textContent = "";
        domParent.appendChild(closeLink);
        domParent.appendChild(EDITING.duplicateDom());
        document.getElementById(R.id.message.input).value = EDITING.text;
        focusInput();
    } else {
        document.body.classList.remove(R.klass.replyingTo);
        document.getElementById(R.id.message.replyTo).textContent = "";
        focusInput();
    }
}

/**
 * @param {string} chanId
 * @param {string} msgId
 * @param {string} reaction
**/
window['toggleReaction'] = function(chanId, msgId, reaction) {
    var hist = DATA.history[chanId],
        msg,
        ctx;
    if ((hist = DATA.history[chanId]) && (msg = hist.getMessageById(msgId)) && (ctx = DATA.context.getChannelContext(chanId))) {
        if (msg.hasReactionForUser(reaction, ctx.getChatContext().self.id)) {
            removeReaction(chanId, msgId, reaction);
        } else {
            addReaction(chanId, msgId, reaction);
        }
    }
};

/**
 * Try to resolve emoji from customized context
 * @param {string} emoji
 * @return {Element|string}
**/
function tryGetCustomEmoji(emoji) {
    var loop = {};

    if (SELECTED_CONTEXT) {
        var ctx = SELECTED_CONTEXT.getChatContext();

        while (!loop[emoji]) {
            var emojisrc= ctx.emojis.data[emoji];
            if (emojisrc) {
                if (emojisrc.substr(0, 6) == "alias:") {
                    loop[emoji] = true;
                    emoji = emojisrc.substr(6);
                } else {
                    var dom = document.createElement("span");
                    dom.className = R.klass.emoji.custom +' ' +R.klass.emoji.emoji;
                    dom.style.backgroundImage = "url('" +emojisrc +"')";
                    return dom;
                }
            }
            return emoji; // Emoji not found, fallback to std emoji
        }
    }
    return emoji; //loop detected, return first emoji
}

function makeEmojiDom(emojiCode) {
    var emoji = tryGetCustomEmoji(emojiCode);

    if (typeof emoji === "string" && "makeEmoji" in window)
        emoji = window['makeEmoji'](emoji);
    return typeof emoji === "string" ? null : emoji;
}

/**
 * @param {number} unreadhi
 * @param {number} unread
**/
function setFavicon(unreadhi, unread) {
    if (!unreadhi && !unread)
        document.getElementById(R.id.favicon).href = "favicon_ok.png";
    else
        document.getElementById(R.id.favicon).href = "favicon.png?h="+unreadhi+"&m="+unread;
}

function setNetErrorFavicon() {
    document.getElementById(R.id.favicon).href = "favicon_err.png";
}

function updateTitle() {
    var hasHl = HIGHLIGHTED_CHANS.length,
        title = "";

    if (NEXT_RETRY) {
        title = '!' +locale.netErrorShort +' - ';
        setNetErrorFavicon();

    } else if (hasHl) {
        title = "(!" +hasHl +") - ";
        setFavicon(hasHl, hasHl);
    } else {
        var hasUnread = 0;
        DATA.context.foreachChannels(function(i) {
            if (i.lastMsg > i.lastRead)
                hasUnread++;
        });
        if (hasUnread)
            title = "(" +hasUnread +") - ";
        setFavicon(0, hasUnread);
    }
    if (DATA.context.team)
        title += DATA.context.team.name;
    document.title = title;
}

function spawnNotification() {
    if (!("Notification" in window))
    {}
    else if (Notification.permission === "granted") {
        var now = Date.now();

        if (lastNotificationSpawn + NOTIFICATION_COOLDOWN < now) {
            var n = new Notification(locale.newMessage);
            lastNotificationSpawn = now;
            setTimeout(function() {
                n.close();
            }, NOTIFICATION_DELAY);
        }
    }
    else if (Notification.permission !== "denied")
        Notification.requestPermission();
}

function onRoomUpdated() {
    var chatFrag = document.createDocumentFragment(),
        currentRoomId = SELECTED_ROOM.id,
        prevMsg = null,
        firstTsCombo = 0,
        prevMsgDom = null,
        currentMsgGroupDom;

    MSG_GROUPS = [];
    if (DATA.history[currentRoomId])
        DATA.history[currentRoomId].messages.forEach(function(msg) {
            if (!msg.removed) {
                var dom = msg.getDom(),
                    newGroupDom = false;
                if (prevMsg && prevMsg.userId === msg.userId && msg.userId) {
                    if (Math.abs(firstTsCombo -msg.ts) < 30 && !(msg instanceof MeMessage))
                        prevMsgDom.classList.add(R.klass.msg.sameTs);
                    else
                        firstTsCombo = msg.ts;
                } else {
                    firstTsCombo = msg.ts;
                    newGroupDom = true;
                }
                if ((!prevMsg || prevMsg.ts <= SELECTED_ROOM.lastRead) && msg.ts > SELECTED_ROOM.lastRead)
                    dom.classList.add(R.klass.msg.firstUnread);
                else
                    dom.classList.remove(R.klass.msg.firstUnread);
                if (msg instanceof MeMessage) {
                    prevMsg = null;
                    prevMsgDom = null;
                    firstTsCombo = 0;
                    newGroupDom = true;
                    chatFrag.appendChild(dom);
                    currentMsgGroupDom = null;
                } else {
                    if (newGroupDom || !currentMsgGroupDom) {
                        currentMsgGroupDom = createMessageGroupDom(DATA.context.getUser(msg.userId), msg.username);
                        MSG_GROUPS.push(currentMsgGroupDom);
                        chatFrag.appendChild(currentMsgGroupDom);
                    }
                    prevMsg = msg;
                    prevMsgDom = dom;
                    currentMsgGroupDom.content.appendChild(dom);
                }
            } else {
                msg.removeDom();
            }
        });
    var content = document.getElementById(R.id.currentRoom.content);
    //TODO lazy add dom if needed
    content.textContent = "";
    content.appendChild(chatFrag);
    //TODO scroll lock
    content.scrollTop = content.scrollHeight -content.clientHeight;
    if (window.hasFocus)
        markRoomAsRead(SELECTED_ROOM);
}

function onMsgClicked(target, msg) {
    if (target.classList.contains(R.klass.msg.hover.reply)) {
        if (EDITING) {
            EDITING = null;
            onEditingUpdated();
        }
        if (REPLYING_TO !== msg) {
            REPLYING_TO = msg;
            onReplyingToUpdated();
        }
    } else if (target.classList.contains(R.klass.msg.hover.reaction)) {
        EMOJI_BAR.spawn(document.body, {
                selectedRoomId: SELECTED_ROOM.id,
                msgId: msg.id
            }, function(emoji) {
                if (emoji)
                    addReaction(this.selectedRoomId, this.msgId, emoji);
        });
    } else if (target.classList.contains(R.klass.msg.hover.edit)) {
        if (REPLYING_TO) {
            REPLYING_TO = null;
            onReplyingToUpdated();
        }
        if (EDITING !== msg) {
            EDITING = msg;
            onEditingUpdated();
        }
    } else if (target.classList.contains(R.klass.msg.hover.remove)) {
        //TODO prompt confirm
        if (REPLYING_TO) {
            REPLYING_TO = null;
            onReplyingToUpdated();
        }
        if (EDITING) {
            EDITING = null;
            onEditingUpdated();
        }
        removeMsg(SELECTED_ROOM, msg);
    }
}

function chatClickDelegate(e) {
    var target = e.target,
        getMessageId = function(e, target) {
            target = target || e.target;
            while (target !== e.currentTarget && target) {
                if (target.id && target.classList.contains(R.klass.msg.item)) {
                    return target.id;
                }
                target = target.parentElement;
            }
        };
    while (target !== e.currentTarget && target) {
        if (target.classList.contains(R.klass.msg.hover.container)) {
            return;
        }
        var messageId,
            msg;
        if (target.parentElement && target.classList.contains(R.klass.msg.attachment.actionItem)) {
            var attachmentIndex = target.dataset["attachmentIndex"],
                actionIndex = target.dataset["actionIndex"];

            messageId = getMessageId(e, target);
            if (messageId && attachmentIndex !== undefined && actionIndex !== undefined) {
                messageId = messageId.substr(messageId.lastIndexOf("_") +1);
                msg = DATA.history[SELECTED_ROOM.id].getMessageById(messageId);

                if (msg && msg.attachments[attachmentIndex] && msg.attachments[attachmentIndex].actions && msg.attachments[attachmentIndex].actions[actionIndex]) {
                    confirmAction(SELECTED_ROOM.id, msg, msg.attachments[attachmentIndex], msg.attachments[attachmentIndex].actions[actionIndex]);
                }
                return;
            }
        }
        if (target.parentElement && target.parentElement.classList.contains(R.klass.msg.hover.container)) {
            if ((messageId = getMessageId(e, target))) {
                messageId = messageId.substr(messageId.lastIndexOf("_") +1);
                msg = DATA.history[SELECTED_ROOM.id].getMessageById(messageId);

                if (msg)
                    onMsgClicked(target, msg);
            }
            return;
        }
        target = target.parentElement;
    }
}

function confirmAction(roomId, msg, attachmentObject, actionObject) {
    var confirmed = function() {
        var payload = getActionPayload(roomId, msg, attachmentObject, actionObject);

        sendCommand(payload, msg.userId);
    };
    if (actionObject["confirm"]) {
        (new ConfirmDialog(actionObject["confirm"]["title"], actionObject["confirm"]["text"]))
            .setButtonText(actionObject["confirm"]["ok_text"], actionObject["confirm"]["dismiss_text"])
            .onConfirm(confirmed)
            .spawn();
    } else {
        confirmed();
    }
}

function focusInput() {
    document.getElementById(R.id.message.input).focus();
}

function setRoomFromHashBang() {
    var hashId = document.location.hash.substr(1),
        room = DATA.context.getChannel(hashId);

    if (room && room !== SELECTED_ROOM)
        selectRoom(room);
    else {
        var user = DATA.context.getUser(hashId);
        if (user && user.ims)
            selectRoom(user.ims);
    }
}

function updateAuthorAvatarImsOffset() {
    var chatDom = document.getElementById(R.id.currentRoom.content),
        chatTop = chatDom.getBoundingClientRect().top;

    MSG_GROUPS.forEach(function(group) {
        var imgDom = group.authorImg,
            imgSize = imgDom.clientHeight,
            domRect = group.getBoundingClientRect(),
            _top = 0;

        imgDom.style.top = Math.max(0, Math.min(chatTop -domRect.top, domRect.height -imgSize -(imgSize /2))) +"px";
    });
}

document.addEventListener('DOMContentLoaded', function() {
    initLang();

    // FIXME load config
    initHljs();

    document.getElementById(R.id.currentRoom.content).addEventListener("click", chatClickDelegate);
    window.addEventListener("hashchange", function(e) {
        if (document.location.hash && document.location.hash[0] === '#') {
            setRoomFromHashBang();
        }
    });
    document.getElementById(R.id.message.file.cancel).addEventListener("click", function(e) {
        e.preventDefault();
        document.getElementById(R.id.message.file.error).classList.add(R.klass.hidden);
        document.getElementById(R.id.message.file.formContainer).classList.add(R.klass.hidden);
        document.getElementById(R.id.message.file.fileInput).value = "";
        return false;
    });
    document.getElementById(R.id.message.file.form).addEventListener("submit", function(e) {
        e.preventDefault();
        var fileInput = document.getElementById(R.id.message.file.fileInput),
            filename = fileInput.value;

        if (filename) {
            filename = filename.substr(filename.lastIndexOf('\\') +1);
            uploadFile(SELECTED_ROOM, filename, fileInput.files[0], function(errorMsg) {
                var error = document.getElementById(R.id.message.file.error);
                if (errorMsg) {
                    error.textContent = errorMsg;
                    error.classList.remove(R.klass.hidden);
                } else {
                    error.classList.add(R.klass.hidden);
                    document.getElementById(R.id.message.file.fileInput).value = "";
                    document.getElementById(R.id.message.file.formContainer).classList.add(R.klass.hidden);
                }
            });
        }
        return false;
    });
    document.getElementById(R.id.message.file.bt).addEventListener("click", function(e) {
        e.preventDefault();
        if (SELECTED_ROOM) {
            document.getElementById(R.id.message.file.formContainer).classList.remove(R.klass.hidden);
        }
        return false;
    });
    document.getElementById(R.id.message.form).addEventListener("submit", function(e) {
        e.preventDefault();
        var input =document.getElementById(R.id.message.input);
        if (SELECTED_ROOM && input.value) {
            if (onTextEntered(input.value)) {
                input.value = "";
                if (REPLYING_TO) {
                    REPLYING_TO = null;
                    onReplyingToUpdated();
                }
                if (EDITING) {
                    EDITING = null;
                    onReplyingToUpdated();
                }
                document.getElementById(R.id.message.slashComplete).textContent = '';
            }
        }
        focusInput();
        return false;
    });
    window.addEventListener('blur', function() {
        window.hasFocus = false;
    });
    window.addEventListener('focus', function() {
        window.hasFocus = true;
        lastNotificationSpawn = 0;
        if (SELECTED_ROOM)
            markRoomAsRead(SELECTED_ROOM);
        focusInput();
    });
    document.getElementById(R.id.currentRoom.content).addEventListener('scroll', updateAuthorAvatarImsOffset);

    var lastKeyDown = 0;
    document.getElementById(R.id.message.input).addEventListener('input', function() {
        if (SELECTED_ROOM) {
            var now = Date.now();
            if (lastKeyDown + 3000 < now && (SELECTED_CONTEXT.getChatContext().self.presence || (SELECTED_ROOM instanceof PrivateMessageRoom))) {
                sendTyping(SELECTED_ROOM);
                lastKeyDown = now;
            }
            var /** @type {Array<Command|{names: Array<string>!, desc: string!, usage: string!, category: string!, exec: Function!}>} */
                commands = [],
                input = this.value;
            if (this.value[0] === '/') {
                var endCmd = input.indexOf(' '),
                    inputFinished = endCmd !== -1;
                endCmd = endCmd === -1 ? input.length : endCmd;
                var inputCmd = input.substr(0, endCmd);

                if (inputFinished) {
                    var currentClientCmd = CLIENT_COMMANDS.getCommand(inputCmd);
                    if (currentClientCmd)
                        commands.push(currentClientCmd);
                } else {
                    commands = CLIENT_COMMANDS.getCommandsStartingWith(inputCmd);
                }
                var availableCommands = (SELECTED_CONTEXT ? SELECTED_CONTEXT.getChatContext().commands.data : {});
                for (var currentCmdId in availableCommands) {
                    var currentCmd = availableCommands[currentCmdId];
                    if ((!inputFinished && currentCmd.name.substr(0, endCmd) === inputCmd) ||
                        (inputFinished && currentCmd.name === inputCmd))
                        commands.push(currentCmd);
                }
            }
            commands.sort(function(a, b) {
                return a.category.localeCompare(b.category) || a.name.localeCompare(b.name);
            });
            var slashDom = document.getElementById(R.id.message.slashComplete),
                slashFrag = document.createDocumentFragment(),
                prevService;
            slashDom.textContent = '';
            for (var i =0, nbCmd = commands.length; i < nbCmd; i++) {
                var command = commands[i];
                if (prevService !== command.category) {
                    prevService = command.category;
                    slashFrag.appendChild(createSlashAutocompleteHeader(command.category));
                }
                slashFrag.appendChild(createSlashAutocompleteDom(command));
            }
            slashDom.appendChild(slashFrag);
        }
    });

    window.hasFocus = true;

    //Emoji closure
    (function() {
        var emojiButton = document.getElementById(R.id.message.emoji);
        if ('makeEmoji' in window) {
            var emojiDom = window['makeEmoji']('smile');

            if (emojiDom) {
                emojiButton.innerHTML = "<span class='" +R.klass.emoji.small +"'>" +emojiDom.outerHTML +"</span>";
            } else {
                emojiButton.style.backgroundImage = 'url("smile.svg")';
            }

            emojiDom = window['makeEmoji']('paperclip');
            if (emojiDom) {
                document.getElementById(R.id.message.file.bt).innerHTML = "<span class='" +R.klass.emoji.small +"'>" +emojiDom.outerHTML +"</span>";
            } else {
                document.getElementById(R.id.message.file.bt).style.backgroundImage = 'url("public/paperclip.svg")';
            }

            emojiButton.addEventListener("click", function() {
                if (SELECTED_CONTEXT)
                    EMOJI_BAR.spawn(document.body, SELECTED_CONTEXT.getChatContext(), function(e) {
                        if (e) document.getElementById(R.id.message.input).value += ":"+e+":";
                        focusInput();
                    });
            });
        } else {
            emojiButton.classList.add(R.klass.hidden);
        }
    })();

    startPolling();
});

