// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

import Client from '../client/web_client.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import Constants from 'utils/constants.jsx';
import EventEmitter from 'events';
import * as Emoji from 'utils/emoji.jsx';

const ActionTypes = Constants.ActionTypes;

const CHANGE_EVENT = 'changed';
const MAXIMUM_RECENT_EMOJI = 27;

// Wrap the contents of the store so that we don't need to construct an ES6 map where most of the content
// (the system emojis) will never change. It provides the get/has functions of a map and an iterator so
// that it can be used in for..of loops
class EmojiMap {
    constructor(customEmojis) {
        this.customEmojis = customEmojis;

        // Store customEmojis to an array so we can iterate it more easily
        this.customEmojisArray = [...customEmojis];
    }

    has(name) {
        return Emoji.EmojiIndicesByAlias.has(name) || this.customEmojis.has(name);
    }

    get(name) {
        if (Emoji.EmojiIndicesByAlias.has(name)) {
            return Emoji.Emojis[Emoji.EmojiIndicesByAlias.get(name)];
        }

        return this.customEmojis.get(name);
    }

    [Symbol.iterator]() {
        const customEmojisArray = this.customEmojisArray;

        return {
            systemIndex: 0,
            customIndex: 0,
            next() {
                if (this.systemIndex < Emoji.Emojis.length) {
                    const emoji = Emoji.Emojis[this.systemIndex];

                    this.systemIndex += 1;

                    return {value: [emoji.aliases[0], emoji]};
                }

                if (this.customIndex < customEmojisArray.length) {
                    const emoji = customEmojisArray[this.customIndex][1];

                    this.customIndex += 1;

                    return {value: [emoji.name, emoji]};
                }

                return {done: true};
            }
        };
    }
}

class EmojiStore extends EventEmitter {
    constructor() {
        super();

        this.dispatchToken = AppDispatcher.register(this.handleEventPayload.bind(this));

        this.setMaxListeners(600);

        this.receivedCustomEmojis = false;
        this.customEmojis = new Map();

        this.map = new EmojiMap(this.customEmojis);
    }

    addChangeListener(callback) {
        this.on(CHANGE_EVENT, callback);
    }

    removeChangeListener(callback) {
        this.removeListener(CHANGE_EVENT, callback);
    }

    emitChange() {
        this.emit(CHANGE_EVENT);
    }

    hasReceivedCustomEmojis() {
        return this.receivedCustomEmojis;
    }

    setCustomEmojis(customEmojis) {
        customEmojis.sort((a, b) => a.name.localeCompare(b.name));

        this.customEmojis = new Map();

        for (const emoji of customEmojis) {
            this.addCustomEmoji(emoji);
        }

        this.map = new EmojiMap(this.customEmojis);
    }

    addCustomEmoji(emoji) {
        this.customEmojis.set(emoji.name, emoji);
    }

    removeCustomEmoji(id) {
        for (const [name, emoji] of this.customEmojis) {
            if (emoji.id === id) {
                this.customEmojis.delete(name);
                break;
            }
        }
    }

    hasSystemEmoji(name) {
        return Emoji.EmojiIndicesByAlias.has(name);
    }

    getCustomEmojiMap() {
        return this.customEmojis;
    }

    getEmojis() {
        return this.map;
    }

    has(name) {
        return this.map.has(name);
    }

    get(name) {
        return this.map.get(name);
    }

    addRecentEmoji(rawAlias) {
        const recentEmojis = this.getRecentEmojis();

        const alias = rawAlias.split(':').join('');

        let emoji = this.getCustomEmojiMap().get(alias);

        if (!emoji) {
            const emojiIndex = Emoji.EmojiIndicesByAlias.get(alias);
            emoji = Emoji.Emojis[emojiIndex];
        }

        if (!emoji) {
            // something is wrong, so we return
            return;
        }

        // odd workaround to the lack of array.findLastIndex - reverse looping & splice
        for (let i = recentEmojis.length - 1; i >= 0; i--) {
            if ((emoji.name && recentEmojis[i].name === emoji.name) ||
                (emoji.filename && recentEmojis[i].filename === emoji.filename)) {
                recentEmojis.splice(i, 1);
                break;
            }
        }
        recentEmojis.push(emoji);

        // cut off the _top_ if it's over length (since new are added to end)
        if (recentEmojis.length > MAXIMUM_RECENT_EMOJI) {
            recentEmojis.splice(0, recentEmojis.length - MAXIMUM_RECENT_EMOJI);
        }
        localStorage.setItem(Constants.RECENT_EMOJI_KEY, JSON.stringify(recentEmojis));
    }

    getRecentEmojis() {
        const result = JSON.parse(localStorage.getItem(Constants.RECENT_EMOJI_KEY));
        if (!result) {
            return [];
        }
        return result;
    }

    hasUnicode(codepoint) {
        return Emoji.EmojiIndicesByUnicode.has(codepoint);
    }

    getUnicode(codepoint) {
        return Emoji.Emojis[Emoji.EmojiIndicesByUnicode.get(codepoint)];
    }

    getEmojiImageUrl(emoji) {
        if (emoji.id) {
            return Client.getCustomEmojiImageUrl(emoji.id);
        }

        const filename = emoji.filename || emoji.aliases[0];

        return Constants.EMOJI_PATH + '/' + filename + '.png';
    }

    handleEventPayload(payload) {
        const action = payload.action;

        switch (action.type) {
        case ActionTypes.RECEIVED_CUSTOM_EMOJIS:
            this.setCustomEmojis(action.emojis);
            this.receivedCustomEmojis = true;
            this.emitChange();
            break;
        case ActionTypes.RECEIVED_CUSTOM_EMOJI:
            this.addCustomEmoji(action.emoji);
            this.emitChange();
            break;
        case ActionTypes.REMOVED_CUSTOM_EMOJI:
            this.removeCustomEmoji(action.id);
            this.emitChange();
            break;
        case ActionTypes.EMOJI_POSTED:
            this.addRecentEmoji(action.alias);
            this.emitChange();
            break;
        }
    }
}

export default new EmojiStore();
