/// <summary>
        /// When filtering options change, we refilter the list and change Messages
        /// to be a new list of those items.
        /// </summary>
        private void StartAsyncRefilter(RefilterResultEvent evt)
        {
            Task t = new Task(delegate()
            {
                var items    = from item in m_allItems where AllowItem(item) select item;
                var messages = new ObservableCollection <ListItem>(items);

                for (int i = 0; i < messages.Count; ++i)
                {
                    messages[i].Index = i;
                }

                evt.Result = messages;
            });

            t.Start();
        }
        /// <summary>
        /// When filtering options change, we refilter the list and change Messages
        /// to be a new list of those items.
        /// </summary>
        private void StartAsyncRefilter(RefilterResultEvent evt)
        {
            Task t = new Task(delegate()
                {
                    var items = from item in m_allItems where AllowItem(item) select item;
                    var messages = new ObservableCollection<ListItem>(items);

                    for (int i = 0; i < messages.Count; ++i)
                        messages[i].Index = i;

                    evt.Result = messages;
                });

            t.Start();
        }
        /// <summary>
        /// This is the primary update loop for the UI.  Every 250 milliseconds we scour
        /// each concurrent queue looking for new messages and update the UI with it.
        /// This may seem like overkill but it ensures that all events are handled in a
        /// correct/sane ordering.
        /// </summary>
        private void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            // If IrcDotNet hit an error, reset the connection entirely (by creating a new
            // TwitchClient and throwing away the old one).
            if (m_error)
            {
                if (m_twitch.IsStreamLive)
                {
                    ResetConnection();
                }
            }

            // If we are refiltering (asynchronously) the list, actually stop all processing
            // on the list until it's complete.  This prevents us from modifying the message
            // list while we are reading from it on another thread.
            if (m_refilterAsync != null)
            {
                if (!m_refilterAsync.Complete)
                {
                    return;
                }

                Messages = m_refilterAsync.Result;
                ScrollBar.ScrollToEnd();
                m_refilterAsync = null;
            }

            // Now go through the queue, add messages we find, and process refilter requests.
            if (m_eventQueue.Count == 0)
            {
                return;
            }

            bool  refilterInPlace = false;
            Event evt;

            while (m_eventQueue.TryDequeue(out evt))
            {
                switch (evt.Type)
                {
                case EventType.NewListItem:
                    AddItem(((NewListItemEvent)evt).Item);
                    break;

                case EventType.NewSubscriber:
                    NewSubscriberEvent newSub = (NewSubscriberEvent)evt;
                    m_subs.Add(newSub.User);
                    AddItem(newSub.Item);
                    break;

                case EventType.NotifySubscriber:
                    m_subs.Add(((NotifySubscriberEvent)evt).User);
                    break;

                case EventType.RefilterInPlace:
                    refilterInPlace = true;
                    break;

                case EventType.StatusUpdate:
                    var status = ((StatusEvent)evt).Status;
                    AddItem(ListItem.CreateFromStatus(status));
                    break;

                case EventType.DuplicateListItem:
                    var dupe = ((DuplicateListItemEvent)evt).Item;
                    int i    = dupe.Index;
                    Debug.Assert(dupe == Messages[i]);

                    Messages.RemoveAt(i);
                    for (; i < Messages.Count; ++i)
                    {
                        Messages[i].Index = i;
                    }

                    AddItem(dupe);
                    break;

                // If we need to refilter the list (due to the user changing filtering options),
                // we do that work on a background thread.  During that time, we stop all message
                // processing, hence the return instead of break here.
                case EventType.RefilterAsyncEvent:
                    m_refilterAsync = ((RefilterResultEvent)evt);
                    StartAsyncRefilter(m_refilterAsync);
                    return;
                }
            }

            // If we were asked to refilter the list in place (that is, remove messages),
            // then we do that only once, now at the end.
            if (refilterInPlace)
            {
                int i = 0;
                while (i < Messages.Count)
                {
                    var curr = Messages[i];
                    if (!AllowItem(curr))
                    {
                        Messages.RemoveAt(i);
                    }
                    else
                    {
                        curr.Index = i;
                        i++;
                    }
                }
            }

            // If we have new notifications of subscribers, we need to go back and mark the
            // questions they asked with a sub icon.
            if (m_subs.Count > 0)
            {
                var needsUpdate = from item in m_allItems
                                  where (item.Type == ListItemType.ImportantQuestion || item.Type == ListItemType.Question) &&
                                  item.SubscriberIcon != System.Windows.Visibility.Visible && m_subs.Contains(item.User)
                                  select item;

                foreach (var item in needsUpdate)
                {
                    item.SubscriberIcon = System.Windows.Visibility.Visible;
                }

                m_subs.Clear();
            }
        }
        /// <summary>
        /// This is the primary update loop for the UI.  Every 250 milliseconds we scour
        /// each concurrent queue looking for new messages and update the UI with it.
        /// This may seem like overkill but it ensures that all events are handled in a
        /// correct/sane ordering.  
        /// </summary>
        private void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            // If IrcDotNet hit an error, reset the connection entirely (by creating a new
            // TwitchClient and throwing away the old one).
            if (m_error)
            {
                if (m_twitch.IsStreamLive)
                    ResetConnection();
            }

            // If we are refiltering (asynchronously) the list, actually stop all processing
            // on the list until it's complete.  This prevents us from modifying the message
            // list while we are reading from it on another thread.
            if (m_refilterAsync != null)
            {
                if (!m_refilterAsync.Complete)
                    return;

                Messages = m_refilterAsync.Result;
                ScrollBar.ScrollToEnd();
                m_refilterAsync = null;
            }

            // Now go through the queue, add messages we find, and process refilter requests.
            if (m_eventQueue.Count == 0)
                return;

            bool refilterInPlace = false;
            Event evt;
            while (m_eventQueue.TryDequeue(out evt))
            {
                switch (evt.Type)
                {
                    case EventType.NewListItem:
                        AddItem(((NewListItemEvent)evt).Item);
                        break;

                    case EventType.NewSubscriber:
                        NewSubscriberEvent newSub = (NewSubscriberEvent)evt;
                        m_subs.Add(newSub.User);
                        AddItem(newSub.Item);
                        break;

                    case EventType.NotifySubscriber:
                        m_subs.Add(((NotifySubscriberEvent)evt).User);
                        break;

                    case EventType.RefilterInPlace:
                        refilterInPlace = true;
                        break;

                    case EventType.StatusUpdate:
                        var status = ((StatusEvent)evt).Status;
                        AddItem(ListItem.CreateFromStatus(status));
                        break;

                    case EventType.DuplicateListItem:
                        var dupe = ((DuplicateListItemEvent)evt).Item;
                        int i = dupe.Index;
                        Debug.Assert(dupe == Messages[i]);

                        Messages.RemoveAt(i);
                        for (; i < Messages.Count; ++i)
                            Messages[i].Index = i;

                        AddItem(dupe);
                        break;

                    // If we need to refilter the list (due to the user changing filtering options),
                    // we do that work on a background thread.  During that time, we stop all message
                    // processing, hence the return instead of break here.
                    case EventType.RefilterAsyncEvent:
                        m_refilterAsync = ((RefilterResultEvent)evt);
                        StartAsyncRefilter(m_refilterAsync);
                        return;
                }
            }

            // If we were asked to refilter the list in place (that is, remove messages),
            // then we do that only once, now at the end.
            if (refilterInPlace)
            {
                int i = 0;
                while (i < Messages.Count)
                {
                    var curr = Messages[i];
                    if (!AllowItem(curr))
                    {
                        Messages.RemoveAt(i);
                    }
                    else
                    {
                        curr.Index = i;
                        i++;
                    }
                }
            }

            // If we have new notifications of subscribers, we need to go back and mark the
            // questions they asked with a sub icon.
            if (m_subs.Count > 0)
            {
                var needsUpdate = from item in m_allItems
                                  where (item.Type == ListItemType.ImportantQuestion || item.Type == ListItemType.Question)
                                  && item.SubscriberIcon != System.Windows.Visibility.Visible && m_subs.Contains(item.User)
                                  select item;

                foreach (var item in needsUpdate)
                    item.SubscriberIcon = System.Windows.Visibility.Visible;

                m_subs.Clear();
            }
        }