// ReSharper disable once ReturnTypeCanBeEnumerable.Global
        public static EmojiRenderResult[] RenderEmojiAsStringList(EmojiPackType pack, IList <long> activeEmoji)
        {
            var result     = new List <EmojiRenderResult>();
            var dictionary = emojiList[pack];

            for (int i = 0; i < activeEmoji.Count; i++)
            {
                bool u   = false;
                int  max = Math.Min(i + 3, activeEmoji.Count - 1);
                int  j   = max;
                for (; j >= i; j--)
                {
                    var a = new List <long>();
                    for (int n = i; n <= j; n++)
                    {
                        a.Add(activeEmoji[n]);
                    }

                    var emojiString = GetStringForCodes(a);
                    if (dictionary.Contains(emojiString))
                    {
                        u = true;
                        result.Add(new EmojiRenderResult()
                        {
                            emojiCode = emojiString
                        });
                        break;
                    }
                }

                if (u)
                {
                    // Такой сиквенс, который начинается на activeEmoji[i] есть в словаре
                    i = j;
                }
                else
                {
                    // Такого сиквенса не существует
                    result.Add(new EmojiRenderResult()
                    {
                        isSuccess = false,
                        rawText   = Utf8ToUnicode.UnicodeCodeToString(activeEmoji[i])
                    });
                }
            }

            return(result.ToArray());
        }
        private static string AddMessageReactionHTMLWithEmojiPack(
            NKDiscordChatWidget.DiscordBot.Classes.EventMessageCreate.EventMessageCreate_Reaction reaction,
            EmojiPackType emojiPack
            )
        {
            var longs = Utf8ToUnicode.ToUnicodeCode(reaction.emoji.name);
            var u     = longs.Any(code => !UnicodeEmojiEngine.IsInIntervalEmoji(code, emojiPack));

            if (u)
            {
                // Реакция без ID, но при этом не является эмодзи, рисуем как есть
                return(HttpUtility.HtmlEncode(reaction.emoji.name));
            }

            // Реакция без ID и является эмодзи, поэтому рисуем как картинку
            var localEmojiList = UnicodeEmojiEngine.RenderEmojiAsStringList(
                emojiPack, longs);
            var emojiHtml = "";

            // ReSharper disable once LoopCanBeConvertedToQuery
            foreach (var item in localEmojiList)
            {
                if (!item.isSuccess)
                {
                    // Этого символа нет в паке, выводим как есть. Куда деваться
                    emojiHtml += HttpUtility.HtmlEncode(item.rawText);
                    continue;
                }

                // hint: localEmojiList.Count может быть больше 1 в случае сложных эмодзи типа :one:
                var emojiSubFolderName = UnicodeEmojiEngine.GetImageSubFolder(emojiPack);
                var emojiExtension     = UnicodeEmojiEngine.GetImageExtension(emojiPack);
                var url = string.Format("/images/emoji/{0}/{1}.{2}",
                                        emojiSubFolderName,
                                        item.emojiCode,
                                        emojiExtension
                                        );

                emojiHtml += string.Format("<img src='{0}' alt=':{1}:'>",
                                           HttpUtility.HtmlEncode(url),
                                           HttpUtility.HtmlEncode(reaction.emoji.name)
                                           );
            }

            return(emojiHtml);
        }
        private static string DrawMessageContent(EventMessageCreate message, ChatDrawOption chatOption)
        {
            var usedEmbedsUrls = new HashSet <string>();

            foreach (var embed in message.embeds)
            {
                usedEmbedsUrls.Add(embed.url);
            }

            var guildID         = message.guild_id;
            var guild           = NKDiscordChatWidget.DiscordBot.Bot.guilds[guildID];
            var thisGuildEmojis = new HashSet <string>(guild.emojis.Select(emoji => emoji.id).ToList());

            // Основной текст
            string directContentHTML = NKDiscordChatWidget.General.MessageMarkdownParser.RenderMarkdownAsHTML(
                message.content, chatOption, message.mentions, guildID, usedEmbedsUrls);
            bool containOnlyUnicodeAndSpace;
            {
                var    rEmojiWithinText = new Regex(@"<\:(.+?)\:([0-9]+)>", RegexOptions.Compiled);
                long[] longs            = { };
                if (message.content != null)
                {
                    longs = Utf8ToUnicode.ToUnicodeCode(rEmojiWithinText.Replace(message.content, ""));
                }

                containOnlyUnicodeAndSpace = Utf8ToUnicode.ContainOnlyUnicodeAndSpace(longs);
            }
            string html = string.Format("<div class='content-direct {1}'>{0}</div>",
                                        directContentHTML, containOnlyUnicodeAndSpace ? "only-emoji" : "");

            // attachments
            if ((message.attachments != null) && message.attachments.Any() && (chatOption.attachments != 2))
            {
                string attachmentHTML = "";

                foreach (var attachment in message.attachments)
                {
                    var extension = attachment.filename.Split('.').Last().ToLowerInvariant();
                    // ReSharper disable once SwitchStatementMissingSomeCases
                    switch (extension)
                    {
                    case "mp4":
                    case "webm":
                        // TODO: Отображать размер видео
                        attachmentHTML += string.Format(
                            "<div class='attachment {1}'><div class='attachment-wrapper'><video><source src='{0}'></video></div></div>",
                            HttpUtility.HtmlEncode(attachment.proxy_url),
                            ((chatOption.attachments == 1) || attachment.IsSpoiler) ? "blur" : ""
                            );
                        break;

                    case "jpeg":
                    case "jpg":
                    case "bmp":
                    case "gif":
                    case "png":
                        attachmentHTML += string.Format(
                            "<div class='attachment {3}'><div class='attachment-wrapper'><img src='{0}' data-width='{1}' data-height='{2}'></div></div>",
                            HttpUtility.HtmlEncode(attachment.proxy_url),
                            attachment.width,
                            attachment.height,
                            ((chatOption.attachments == 1) || attachment.IsSpoiler) ? "blur" : ""
                            );
                        break;
                    }
                }

                html += string.Format("<div class='attachment-block'>{0}</div>", attachmentHTML);
            }

            // Preview
            if ((message.embeds != null) && message.embeds.Any() && (chatOption.link_preview != 2))
            {
                string previewHTML = "";

                foreach (var embed in message.embeds)
                {
                    var subHTML = "";
                    if (embed.provider != null)
                    {
                        subHTML += string.Format("<div class='provider'>{0}</div>",
                                                 HttpUtility.HtmlEncode(embed.provider.name));
                    }

                    if (embed.author != null)
                    {
                        subHTML += string.Format("<div class='author'>{0}</div>",
                                                 HttpUtility.HtmlEncode(embed.author.name));
                    }

                    subHTML += string.Format("<div class='title'>{0}</div>",
                                             HttpUtility.HtmlEncode(embed.title));

                    if (embed.thumbnail != null)
                    {
                        subHTML += string.Format("<div class='preview'><img src='{0}' alt='{1}'></div>",
                                                 HttpUtility.HtmlEncode(embed.thumbnail.proxy_url),
                                                 HttpUtility.HtmlEncode("Превью для «" + embed.title + "»")
                                                 );
                    }

                    var nickColor = embed.color.ToString("X");
                    nickColor = "#" + nickColor.PadLeft(6, '0');

                    previewHTML += string.Format(
                        "<div class='embed {2}'><div class='embed-pill' style='background-color: {1};'></div>" +
                        "<div class='embed-content {3}'>{0}</div></div>",
                        subHTML,
                        nickColor,
                        (chatOption.link_preview == 1) ? "blur" : "",
                        string.IsNullOrEmpty(embed.title) ? "embed-content_no-title" : ""
                        );
                }

                html += string.Format("<div class='embed-block'>{0}</div>", previewHTML);
            }

            // Реакции
            if (
                (message.reactions != null) && message.reactions.Any() &&
                ((chatOption.message_relative_reaction != 2) || (chatOption.message_stranger_reaction != 2))
                )
            {
                // Реакции
                var reactionHTMLs = new List <string>();
                foreach (var reaction in message.reactions)
                {
                    bool isRelative = ((reaction.emoji.id == null) || thisGuildEmojis.Contains(reaction.emoji.id));
                    int  emojiShow  = isRelative
                        ? chatOption.message_relative_reaction
                        : chatOption.message_stranger_reaction;
                    if (emojiShow == 2) // @todo убрать магическую константу
                    {
                        continue;
                    }

                    AddMessageReactionHTML(
                        reactionHTMLs,
                        reaction,
                        emojiShow,
                        chatOption
                        );
                }

                var s = reactionHTMLs.Aggregate("", (current, s1) => current + s1);
                html += string.Format("<div class='content-reaction'>{0}</div>", s);
            }

            return(html);
        }
        /// <summary>
        /// Парсинг эмодзи из дополнительных plain'ов в Unicode
        /// </summary>
        /// <param name="text">Текст с сырым Markdown</param>
        /// <param name="chatOption">Опции чата, заданные стримером для виджета</param>
        /// <param name="waitDictionary">Dictionary для саб-блоков</param>
        /// <returns></returns>
        public static string MarkEmojiUnicode(
            string text,
            ChatDrawOption chatOption,
            Dictionary <string, string> waitDictionary
            )
        {
            if (!UnicodeEmojiEngine.emojiList[chatOption.unicode_emoji_displaying].Any())
            {
                return(text);
            }

            var activeEmoji = new List <long>();

            var longs        = Utf8ToUnicode.ToUnicodeCode(text);
            var textAfter    = "";
            var containEmoji = false;

            foreach (var code in longs)
            {
                if (!UnicodeEmojiEngine.IsInIntervalEmoji(code, chatOption.unicode_emoji_displaying))
                {
                    // Этот символ НЕ является unicode emoji
                    if (activeEmoji.Any())
                    {
                        // У нас непустой буффер emoji символов, надо их записать в строку
                        var localEmojiList = UnicodeEmojiEngine.RenderEmojiAsStringList(
                            chatOption.unicode_emoji_displaying, activeEmoji);
                        textAfter += RenderEmojiStringListAsHtml(
                            localEmojiList,
                            chatOption.unicode_emoji_displaying,
                            waitDictionary,
                            chatOption.emoji_relative
                            );
                        activeEmoji = new List <long>();
                    }

                    textAfter += Utf8ToUnicode.UnicodeCodeToString(code);

                    continue;
                }

                // Этот символ ЯВЛЯЕТСЯ unicode emoji
                containEmoji = true;
                activeEmoji.Add(code);
            } // foreach

            if (activeEmoji.Any())
            {
                // У нас не пустой буффер emoji символов, надо их записать в строку
                var localEmojiList = UnicodeEmojiEngine.RenderEmojiAsStringList(
                    chatOption.unicode_emoji_displaying, activeEmoji);
                textAfter += RenderEmojiStringListAsHtml(
                    localEmojiList,
                    chatOption.unicode_emoji_displaying,
                    waitDictionary,
                    chatOption.emoji_relative
                    );
            }

            if (containEmoji)
            {
                text = textAfter;
            }

            return(text);
        }