private static IEnumerable <bool> LoadBubblesInto(BubbleGroup group, List <VisualBubble> listToLoadInto,
                                                          LoadBubbles instance)
        {
            if (!listToLoadInto.Any())
            {
                yield break;
            }

            Utils.DebugPrint("Loading into convo " + group.ID);

            var unifiedGroup = group as UnifiedBubbleGroup;
            var groups       = unifiedGroup != null ? unifiedGroup.Groups : new List <BubbleGroup>()
            {
                group
            };

            const int MaxLoadPerGroup = 100;
            var       currentTime     = listToLoadInto.Min(x => x.Time);

            var bubbleGroupStates = new Dictionary <BubbleGroup, LoadBubblesIntoStateHolder>();

            foreach (var innerGroup in groups)
            {
                bubbleGroupStates[innerGroup] = new LoadBubblesIntoStateHolder();
            }

            EventHandler <Service> serviceStarted = (sender, e) =>
            {
                foreach (var state in bubbleGroupStates)
                {
                    if (state.Key.Service == e)
                    {
                        state.Value.Dead = false;
                    }
                }
            };

            ServiceEvents.Started  += serviceStarted;
            instance.ServiceStarted = serviceStarted;

            while (true)
            {
                var listToLoadIntoCount = listToLoadInto.Count;
                var bubbleLoads         = new Dictionary <BubbleGroup, List <Tuple <VisualBubble, bool> > >();

                foreach (var innerGroup in groups)
                {
                    var innerGroupState = bubbleGroupStates[innerGroup];

                    if (innerGroupState.Dead)
                    {
                        bubbleLoads[innerGroup] = null;
                        continue;
                    }

                    if (innerGroupState.LocalFinished)
                    {
                        var agentBubbles = LoadBubblesIntoDoAgent(innerGroup, currentTime, MaxLoadPerGroup, innerGroupState);
                        if (agentBubbles != null)
                        {
                            bubbleLoads[innerGroup] = agentBubbles.Select(x =>
                                                                          new Tuple <VisualBubble, bool>(x, true)).ToList();
                        }
                        else
                        {
                            bubbleLoads[innerGroup] = null;
                        }
                    }
                    else
                    {
                        var localBubbles = new List <VisualBubble>();
                        innerGroupState.LocalCursor = BubbleGroupDatabase.FetchBubblesAt(innerGroup,
                                                                                         currentTime, MaxLoadPerGroup, ref localBubbles, innerGroupState.LocalCursor);
                        if (innerGroupState.LocalCursor == 0 || localBubbles.Count == 0)
                        {
                            innerGroupState.LocalFinished = true;
                        }

                        if (localBubbles.Count == MaxLoadPerGroup)
                        {
                            bubbleLoads[innerGroup] = localBubbles.Select(x =>
                                                                          new Tuple <VisualBubble, bool>(x, false)).ToList();
                        }
                        else
                        {
                            var innerGroupComparer = innerGroup.Service as Comparer;

                            if (innerGroupComparer != null)
                            {
                                var agentBubbles = LoadBubblesIntoDoAgent(innerGroup, currentTime, MaxLoadPerGroup, innerGroupState);

                                if (agentBubbles == null)
                                {
                                    bubbleLoads[innerGroup] = localBubbles.Select(x =>
                                                                                  new Tuple <VisualBubble, bool>(x, false)).ToList();
                                }
                                else
                                {
                                    var innerGroupAgent = innerGroup.Service as Agent;
                                    var combined        = new List <Tuple <VisualBubble, bool> >();

                                    // combine them: take all agent bubbles, and then try to replace the clouds with the locals.
                                    // what can't be replaced becomes the inserts.

                                    for (int i = 0; i < agentBubbles.Count; i++)
                                    {
                                        var agentBubble = agentBubbles[i];
                                        var localBubble =
                                            localBubbles.FirstOrDefault(x => innerGroupComparer.LoadBubblesComparer(x, agentBubble));
                                        if (localBubble != null)
                                        {
                                            combined.Add(new Tuple <VisualBubble, bool>(localBubble, false));
                                        }
                                        else
                                        {
                                            combined.Add(new Tuple <VisualBubble, bool>(agentBubble, true));
                                        }
                                    }

                                    bubbleLoads[innerGroup] = combined;
                                }
                            }
                            else
                            {
                                bubbleLoads[innerGroup] = localBubbles.Select(x =>
                                                                              new Tuple <VisualBubble, bool>(x, false)).ToList();
                            }
                        }
                    }
                }

                // insert the bubbles into the disa bubble group with two conditions
                // a) must not be bubble retreived from disa bubble group
                // b) must not be a duplicate already in the list to load into
                var listToLoadIntoBubblesOnTime = listToLoadInto.Where(x => x.Time == currentTime).ToList();
                foreach (var bubbleLoad in bubbleLoads)
                {
                    if (bubbleLoad.Value == null)
                    {
                        continue;
                    }

                    var bubbleGroup     = bubbleLoad.Key;
                    var bubblesToInsert = LoadBubblesIntoRemoveDuplicates(bubbleLoad.Value.Where(x => x.Item2)
                                                                          .Select(x => x.Item1).ToList(), listToLoadIntoBubblesOnTime);
                    if (bubblesToInsert.Any())
                    {
                        BubbleGroupDatabase.InsertBubblesByTime(bubbleGroup,
                                                                bubblesToInsert.ToArray(), int.MaxValue, true, true);
                    }
                }

                // find the greatest minimum time of all the bubble loads
                // and merge the bubble loads against that

                var greatestMin = 0L;
                foreach (var bubbleLoad in bubbleLoads)
                {
                    if (bubbleLoad.Value == null || !bubbleLoad.Value.Any())
                    {
                        continue;
                    }

                    var min = bubbleLoad.Value.Min(x => x.Item1.Time);
                    if (min > greatestMin)
                    {
                        greatestMin = min;
                    }
                }

                var mergedBubbles = new List <VisualBubble>();
                foreach (var bubbleLoad in bubbleLoads)
                {
                    if (bubbleLoad.Value != null)
                    {
                        var bubblesToMerge = bubbleLoad.Value.Where(x =>
                                                                    x.Item1.Time >= greatestMin).Select(x => x.Item1).ToList();
                        foreach (var bubbleToMerge in bubblesToMerge)
                        {
                            bubbleToMerge.BubbleGroupReference = bubbleLoad.Key;
                        }
                        mergedBubbles.AddRange(bubblesToMerge);
                    }
                }
                mergedBubbles.TimSort((x, y) => x.Time.CompareTo(y.Time));

                // insert the merged bubbles into the list to load into, making sure to
                // remove and duplicates encountered.
                listToLoadInto.InsertRange(0, mergedBubbles);
                LoadBubblesIntoRemoveDuplicates(listToLoadInto, currentTime);

                if (mergedBubbles.Any())
                {
                    currentTime = mergedBubbles.First().Time;
                }

                // if the count wasn't altered, we've hit the end
                if (listToLoadIntoCount == listToLoadInto.Count)
                {
                    yield break;
                }

                yield return(true);
            }
        }
        public static IEnumerable <bool> LoadBubblesIntoOffline(BubbleGroup group, List <VisualBubble> listToLoadInto)
        {
            if (!listToLoadInto.Any())
            {
                yield break;
            }

            Utils.DebugPrint("Loading into convo (offline): " + group.ID);

            var unifiedGroup = group as UnifiedBubbleGroup;
            var groups       = unifiedGroup != null ? unifiedGroup.Groups : new List <BubbleGroup>()
            {
                group
            };

            const int MaxLoadPerGroup = 100;
            var       currentTime     = listToLoadInto.Min(x => x.Time);

            var bubbleGroupStates = new Dictionary <BubbleGroup, LoadBubblesIntoStateHolder>();

            foreach (var innerGroup in groups)
            {
                bubbleGroupStates[innerGroup] = new LoadBubblesIntoStateHolder();
            }

            while (true)
            {
                var listToLoadIntoCount = listToLoadInto.Count;
                var bubbleLoads         = new Dictionary <BubbleGroup, List <Tuple <VisualBubble, bool> > >();

                foreach (var innerGroup in groups)
                {
                    var innerGroupState = bubbleGroupStates[innerGroup];

                    if (innerGroupState.LocalFinished)
                    {
                        bubbleLoads[innerGroup] = null;
                    }
                    else
                    {
                        var localBubbles = new List <VisualBubble>();
                        innerGroupState.LocalCursor = BubbleGroupDatabase.FetchBubblesAt(innerGroup,
                                                                                         currentTime, MaxLoadPerGroup, ref localBubbles, innerGroupState.LocalCursor);
                        if (innerGroupState.LocalCursor == 0 || localBubbles.Count == 0)
                        {
                            innerGroupState.LocalFinished = true;
                        }

                        bubbleLoads[innerGroup] = localBubbles.Select(x =>
                                                                      new Tuple <VisualBubble, bool>(x, false)).ToList();
                    }
                }

                // find the greatest minimum time of all the bubble loads
                // and merge the bubble loads against that

                var greatestMin = 0L;
                foreach (var bubbleLoad in bubbleLoads)
                {
                    if (bubbleLoad.Value == null || !bubbleLoad.Value.Any())
                    {
                        continue;
                    }

                    var min = bubbleLoad.Value.Min(x => x.Item1.Time);
                    if (min > greatestMin)
                    {
                        greatestMin = min;
                    }
                }

                var mergedBubbles = new List <VisualBubble>();
                foreach (var bubbleLoad in bubbleLoads)
                {
                    if (bubbleLoad.Value != null)
                    {
                        var bubblesToMerge = bubbleLoad.Value.Where(x =>
                                                                    x.Item1.Time >= greatestMin).Select(x => x.Item1).ToList();
                        foreach (var bubbleToMerge in bubblesToMerge)
                        {
                            bubbleToMerge.BubbleGroupReference = bubbleLoad.Key;
                        }
                        mergedBubbles.AddRange(bubblesToMerge);
                    }
                }
                mergedBubbles.TimSort((x, y) => x.Time.CompareTo(y.Time));

                // insert the merged bubbles into the list to load into, making sure to
                // remove and duplicates encountered.
                listToLoadInto.InsertRange(0, mergedBubbles);
                LoadBubblesIntoRemoveDuplicates(listToLoadInto, currentTime);

                if (mergedBubbles.Any())
                {
                    currentTime = mergedBubbles.First().Time;
                }

                // if the count wasn't altered, we've hit the end
                if (listToLoadIntoCount == listToLoadInto.Count)
                {
                    yield break;
                }

                yield return(true);
            }
        }