private TileDay CreateOrGetDay(DateTime date, string fullPath, string relPath, out bool wasCreated)
        {
            wasCreated = false;

            var day = Days.FirstOrDefault((d) => d != null ? d.Date == date : false);

            if (day == null)
            {
                wasCreated = true;

                day = new TileDay() { FullPath = fullPath, RelativePath = relPath, Date = date/*, LastGroupZ = lastZ, LastGroupDeltaZ = lastDeltaZ*/ };

                // Propagate with additional context.
                day.TileDataChanged += (s, e) =>
                {
                    if (e.Context.Day.Tile.HaveAcquisition)
                    {
                        double z;

                        if (Double.IsNaN(e.Context.Day.Tile.Contents.DeltaZ))
                        {
                            // Current tile DeltaZ has never been set - try to set.
                            if (this.ZIndexZValueMap.TryGetValue(e.Context.Day.Tile.Contents.LatticePosition.Z - 1, out z))
                            {
                                e.Context.Day.Tile.Contents.DeltaZ = e.Context.Day.Tile.Contents.Position.Z - z;
                            }
                            else
                            {
                                // If this tile is in the lowest Z index, make it zero.
                                if (this.ZIndexZValueMap.Count == 0 || this.ZIndexZValueMap.Keys.OrderBy(x => x).FirstOrDefault() >= e.Context.Day.Tile.Contents.LatticePosition.Z)
                                {
                                    e.Context.Day.Tile.Contents.DeltaZ = 0;
                                }
                            }
                        }

                        var shouldUpdateDeltaZ = false;

                        // See if this is a new minimum for this lattice z index.
                        if (this.ZIndexZValueMap.TryGetValue(e.Context.Day.Tile.Contents.LatticePosition.Z, out z))
                        {
                            if (e.Context.Day.Tile.Contents.Position.Z < z)
                            {
                                z = e.Context.Day.Tile.Contents.Position.Z;
                                ZIndexZValueMap[e.Context.Day.Tile.Contents.LatticePosition.Z] = z;
                                shouldUpdateDeltaZ = true;
                            }
                        }
                        else
                        {
                            // We don't have an entry yet, this is the new minimum for this ZIndex.
                            z = e.Context.Day.Tile.Contents.Position.Z;
                            ZIndexZValueMap[e.Context.Day.Tile.Contents.LatticePosition.Z] = z;
                            shouldUpdateDeltaZ = true;
                        }

                        if (shouldUpdateDeltaZ)
                        {
                            // Update the delta Z for any tiles in the next layer that have not been set.  Only needed when tiles are discovered out of order or there was something incomplete.
                            var tiles = this.Days.Where((d) =>
                            {
                                return (d != null) && (d.Index >= e.Context.Day.Index);
                            })
                                .SelectMany(d => d.Groups)
                                .SelectMany(g => g.Tiles)
                                .Where(t => (t.Contents.LatticePosition.Z == e.Context.Day.Tile.Contents.LatticePosition.Z + 1));

                            tiles.All(t =>
                            {
                                t.Contents.DeltaZ = t.Contents.Position.Z - z;
                                return true;
                            }
                            );

                            // TODO also, if everything in the next layer is 0 (it thought it was the first Z index, then update to a real value.
                        }
                        if (e.Action == TileStateChangeAction.Add || e.Action == TileStateChangeAction.Changed || e.Action == TileStateChangeAction.Complete)
                        {
                            if (Lattice.AddTile(e.Context.Day.Tile))
                            {
                                OnPropertyChanged("DuplicateGrids");
                            }
                        }
                        else if (e.Action == TileStateChangeAction.Remove)
                        {
                            // TODO Remove duplicate.
                        }
                    }

                    e.SetMonitorId(Id);
                    RaiseTileDataChanged(e);
                };

                // Just propagate.
                day.IncompleteCollectionChanged += (s, e) =>
                {
                    OnPropertyChanged("IncompleteTiles");
                    OnCollectionChanged(IncompleteCollectionChanged, e);
                };

                // Just propagate.
                day.TileCollectionChanged += (s, e) => OnCollectionChanged(TileCollectionChanged, e);

                // The first day a monitor sees must be the first day of the acquisition.  At that point days can come in
                // any order.  However, there is no support for shifting days if an earlier first day arrives.  Deserialization
                // explicitly sorts by index and the initial scan explicitly scans and sorts day directories and submits to
                // the monitor before scanning group directories or tiles to enforce this.
                if (Days.Count == 0)
                {
                    day.Index = 0;
                    Days.Add(day);
                }
                else
                {
                    int index = (int)Math.Round((date - Days[0].Date).TotalDays);
                    day.Index = index;
                    while (Days.Count <= index)
                    {
                        Days.Add(null);
                    }
                    Days[index] = day;
                }

                OnPropertyChanged("Days");
            }

            return day;
        }
        private void ScanForTileGroups(TileDay tileDay, bool isBeyondEmergingThreshold)
        {
            var groups = Directory.GetDirectories(tileDay.FullPath, "0?", SearchOption.TopDirectoryOnly).OrderBy(s => s);

            List<TileGroup> validGroups = new List<TileGroup>();

            foreach (var groupFullPath in groups)
            {
                bool wasCreated;

                TileGroup tileGroup = tileDay.CreateOrGetTileGroup(groupFullPath, out wasCreated);

                if (tileGroup == null)
                {
                    Trace.TraceError("\t\tFailed to create Tile Group object for {0} even though it matches the required naming pattern.", groupFullPath);
                    continue;
                }

                validGroups.Add(tileGroup);

                if (wasCreated)
                {
                    Trace.TraceInformation("\t\t{0} was an unrecognized Tile Group and has been added.", groupFullPath);
                }
                else
                {
                    Trace.TraceInformation("\t\t{0} is an existing known Tile Group from earlier scans or deserialization.", groupFullPath);
                }
            }

            if (isBeyondEmergingThreshold)
            {
                // Do not monitor (or stop monitoring) for emerging files on days that are earlier than some threshold.
                UpdateHistorialTileGroupWorkers(validGroups);
            }
            else
            {
                UpdateTileGroupWorkers(validGroups);
            }
        }