/// <summary>
        /// Updates all of the automaticly added GrhDatas.
        /// </summary>
        /// <param name="cm"><see cref="IContentManager"/> to use for new GrhDatas.</param>
        /// <param name="rootGrhDir">Root Grh texture directory.</param>
        /// <param name="added">The GrhDatas that were added (empty if none were added).</param>
        /// <param name="deleted">The GrhDatas that were deleted (empty if none were added).</param>
        /// <param name="grhDataFileTags">The file tags for the corresponding GrhDatas.</param>
        /// <returns>
        /// IEnumerable of all of the new GrhDatas created.
        /// </returns>
        public static void Update(IContentManager cm, string rootGrhDir, out GrhData[] added, out GrhData[] deleted, out Dictionary<GrhData, GrhData.FileTags> grhDataFileTags)
        {
            if (!rootGrhDir.EndsWith("\\") && !rootGrhDir.EndsWith("/"))
                rootGrhDir += "/";

            // Clear the temporary content to make sure we have plenty of working memory
            cm.Unload(ContentLevel.Temporary, true);

            // Get the relative file path for all files up-front (only do this once since it doesn't scale well)
            var relFilePaths = Directory.GetFiles(rootGrhDir, "*", SearchOption.AllDirectories)
                .Select(x => x.Replace('\\', '/').Substring(rootGrhDir.Length))
                .ToArray();

            // Also grab the existing GrhDatas
            var existingGrhDatas = GrhInfo.GrhDatas.ToDictionary(x => x.Categorization.ToString(), x => x);

            // Go through each file and do the adds
            grhDataFileTags = new Dictionary<GrhData, GrhData.FileTags>();
            HashSet<GrhData> addedGrhDatas = new HashSet<GrhData>();
            HashSet<GrhData> deletedGrhDatas = new HashSet<GrhData>();
            HashSet<GrhData> grhDatasToDelete = new HashSet<GrhData>(existingGrhDatas.Values);
            HashSet<string> checkedAnimationRelDirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            foreach (var relFilePath in relFilePaths)
            {
                // Before doing anything else, ensure it is a valid file type to handle
                string fileExtension = Path.GetExtension(relFilePath);
                if (!_graphicFileSuffixes.Contains(fileExtension, StringComparer.OrdinalIgnoreCase))
                    continue;

                string absFilePath = rootGrhDir + relFilePath;

                // Grab some stuff based on the file path
                string absDir = Path.GetDirectoryName(absFilePath);
                if (rootGrhDir.Length >= absDir.Length)
                    continue;

                string relDir = absDir.Substring(rootGrhDir.Length);
                string parentDirName = absDir.Substring(Path.GetDirectoryName(absDir).Length + 1);
                string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(relFilePath);

                bool isAnimationFrame = parentDirName.StartsWith("_");

                if (!isAnimationFrame)
                {
                    // ** Stationary **

                    var fileTags = GrhData.FileTags.Create(fileNameWithoutExtension);

                    // Build the categorization info
                    string category = relDir.Replace("/", SpriteCategorization.Delimiter).Replace("\\", SpriteCategorization.Delimiter);
                    SpriteCategorization cat = new SpriteCategorization(category, fileTags.Title);

                    // Get existing
                    GrhIndex? grhIndex = null;
                    GrhData grhData;
                    if (existingGrhDatas.TryGetValue(cat.ToString(), out grhData))
                    {
                        grhDatasToDelete.Remove(grhData);
                    }

                    // If already exists as animated, delete first
                    if (grhData != null && (grhData is AnimatedGrhData || grhData is AutomaticAnimatedGrhData))
                    {
                        grhIndex = grhData.GrhIndex; // We will re-use this GrhIndex
                        GrhInfo.Delete(grhData);
                        deletedGrhDatas.Add(grhData);
                        grhData = null;
                    }

                    // Add new
                    string texturePath = "/" + relFilePath.Substring(0, relFilePath.Length - Path.GetExtension(absFilePath).Length);
                    if (grhData == null)
                    {
                        grhData = GrhInfo.CreateGrhData(cm, cat, texturePath, grhIndex);
                        addedGrhDatas.Add(grhData);
                    }
                    else
                    {
                        // Make sure the texture is correct
                        string currTextureName = "/" + ((StationaryGrhData)grhData).TextureName.ToString().TrimStart('/', '\\');
                        if (currTextureName != texturePath)
                        {
                            ((StationaryGrhData)grhData).ChangeTexture(texturePath);
                        }
                    }

                    // Ensure set to auto-size
                    StationaryGrhData stationaryGrhData = (StationaryGrhData)grhData;
                    if (!stationaryGrhData.AutomaticSize)
                    {
                        stationaryGrhData.AutomaticSize = true;
                    }

                    // Add to GrhDataFileTags
                    if (grhDataFileTags.ContainsKey(grhData))
                    {
                        throw new GrhDataException(grhData, string.Format("Found more than one stationary GrhData with the categorization: `{0}`." +
                            " Make sure that you do not have multiple image files in /DevContent/ in the same folder with the same name but different extensions (e.g. Sprite.png and Sprite.jpg).",
                            grhData.Categorization));
                    }

                    grhDataFileTags.Add(grhData, fileTags);
                }
                else
                {
                    // ** Animated **

                    // Make sure we only handle each animation once (since this will get called for each frame since we're looping over the files)
                    if (!checkedAnimationRelDirs.Add(relDir))
                        continue;

                    var fileTags = GrhData.FileTags.Create(parentDirName.Substring(1)); // Remove the _ prefix from directory name

                    // Build the categorization
                    string category = Path.GetDirectoryName(absDir).Substring(rootGrhDir.Length)
                        .Replace("/", SpriteCategorization.Delimiter).Replace("\\", SpriteCategorization.Delimiter);
                    SpriteCategorization cat = new SpriteCategorization(category, fileTags.Title);

                    // Get existing
                    GrhIndex? grhIndex = null;
                    GrhData grhData;
                    if (existingGrhDatas.TryGetValue(cat.ToString(), out grhData))
                    {
                        grhDatasToDelete.Remove(grhData);

                        // If already exists as stationary, delete first
                        if (grhData is StationaryGrhData)
                        {
                            grhIndex = grhData.GrhIndex; // We will re-use this GrhIndex
                            GrhInfo.Delete(grhData);
                            deletedGrhDatas.Add(grhData);
                            grhData = null;
                        }
                    }

                    // Add new
                    if (grhData == null)
                    {
                        grhData = GrhInfo.CreateAutomaticAnimatedGrhData(cm, cat, grhIndex);
                        addedGrhDatas.Add(grhData);
                    }

                    // Add to GrhDataFileTags
                    if (grhDataFileTags.ContainsKey(grhData))
                    {
                        throw new GrhDataException(grhData, string.Format("Found more than one animated GrhData with the categorization: `{0}`." +
                            " Make sure that you do not have multiple sub-folders in /DevContent/ in the same folder with the same name but different folder tags (e.g. /_Sprite[s100]/ and /_Sprite[s200]/).",
                            grhData.Categorization));
                    }

                    grhDataFileTags.Add(grhData, fileTags);
                }
            }

            // Now check if there are any GrhDatas to be deleted by taking existing GrhDatas, getting their relative path, and
            // see if that exists in our relative path list we built earlier against the file system
            foreach (var toDelete in grhDatasToDelete)
            {
                GrhInfo.Delete(toDelete);
                deletedGrhDatas.Add(toDelete);
            }

            if (log.IsInfoEnabled)
                log.WarnFormat("Automatic GrhData creation update resulted in `{0}` new GrhData(s) and `{1}` deleted GrhData(s).", addedGrhDatas.Count, deletedGrhDatas.Count);

            added = addedGrhDatas.ToArray();
            deleted = deletedGrhDatas.ToArray();
        }
        /// <summary>
        /// Updates all of the automaticly added GrhDatas.
        /// </summary>
        /// <param name="cm"><see cref="IContentManager"/> to use for new GrhDatas.</param>
        /// <param name="rootGrhDir">Root Grh texture directory.</param>
        /// <param name="added">The GrhDatas that were added (empty if none were added).</param>
        /// <param name="deleted">The GrhDatas that were deleted (empty if none were added).</param>
        /// <param name="grhDataFileTags">The file tags for the corresponding GrhDatas.</param>
        /// <returns>
        /// IEnumerable of all of the new GrhDatas created.
        /// </returns>
        public static void Update(IContentManager cm, string rootGrhDir, out GrhData[] added, out GrhData[] deleted, out Dictionary <GrhData, GrhData.FileTags> grhDataFileTags)
        {
            if (!rootGrhDir.EndsWith("\\") && !rootGrhDir.EndsWith("/"))
            {
                rootGrhDir += "/";
            }

            // Clear the temporary content to make sure we have plenty of working memory
            cm.Unload(ContentLevel.Temporary, true);

            // Get the relative file path for all files up-front (only do this once since it doesn't scale well)
            var relFilePaths = Directory.GetFiles(rootGrhDir, "*", SearchOption.AllDirectories)
                               .Select(x => x.Replace('\\', '/').Substring(rootGrhDir.Length))
                               .ToArray();

            // Also grab the existing GrhDatas
            var existingGrhDatas = GrhInfo.GrhDatas.ToDictionary(x => x.Categorization.ToString(), x => x);

            // Go through each file and do the adds
            grhDataFileTags = new Dictionary <GrhData, GrhData.FileTags>();
            HashSet <GrhData> addedGrhDatas           = new HashSet <GrhData>();
            HashSet <GrhData> deletedGrhDatas         = new HashSet <GrhData>();
            HashSet <GrhData> grhDatasToDelete        = new HashSet <GrhData>(existingGrhDatas.Values);
            HashSet <string>  checkedAnimationRelDirs = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            foreach (var relFilePath in relFilePaths)
            {
                // Before doing anything else, ensure it is a valid file type to handle
                string fileExtension = Path.GetExtension(relFilePath);
                if (!_graphicFileSuffixes.Contains(fileExtension, StringComparer.OrdinalIgnoreCase))
                {
                    continue;
                }

                string absFilePath = rootGrhDir + relFilePath;

                // Grab some stuff based on the file path
                string absDir = Path.GetDirectoryName(absFilePath);
                if (rootGrhDir.Length >= absDir.Length)
                {
                    continue;
                }

                string relDir                   = absDir.Substring(rootGrhDir.Length);
                string parentDirName            = absDir.Substring(Path.GetDirectoryName(absDir).Length + 1);
                string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(relFilePath);

                bool isAnimationFrame = parentDirName.StartsWith("_");

                if (!isAnimationFrame)
                {
                    // ** Stationary **

                    var fileTags = GrhData.FileTags.Create(fileNameWithoutExtension);

                    // Build the categorization info
                    string category          = relDir.Replace("/", SpriteCategorization.Delimiter).Replace("\\", SpriteCategorization.Delimiter);
                    SpriteCategorization cat = new SpriteCategorization(category, fileTags.Title);

                    // Get existing
                    GrhIndex?grhIndex = null;
                    GrhData  grhData;
                    if (existingGrhDatas.TryGetValue(cat.ToString(), out grhData))
                    {
                        grhDatasToDelete.Remove(grhData);
                    }

                    // If already exists as animated, delete first
                    if (grhData != null && (grhData is AnimatedGrhData || grhData is AutomaticAnimatedGrhData))
                    {
                        grhIndex = grhData.GrhIndex; // We will re-use this GrhIndex
                        GrhInfo.Delete(grhData);
                        deletedGrhDatas.Add(grhData);
                        grhData = null;
                    }

                    // Add new
                    string texturePath = "/" + relFilePath.Substring(0, relFilePath.Length - Path.GetExtension(absFilePath).Length);
                    if (grhData == null)
                    {
                        grhData = GrhInfo.CreateGrhData(cm, cat, texturePath, grhIndex);
                        addedGrhDatas.Add(grhData);
                    }
                    else
                    {
                        // Make sure the texture is correct
                        string currTextureName = "/" + ((StationaryGrhData)grhData).TextureName.ToString().TrimStart('/', '\\');
                        if (currTextureName != texturePath)
                        {
                            ((StationaryGrhData)grhData).ChangeTexture(texturePath);
                        }
                    }

                    // Ensure set to auto-size
                    StationaryGrhData stationaryGrhData = (StationaryGrhData)grhData;
                    if (!stationaryGrhData.AutomaticSize)
                    {
                        stationaryGrhData.AutomaticSize = true;
                    }

                    grhDataFileTags.Add(grhData, fileTags);
                }
                else
                {
                    // ** Animated **

                    // Make sure we only handle each animation once (since this will get called for each frame since we're looping over the files)
                    if (!checkedAnimationRelDirs.Add(relDir))
                    {
                        continue;
                    }

                    var fileTags = GrhData.FileTags.Create(parentDirName.Substring(1)); // Remove the _ prefix from directory name

                    // Build the categorization
                    string category = Path.GetDirectoryName(absDir).Substring(rootGrhDir.Length)
                                      .Replace("/", SpriteCategorization.Delimiter).Replace("\\", SpriteCategorization.Delimiter);
                    SpriteCategorization cat = new SpriteCategorization(category, fileTags.Title);

                    // Get existing
                    GrhIndex?grhIndex = null;
                    GrhData  grhData;
                    if (existingGrhDatas.TryGetValue(cat.ToString(), out grhData))
                    {
                        grhDatasToDelete.Remove(grhData);

                        // If already exists as stationary, delete first
                        if (grhData is StationaryGrhData)
                        {
                            grhIndex = grhData.GrhIndex; // We will re-use this GrhIndex
                            GrhInfo.Delete(grhData);
                            deletedGrhDatas.Add(grhData);
                            grhData = null;
                        }
                    }

                    // Add new
                    if (grhData == null)
                    {
                        grhData = GrhInfo.CreateAutomaticAnimatedGrhData(cm, cat, grhIndex);
                        addedGrhDatas.Add(grhData);
                    }

                    grhDataFileTags.Add(grhData, fileTags);
                }
            }

            // Now check if there are any GrhDatas to be deleted by taking existing GrhDatas, getting their relative path, and
            // see if that exists in our relative path list we built earlier against the file system
            foreach (var toDelete in grhDatasToDelete)
            {
                GrhInfo.Delete(toDelete);
                deletedGrhDatas.Add(toDelete);
            }

            if (log.IsInfoEnabled)
            {
                log.WarnFormat("Automatic GrhData creation update resulted in `{0}` new GrhData(s) and `{1}` deleted GrhData(s).", addedGrhDatas.Count, deletedGrhDatas.Count);
            }

            added   = addedGrhDatas.ToArray();
            deleted = deletedGrhDatas.ToArray();
        }
 public static void Write(this IValueWriter writer, string name, SpriteCategorization value)
 {
     writer.Write(name, value.ToString());
 }