/* jshint sub: true */

/**
 * @constructor
 * @extends {RoomHistory}
 * @param {Room|string} room or roomId
 * @param {number} keepMessages number of messages to keep in memory
 * @param {Array|undefined} evts
 * @param {number|undefined} now
**/
function UiRoomHistory(room, keepMessages, evts, now) {
    RoomHistory.call(this, room, keepMessages, 0, evts, now);
}
UiRoomHistory.prototype = Object.create(RoomHistory.prototype);
UiRoomHistory.prototype.constructor = UiRoomHistory;

UiRoomHistory.prototype.messageFactory = function(ev, ts) {
    if (ev["isMeMessage"] === true)
        return new UiMeMessage(this.id, ev, ts);
    if (ev["isNotice"] === true)
        return new UiNoticeMessage(this.id, ev, ts);
    return new UiMessage(this.id, ev, ts);
};

UiRoomHistory.prototype.invalidateAllMessages = function() {
    this.messages.forEach(function(m) {
        m.invalidate();
    });
};

/** @interface */
function IUiMessage() {}

/**
 * @return {Element}
**/
IUiMessage.prototype.getDom = function() {};

/**
 * @return {IUiMessage}
**/
IUiMessage.prototype.removeDom = function() {};

/**
 * @return {IUiMessage}
**/
IUiMessage.prototype.invalidate = function() {};

/**
 * @return {IUiMessage}
**/
IUiMessage.prototype.createDom = function() {};

/**
 * @return {IUiMessage}
**/
IUiMessage.prototype.updateDom = function() {};

/**
 * @return {Element}
**/
IUiMessage.prototype.duplicateDom = function() {};

/** @const */
var AbstractUiMessage = (function() {
    var updateReactions = function(_this, channelId) {
        var reactionFrag = document.createDocumentFragment();

        if (_this.reactions) {
            for (var reaction in _this.reactions) {
                var reac = createReactionDom(channelId, _this.id, reaction, _this.reactions[reaction]);
                if (reac)
                    reactionFrag.appendChild(reac);
            }
        }
        _this.dom.reactions.textContent = '';
        _this.dom.reactions.appendChild(reactionFrag);
    },

    _linkFilter = function(msgContext, str) {
        var sep = str.indexOf('|'),
            link,
            text,
            style,
            classes,
            isInternal = false;

        if (sep === -1) {
            link = str;
        } else {
            link = str.substr(0, sep);
            text = str.substr(sep +1);
        }
        var newLink;
        if (link[0] === '@') {
            newLink = msgContext.context.getId() +'|' +link.substr(1);
            var user = DATA.context.getUser(newLink);

            if (user) {
                isInternal = true;
                link = '#' +user.privateRoom.id;
                text = '@' +user.getName();
                style = "background-color:" +makeUserColor(user.getName());
                classes = [ R.klass.msg.linkuser, R.klass.msg.link ];
            } else {
                return null;
            }
        } else if (link[0] === '#') {
            newLink = msgContext.context.getId() +'|' +link.substr(1);
            var chan = DATA.context.getChannel(newLink);

            if (chan) {
                isInternal = true;
                link = '#' +newLink;
                text = '#' +chan.name;
                classes = [ R.klass.msg.linkchan, R.klass.msg.link ];
            } else {
                return null;
            }
        } else {
            if (!link.match(/^(https?|mailto):\/\//i))
                return null;
            classes = [ R.klass.msg.link ];
            isInternal = false;
        }
        return {
            link: link,
            text: text || link,
            style: style,
            classes: classes,
            isInternal: isInternal
        };
    },

    _formatText = function(_this, text) {
        return formatText(text, {
            highlights: _this.context.self.prefs.highlights,
            emojiFormatFunction: function(emoji) {
                if (emoji[0] === ':' && emoji[emoji.length -1] === ':')
                    emoji = emoji.substr(1, emoji.length -2);
                var emojiDom = makeEmojiDom(emoji);
                if (emojiDom) {
                    var domParent = document.createElement("span");
                    domParent.className = R.klass.emoji.small;
                    domParent.appendChild(emojiDom);
                    return domParent.outerHTML;
                }
                return null;
            },
            linkFilter: function(link) {
                return _linkFilter(_this, link);
            }
        });
    },

    updateAttachments = function(_this, channelId) {
        var attachmentFrag = document.createDocumentFragment();

        for (var i =0, nbAttachments = _this.attachments.length; i < nbAttachments; i++) {
            var attachment = _this.attachments[i];
            if (attachment) {
                var domAttachment = createAttachmentDom(channelId, _this, attachment, i);
                if (domAttachment)
                    attachmentFrag.appendChild(domAttachment);
            }
        }
        _this.dom.attachments.textContent = '';
        _this.dom.attachments.appendChild(attachmentFrag);
    },

    updateHover = function(_this, channelId) {
        if (_this.dom.hover.hoverStar)
            _this.dom.hover.hoverStar.style.backgroundImage = _this.starred ? 'url("star_full.png")' : 'url("star_empty.png")';
    },

    updateCommon = function(_this, sender) {
        _this.dom.ts.innerHTML = locale.formatDate(_this.ts);
        _this.dom.textDom.innerHTML = _formatText(_this, _this.text);
        _this.dom.authorName.textContent = sender ? sender.getName() : (_this.username || "?");
    };

    return {
        /** @type {Element|null} *
        dom: null,

        /** @type {boolean} *
        uiNeedRefresh: true,
        */

        /** @param {IUiMessage} _this */
        invalidate: function(_this) {
            _this.uiNeedRefresh = true;
            return _this;
        },

        /** @param {UiMessage|UiMeMessage|UiNoticeMessage} _this */
        removeDom: function(_this) {
            if (_this.dom && _this.dom.parentElement) {
                _this.dom.remove();
                delete(_this.dom);
            }
            return _this;
        },

        /** @param {UiMessage|UiMeMessage|UiNoticeMessage} _this */
        getDom: function(_this) {
            if (!_this.dom) {
                _this.createDom().updateDom();

            } else if (_this.uiNeedRefresh) {
                _this.uiNeedRefresh = false;
                _this.updateDom();
            }
            return _this.dom;
        },

        /** @param {UiMessage|UiMeMessage|UiNoticeMessage} _this */
        updateDom: function(_this) {
            var sender = DATA.context.getUser(_this.userId);

            updateCommon(_this, sender);
            updateAttachments(_this, _this.channelId);
            updateReactions(_this, _this.channelId);
            updateHover(_this, _this.channelId);
            if (_this.edited) {
                _this.dom.edited.innerHTML = locale.edited(_this.edited);
                _this.dom.classList.add(R.klass.msg.editedStatus);
            }
            return _this;
        },

        duplicateDom: function(_this) {
            return _this.getDom().cloneNode(true);
        },

        formatText: function(_this, text) {
            return _formatText(_this, text);
        }
    };
})();

/**
 * @constructor
 * @implements {IUiMessage}
 * @extends {MeMessage}
 * @param {*} ev
 * @param {number} ts
**/
function UiMeMessage(channelId, ev, ts) {
    // Extends AbstractUiMessage and MeMessage
    MeMessage.call(this, ev, ts);

    /** @const @type {ChatContext} */
    this.context = DATA.context.getChannelContext(channelId).getChatContext();

    /** @type {string} @const */
    this.channelId = channelId;
    this.dom = AbstractUiMessage.dom;
    this.uiNeedRefresh = AbstractUiMessage.uiNeedRefresh;
}
UiMeMessage.prototype = Object.create(MeMessage.prototype);
UiMeMessage.prototype.constructor = UiMeMessage;

UiMeMessage.prototype.invalidate = function() {
    return AbstractUiMessage.invalidate(this);
};

UiMeMessage.prototype.formatText = function(text) {
    return AbstractUiMessage.formatText(this, text);
};

UiMeMessage.prototype.removeDom = function() {
    return AbstractUiMessage.removeDom(this);
};

UiMeMessage.prototype.getDom = function() {
    return AbstractUiMessage.getDom(this);
};

UiMeMessage.prototype.createDom = function() {
    this.dom = doCreateMessageDom(this);
    this.dom.classList.add(R.klass.msg.meMessage);
    return this;
};

UiMeMessage.prototype.duplicateDom = function() {
    return AbstractUiMessage.duplicateDom(this);
};

UiMeMessage.prototype.updateDom = function() {
    AbstractUiMessage.updateDom(this);
    return this;
};

UiMeMessage.prototype.update = function(ev, ts) {
    MeMessage.prototype.update.call(this, ev, ts);
    this.invalidate();
};

/**
 * @constructor
 * @implements {IUiMessage}
 * @extends {Message}
 * @param {*} ev
 * @param {number} ts
**/
function UiMessage(channelId, ev, ts) {
    // Extends AbstractUiMessage and Message
    Message.call(this, ev, ts);

    /** @const @type {ChatContext} */
    this.context = DATA.context.getChannelContext(channelId).getChatContext();

    /** @type {string} @const */
    this.channelId = channelId;
    /** @type {Element} */
    this.dom = AbstractUiMessage.dom;
    /** @type {boolean} */
    this.uiNeedRefresh = AbstractUiMessage.uiNeedRefresh;
}
UiMessage.prototype = Object.create(Message.prototype);
UiMessage.prototype.constructor = UiMessage;

UiMessage.prototype.invalidate = function() {
    return AbstractUiMessage.invalidate(this);
};

UiMessage.prototype.formatText = function(text) {
    return AbstractUiMessage.formatText(this, text);
};

UiMessage.prototype.removeDom = function() {
    return AbstractUiMessage.removeDom(this);
};

UiMessage.prototype.getDom = function() {
    return AbstractUiMessage.getDom(this);
};

UiMessage.prototype.createDom = function() {
    this.dom = doCreateMessageDom(this);
    return this;
};

UiMessage.prototype.duplicateDom = function() {
    return AbstractUiMessage.duplicateDom(this);
};

UiMessage.prototype.updateDom = function() {
    AbstractUiMessage.updateDom(this);
    return this;
};

UiMessage.prototype.update = function(ev, ts) {
    Message.prototype.update.call(this, ev, ts);
    this.invalidate();

    var match = this.text.match(/^<?https:\/\/www\.openstreetmap\.org\/\?mlat=(-?[0-9\.]+)(&amp;|&)mlon=(-?[0-9\.]+)(&amp;|&)macc=([0-9\.]+)[^\s]*/);
    if (match) {
        var lat = match[1],
            lon = match[3],
            acc = match[5];
        this.text = this.text.substr(0, match.index) +this.text.substr(match.index +match[0].length).trim();
        this.attachments.unshift({
            "color": "#008000",
            "text": match[0],
            "footer": "Open Street Map",
            "footer_icon": "https://www.openstreetmap.org/assets/favicon-32x32-36d06d8a01933075bc7093c9631cffd02d49b03b659f767340f256bb6839d990.png",
            "geo": {
                "latitude": match[1],
                "longitude": match[3],
                "accuracy": match[5]
            }
        });
    }

};

/**
 * @constructor
 * @implements {IUiMessage}
 * @extends {NoticeMessage}
 * @param {*} ev
 * @param {number} ts
**/
function UiNoticeMessage(channelId, ev, ts) {
    // Extends AbstractUiMessage and NoticeMessage
    NoticeMessage.call(this, ev, ts);

    /** @const @type {ChatContext} */
    this.context = DATA.context.getChannelContext(channelId).getChatContext();

    /** @type {string} @const */
    this.channelId = channelId;

    /** @type {Element} */
    this.dom; // jshint ignore:line

    /** @type {Element} */
    this.domWrapper = null;

    /** @type {boolean} */
    this.uiNeedRefresh = true;
}
UiNoticeMessage.prototype = Object.create(NoticeMessage.prototype);
UiNoticeMessage.prototype.constructor = UiNoticeMessage;

UiNoticeMessage.prototype.invalidate = function() {
    return AbstractUiMessage.invalidate(this);
};

UiNoticeMessage.prototype.formatText = function(text) {
    return AbstractUiMessage.formatText(this, text);
};

UiNoticeMessage.prototype.removeDom = function() {
    if (this.domWrapper && this.domWrapper.parentElement) {
        this.domWrapper.remove();
        delete(this.domWrapper);
    }
    if (this.dom)
        delete(this.dom);
    return this;
};

UiNoticeMessage.prototype.getDom = function() {
    AbstractUiMessage.getDom(this);
    return this.domWrapper;
};

UiNoticeMessage.prototype.duplicateDom = function() {
    return this.domWrapper.cloneNode(true);
};

UiNoticeMessage.prototype.createDom = function() {
    this.dom = doCreateMessageDom(this);
    this.domWrapper = document.createElement("span");
    this.dom.classList.add(R.klass.msg.notice);
    this.domWrapper.className = R.klass.msg.notice;
    this.domWrapper.textContent = locale.onlyVisible;
    this.domWrapper.appendChild(this.dom);
    return this;
};

UiNoticeMessage.prototype.updateDom = function() {
    AbstractUiMessage.updateDom(this);
    return this;
};

UiNoticeMessage.prototype.update = function(ev, ts) {
    NoticeMessage.prototype.update.call(this, ev, ts);
    this.invalidate();
};

/**
 * @param {string} input
 * @param {boolean} isMe
 * @return {Element}
**/
function createTmpMsgDom(input, isMe) {
    var dom = doCreateMessageDom(null),
        sender = SELECTED_CONTEXT.self;

    dom.classList.add(R.klass.msg.pending);
    if (isMe)
        dom.classList.add(R.klass.msg.meMessage);
    dom.textDom.innerHTML = formatText(input, {
        emojiFormatFunction: function(emoji) {
            if (emoji[0] === ':' && emoji[emoji.length -1] === ':')
                emoji = emoji.substr(1, emoji.length -2);
            var emojiDom = makeEmojiDom(emoji);
            if (emojiDom) {
                var domParent = document.createElement("span");
                domParent.className = R.klass.emoji.small;
                domParent.appendChild(emojiDom);
                return domParent.outerHTML;
            }
            return null;
        }
    });
    dom.authorName.textContent = SELECTED_CONTEXT.self ? SELECTED_CONTEXT.self.getName() : "";

    var dot = document.createElement("span");
    dot.className = R.klass.typing.dot1;
    dot.textContent = '.';
    dom.ts.appendChild(dot);
    dot = document.createElement("span");
    dot.className = R.klass.typing.dot2;
    dot.textContent = '.';
    dom.ts.appendChild(dot);
    dot = document.createElement("span");
    dot.className = R.klass.typing.dot3;
    dot.textContent = '.';
    dom.ts.appendChild(dot);
    dom.ts.classList.add(R.klass.typing.container);
    return dom;
}

