//Example IRC message: @badges=moderator/1,warcraft/alliance;color=;display-name=Swiftyspiffyv4;emotes=;mod=1;room-id=40876073;subscriber=0;turbo=0;user-id=103325214;user-type=mod :[email protected] PRIVMSG #swiftyspiffy :asd
        /// <summary>Constructor for ChatMessage object.</summary>
        /// <param name="botUsername">The username of the bot that received the message.</param>
        /// <param name="ircMessage">The IRC message from Twitch to be processed.</param>
        /// <param name="emoteCollection">The <see cref="MessageEmoteCollection"/> to register new emotes on and, if desired, use for emote replacement.</param>
        /// <param name="replaceEmotes">Whether to replace emotes for this chat message. Defaults to false.</param>
        public ChatMessage(
            string botUsername,
            IrcMessage ircMessage,
            ref MessageEmoteCollection emoteCollection,
            bool replaceEmotes = false)
        {
            BotUsername      = botUsername;
            RawIrcMessage    = ircMessage.ToString();
            Message          = ircMessage.Message;
            _emoteCollection = emoteCollection;

            Username = ircMessage.User;
            Channel  = ircMessage.Channel;

            foreach (var tag in ircMessage.Tags.Keys)
            {
                var tagValue = ircMessage.Tags[tag];

                switch (tag)
                {
                case Tags.Badges:
                    Badges = Common.Helpers.ParseBadges(tagValue);
                    // Iterate through saved badges for special circumstances
                    foreach (var badge in Badges)
                    {
                        switch (badge.Key)
                        {
                        case "bits":
                            CheerBadge = new CheerBadge(int.Parse(badge.Value));
                            break;

                        case "subscriber":
                            // Prioritize BadgeInfo subscribe count, as its more accurate
                            if (SubscribedMonthCount == 0)
                            {
                                SubscribedMonthCount = int.Parse(badge.Value);
                            }
                            break;

                        case "vip":
                            IsVip = true;
                            break;
                        }
                    }
                    break;

                case Tags.BadgeInfo:
                    BadgeInfo = Common.Helpers.ParseBadges(tagValue);
                    // check if founder is one of them, and get months from that
                    var founderBadge = BadgeInfo.Find(b => b.Key == "founder");
                    if (!founderBadge.Equals(default(KeyValuePair <string, string>)))
                    {
                        IsSubscriber         = true;
                        SubscribedMonthCount = int.Parse(founderBadge.Value);
                    }
                    else
                    {
                        var subBadge = BadgeInfo.Find(b => b.Key == "subscriber");
                        // BadgeInfo has better accuracy than Badges subscriber value
                        if (!subBadge.Equals(default(KeyValuePair <string, string>)))
                        {
                            SubscribedMonthCount = int.Parse(subBadge.Value);
                        }
                    }
                    break;

                case Tags.Bits:
                    Bits          = int.Parse(tagValue);
                    BitsInDollars = ConvertBitsToUsd(Bits);
                    break;

                case Tags.Color:
                    ColorHex = tagValue;
                    if (!string.IsNullOrWhiteSpace(ColorHex))
                    {
                        Color = ColorTranslator.FromHtml(ColorHex);
                    }
                    break;

                case Tags.CustomRewardId:
                    CustomRewardId = tagValue;
                    break;

                case Tags.DisplayName:
                    DisplayName = tagValue;
                    break;

                case Tags.Emotes:
                    EmoteSet = new EmoteSet(tagValue, Message);
                    break;

                case Tags.Id:
                    Id = tagValue;
                    break;

                case Tags.MsgId:
                    handleMsgId(tagValue);
                    break;

                case Tags.Mod:
                    IsModerator = Common.Helpers.ConvertToBool(tagValue);
                    break;

                case Tags.Noisy:
                    Noisy = Common.Helpers.ConvertToBool(tagValue) ? Noisy.True : Noisy.False;
                    break;

                case Tags.RoomId:
                    RoomId = tagValue;
                    break;

                case Tags.Subscriber:
                    // this check because when founder is set, the subscriber value is actually 0, which is problematic
                    IsSubscriber = IsSubscriber == false?Common.Helpers.ConvertToBool(tagValue) : true;

                    break;

                case Tags.TmiSentTs:
                    TmiSentTs = tagValue;
                    break;

                case Tags.Turbo:
                    IsTurbo = Common.Helpers.ConvertToBool(tagValue);
                    break;

                case Tags.UserId:
                    UserId = tagValue;
                    break;

                case Tags.UserType:
                    switch (tagValue)
                    {
                    case "mod":
                        UserType = UserType.Moderator;
                        break;

                    case "global_mod":
                        UserType = UserType.GlobalModerator;
                        break;

                    case "admin":
                        UserType = UserType.Admin;
                        break;

                    case "staff":
                        UserType = UserType.Staff;
                        break;

                    default:
                        UserType = UserType.Viewer;
                        break;
                    }
                    break;
                }
            }

            if (Message.Length > 0 && (byte)Message[0] == 1 && (byte)Message[Message.Length - 1] == 1)
            {
                //Actions (/me {action}) are wrapped by byte=1 and prepended with "ACTION "
                //This setup clears all of that leaving just the action's text.
                //If you want to clear just the nonstandard bytes, use:
                //_message = _message.Substring(1, text.Length-2);
                if (Message.StartsWith("\u0001ACTION ") && Message.EndsWith("\u0001"))
                {
                    Message = Message.Trim('\u0001').Substring(7);
                    IsMe    = true;
                }
            }

            //Parse the emoteSet
            if (EmoteSet != null && Message != null && EmoteSet.Emotes.Count > 0)
            {
                var uniqueEmotes = EmoteSet.RawEmoteSetString.Split('/');
                foreach (var emote in uniqueEmotes)
                {
                    var firstColon = emote.IndexOf(':');
                    var firstComma = emote.IndexOf(',');
                    if (firstComma == -1)
                    {
                        firstComma = emote.Length;
                    }
                    var firstDash = emote.IndexOf('-');
                    if (firstColon > 0 && firstDash > firstColon && firstComma > firstDash)
                    {
                        if (int.TryParse(emote.Substring(firstColon + 1, firstDash - firstColon - 1), out var low) &&
                            int.TryParse(emote.Substring(firstDash + 1, firstComma - firstDash - 1), out var high))
                        {
                            if (low >= 0 && low < high && high < Message.Length)
                            {
                                //Valid emote, let's parse
                                var id = emote.Substring(0, firstColon);
                                //Pull the emote text from the message
                                var text = Message.Substring(low, high - low + 1);
                                _emoteCollection.Add(new MessageEmote(id, text));
                            }
                        }
                    }
                }
                if (replaceEmotes)
                {
                    EmoteReplacedMessage = _emoteCollection.ReplaceEmotes(Message);
                }
            }

            if (EmoteSet == null)
            {
                EmoteSet = new EmoteSet(default(string), Message);
            }

            // Check if display name was set, and if it wasn't, set it to username
            if (string.IsNullOrEmpty(DisplayName))
            {
                DisplayName = Username;
            }

            // Check if message is from broadcaster
            if (string.Equals(Channel, Username, StringComparison.InvariantCultureIgnoreCase))
            {
                UserType      = UserType.Broadcaster;
                IsBroadcaster = true;
            }

            if (Channel.Split(':').Length == 3)
            {
                if (string.Equals(Channel.Split(':')[1], UserId, StringComparison.InvariantCultureIgnoreCase))
                {
                    UserType      = UserType.Broadcaster;
                    IsBroadcaster = true;
                }
            }
        }