text: ﻿using System;
  using System.Collections.Generic;
  using System.Diagnostics.CodeAnalysis;
  using System.IO;
  using Robust.Server.Interfaces.GameObjects;
  using Robust.Server.Interfaces.Maps;
  using Robust.Shared.Interfaces.Map;
  using Robust.Shared.Interfaces.Resources;
  using Robust.Shared.IoC;
  using Robust.Shared.Log;
  using Robust.Shared.Map;
  using YamlDotNet.RepresentationModel;
  using Robust.Shared.Utility;
  using Robust.Shared.Serialization;
  using Robust.Shared.GameObjects;
  using System.Globalization;
  using Robust.Shared.Interfaces.GameObjects;
  using System.Linq;
  using Robust.Server.Interfaces.Timing;
  using Robust.Shared.GameObjects.Components.Map;
  using Robust.Shared.Interfaces.Serialization;
  using Robust.Shared.Prototypes;
  using YamlDotNet.Core;
  
  namespace Robust.Server.Maps
  {
      /// <summary>
      ///     Saves and loads maps to the disk.
      /// </summary>
      public class MapLoader : IMapLoader
      {
          private const int MapFormatVersion = 2;
  
          [Dependency] private readonly IResourceManager _resMan = default!;
          [Dependency] private readonly IMapManagerInternal _mapManager = default!;
          [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
          [Dependency] private readonly IServerEntityManagerInternal _serverEntityManager = default!;
          [Dependency] private readonly IPauseManager _pauseManager = default!;
          [Dependency] private readonly IComponentManager _componentManager = default!;
          [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  
          public event Action<YamlStream, string>? LoadedMapData;
  
          /// <inheritdoc />
          public void SaveBlueprint(GridId gridId, string yamlPath)
          {
              var grid = _mapManager.GetGrid(gridId);
  
              var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _componentManager, _prototypeManager);
              context.RegisterGrid(grid);
              var root = context.Serialize();
              var document = new YamlDocument(root);
  
              var resPath = new ResourcePath(yamlPath).ToRootedPath();
              _resMan.UserData.CreateDir(resPath.Directory);
  
              using (var file = _resMan.UserData.Create(resPath))
              {
                  using (var writer = new StreamWriter(file))
                  {
                      var stream = new YamlStream();
  
                      stream.Add(document);
                      stream.Save(new YamlMappingFix(new Emitter(writer)), false);
                  }
              }
          }
  
          /// <inheritdoc />
          public IMapGrid? LoadBlueprint(MapId mapId, string path)
          {
              TextReader reader;
              var resPath = new ResourcePath(path).ToRootedPath();
  
              // try user
              if (!_resMan.UserData.Exists(resPath))
              {
                  Logger.InfoS("map", $"No user blueprint path: {resPath}");
  
                  // fallback to content
                  if (_resMan.TryContentFileRead(resPath, out var contentReader))
                  {
                      reader = new StreamReader(contentReader);
                  }
                  else
                  {
                      Logger.ErrorS("map", $"No blueprint found: {resPath}");
                      return null;
                  }
              }
              else
              {
                  var file = _resMan.UserData.OpenRead(resPath);
                  reader = new StreamReader(file);
              }
  
              IMapGrid grid;
              using (reader)
              {
                  Logger.InfoS("map", $"Loading Grid: {resPath}");
  
                  var data = new MapData(reader);
  
                  LoadedMapData?.Invoke(data.Stream, resPath.ToString());
  
                  if (data.GridCount != 1)
                  {
                      throw new InvalidDataException("Cannot instance map with multiple grids as blueprint.");
                  }
  
                  var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _componentManager, _prototypeManager, (YamlMappingNode)data.RootNode, mapId);
                  context.Deserialize();
                  grid = context.Grids[0];
  
                  if (!context.MapIsPostInit && _pauseManager.IsMapInitialized(mapId))
                  {
                      foreach (var entity in context.Entities)
                      {
                          entity.RunMapInit();
                      }
                  }
              }
  
              return grid;
          }
  
          /// <inheritdoc />
          public void SaveMap(MapId mapId, string yamlPath)
          {
              Logger.InfoS("map", $"Saving map {mapId} to {yamlPath}");
              var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _componentManager, _prototypeManager);
              foreach (var grid in _mapManager.GetAllMapGrids(mapId))
              {
                  context.RegisterGrid(grid);
              }
  
              var document = new YamlDocument(context.Serialize());
  
              var resPath = new ResourcePath(yamlPath).ToRootedPath();
              _resMan.UserData.CreateDir(resPath.Directory);
  
              using (var file = _resMan.UserData.Create(resPath))
              {
                  using (var writer = new StreamWriter(file))
                  {
                      var stream = new YamlStream();
  
                      stream.Add(document);
                      stream.Save(new YamlMappingFix(new Emitter(writer)), false);
                  }
              }
              Logger.InfoS("map", "Save completed!");
          }
  
          /// <inheritdoc />
          public void LoadMap(MapId mapId, string path)
          {
              TextReader reader;
              var resPath = new ResourcePath(path).ToRootedPath();
  
              // try user
              if (!_resMan.UserData.Exists(resPath))
              {
                  Logger.InfoS("map", $"No user map found: {resPath}");
  
                  // fallback to content
                  if (_resMan.TryContentFileRead(resPath, out var contentReader))
                  {
                      reader = new StreamReader(contentReader);
                  }
                  else
                  {
                      Logger.ErrorS("map", $"No map found: {resPath}");
                      return;
                  }
              }
              else
              {
                  var file = _resMan.UserData.OpenRead(resPath);
                  reader = new StreamReader(file);
              }
  
              using (reader)
              {
                  Logger.InfoS("map", $"Loading Map: {resPath}");
  
                  var data = new MapData(reader);
  
                  LoadedMapData?.Invoke(data.Stream, resPath.ToString());
  
                  var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _componentManager, _prototypeManager, (YamlMappingNode)data.RootNode, mapId);
                  context.Deserialize();
  
                  if (!context.MapIsPostInit && _pauseManager.IsMapInitialized(mapId))
                  {
                      foreach (var entity in context.Entities)
                      {
                          entity.RunMapInit();
                      }
                  }
              }
          }
  
          /// <summary>
          ///     Handles the primary bulk of state during the map serialization process.
          /// </summary>
          private class MapContext : YamlObjectSerializer.Context, IEntityLoadContext
          {
              private readonly IMapManagerInternal _mapManager;
              private readonly ITileDefinitionManager _tileDefinitionManager;
              private readonly IServerEntityManagerInternal _serverEntityManager;
              private readonly IPauseManager _pauseManager;
              private readonly IComponentManager _componentManager;
              private readonly IPrototypeManager _prototypeManager;
  
              private readonly Dictionary<GridId, int> GridIDMap = new Dictionary<GridId, int>();
              public readonly List<IMapGrid> Grids = new List<IMapGrid>();
  
              private readonly Dictionary<EntityUid, int> EntityUidMap = new Dictionary<EntityUid, int>();
              private readonly Dictionary<int, EntityUid> UidEntityMap = new Dictionary<int, EntityUid>();
              public readonly List<IEntity> Entities = new List<IEntity>();
  
              private readonly List<(IEntity, YamlMappingNode)> _entitiesToDeserialize
                  = new List<(IEntity, YamlMappingNode)>();
  
              private bool IsBlueprintMode => GridIDMap.Count == 1;
  
              private int uidCounter;
  
              private readonly YamlMappingNode RootNode;
              private readonly MapId TargetMap;
  
              private Dictionary<string, YamlMappingNode>? CurrentReadingEntityComponents;
  
              private string? CurrentWritingComponent;
              private IEntity? CurrentWritingEntity;
  
              private Dictionary<ushort, string>? _tileMap;
  
              public bool MapIsPostInit { get; private set; }
  
              public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs, IServerEntityManagerInternal entities, IPauseManager pauseManager, IComponentManager componentManager, IPrototypeManager prototypeManager)
              {
                  _mapManager = maps;
                  _tileDefinitionManager = tileDefs;
                  _serverEntityManager = entities;
                  _pauseManager = pauseManager;
                  _componentManager = componentManager;
                  _prototypeManager = prototypeManager;
  
                  RootNode = new YamlMappingNode();
              }
  
              public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs, IServerEntityManagerInternal entities,
                  IPauseManager pauseManager, IComponentManager componentManager, IPrototypeManager prototypeManager, YamlMappingNode node, MapId targetMapId)
              {
                  _mapManager = maps;
                  _tileDefinitionManager = tileDefs;
                  _serverEntityManager = entities;
                  _pauseManager = pauseManager;
                  _componentManager = componentManager;
  
                  RootNode = node;
                  TargetMap = targetMapId;
                  _prototypeManager = prototypeManager;
              }
  
              // Deserialization
              public void Deserialize()
              {
                  // Verify that prototypes for all the entities exist and throw if they don't.
                  VerifyEntitiesExist();
  
                  // First we load map meta data like version.
                  ReadMetaSection();
  
                  // Create the new map.
                  AllocMap();
  
                  // Load grids.
                  ReadTileMapSection();
                  ReadGridSection();
  
                  // Entities are first allocated. This allows us to know the future UID of all entities on the map before
                  // even ExposeData is loaded. This allows us to resolve serialized EntityUid instances correctly.
                  AllocEntities();
  
                  // Actually instance components and run ExposeData on them.
                  FinishEntitiesLoad();
  
                  // Clear the net tick numbers so that components from prototypes (not modified by map)
                  // aren't sent over the wire initially.
                  ResetNetTicks();
  
                  // Grid entities were NOT created inside ReadGridSection().
                  // We have to fix the created grids up with the grid entities deserialized from the map.
                  FixMapEntities();
  
                  // We have to attach grids to the target map here.
                  // If we don't, initialization & startup can fail for some entities.
                  AttachMapEntities();
  
                  // Run Initialize on all components.
                  FinishEntitiesInitialization();
  
                  // Run Startup on all components.
                  FinishEntitiesStartup();
              }
  
              private void VerifyEntitiesExist()
              {
                  var fail = false;
                  var entities = RootNode.GetNode<YamlSequenceNode>("entities");
                  var reportedError = new HashSet<string>();
                  foreach (var entityDef in entities.Cast<YamlMappingNode>())
                  {
                      if (entityDef.TryGetNode("type", out var typeNode))
                      {
                          var type = typeNode.AsString();
                          if (!_prototypeManager.HasIndex<EntityPrototype>(type) && !reportedError.Contains(type))
                          {
                              Logger.Error("Missing prototype for map: {0}", type);
                              fail = true;
                              reportedError.Add(type);
                          }
                      }
                  }
  
                  if (fail)
                  {
                      throw new InvalidOperationException(
                          "Found missing prototypes in map file. Missing prototypes have been dumped to logs.");
                  }
              }
  
              private void ResetNetTicks()
              {
                  foreach (var (entity, data) in _entitiesToDeserialize)
                  {
                      if (!data.TryGetNode("components", out YamlSequenceNode? componentList))
                      {
                          continue;
                      }
  
                      if (entity.Prototype == null)
                      {
                          continue;
                      }
  
                      foreach (var component in _componentManager.GetNetComponents(entity.Uid))
                      {
                          var castComp = (Component) component;
  
                          if (componentList.Any(p => p["type"].AsString() == component.Name))
                          {
                              if (entity.Prototype.Components.ContainsKey(component.Name))
                              {
                                  // This component is modified by the map so we have to send state.
                                  // Though it's still in the prototype itself so creation doesn't need to be sent.
                                  castComp.ClearCreationTick();
                              }
                              else
                              {
                                  // New component that the prototype normally does not have, need to sync full data.
                                  continue;
                              }
                          }
  
                          // This component is not modified by the map file,
                          // so the client will have the same data after instantiating it from prototype ID.
                          castComp.ClearTicks();
                      }
                  }
              }
  
              private void AttachMapEntities()
              {
                  var mapEntity = _mapManager.GetMapEntity(TargetMap);
  
                  foreach (var grid in Grids)
                  {
                      var entity = _serverEntityManager.GetEntity(grid.GridEntityId);
                      entity.Transform.AttachParent(mapEntity);
                  }
              }
  
              private void FixMapEntities()
              {
                  foreach (var entity in Entities)
                  {
                      if (entity.TryGetComponent(out IMapGridComponent? grid))
                      {
                          var castGrid = (MapGrid) grid.Grid;
                          castGrid.GridEntityId = entity.Uid;
                      }
                  }
              }
  
              private void ReadMetaSection()
              {
                  var meta = RootNode.GetNode<YamlMappingNode>("meta");
                  var ver = meta.GetNode("format").AsInt();
                  if (ver != MapFormatVersion)
                  {
                      throw new InvalidDataException("Cannot handle this map file version.");
                  }
  
                  if (meta.TryGetNode("postmapinit", out var mapInitNode))
                  {
                      MapIsPostInit = mapInitNode.AsBool();
                  }
                  else
                  {
                      MapIsPostInit = true;
                  }
              }
  
              private void ReadTileMapSection()
              {
                  // Load tile mapping so that we can map the stored tile IDs into the ones actually used at runtime.
                  _tileMap = new Dictionary<ushort, string>();
  
                  var tileMap = RootNode.GetNode<YamlMappingNode>("tilemap");
                  foreach (var (key, value) in tileMap)
                  {
                      var tileId = (ushort) key.AsInt();
                      var tileDefName = value.AsString();
                      _tileMap.Add(tileId, tileDefName);
                  }
              }
  
              private void ReadGridSection()
              {
                  var grids = RootNode.GetNode<YamlSequenceNode>("grids");
  
                  foreach (var grid in grids)
                  {
                      var newId = new GridId?();
                      YamlGridSerializer.DeserializeGrid(
                          _mapManager, TargetMap, ref newId,
                          (YamlMappingNode)grid["settings"],
                          (YamlSequenceNode)grid["chunks"],
                          _tileMap!,
                          _tileDefinitionManager
                      );
  
                      if (newId != null)
                      {
                          Grids.Add(_mapManager.GetGrid(newId.Value));
                      }
                  }
              }
  
              private void AllocMap()
              {
                  // Both blueprint and map deserialization use this,
                  // so we need to ensure the map exists (and the map entity)
                  // before allocating entities.
  
                  if (!_mapManager.MapExists(TargetMap))
                  {
                      _mapManager.CreateMap(TargetMap);
  
                      if (!MapIsPostInit)
                      {
                          _pauseManager.AddUninitializedMap(TargetMap);
                      }
                  }
              }
  
              private void AllocEntities()
              {
                  var entities = RootNode.GetNode<YamlSequenceNode>("entities");
                  foreach (var entityDef in entities.Cast<YamlMappingNode>())
                  {
                      string? type = null;
                      if (entityDef.TryGetNode("type", out var typeNode))
                      {
                          type = typeNode.AsString();
                      }
  
                      var uid = Entities.Count;
                      if (entityDef.TryGetNode("uid", out var uidNode))
                      {
                          uid = uidNode.AsInt();
                      }
  
                      var entity = _serverEntityManager.AllocEntity(type);
                      Entities.Add(entity);
                      UidEntityMap.Add(uid, entity.Uid);
                      _entitiesToDeserialize.Add((entity, entityDef));
                  }
              }
  
              private void FinishEntitiesLoad()
              {
                  foreach (var (entity, data) in _entitiesToDeserialize)
                  {
                      CurrentReadingEntityComponents = new Dictionary<string, YamlMappingNode>();
                      if (data.TryGetNode("components", out YamlSequenceNode? componentList))
                      {
                          foreach (var compData in componentList)
                          {
                              CurrentReadingEntityComponents[compData["type"].AsString()] = (YamlMappingNode)compData;
                          }
                      }
  
                      _serverEntityManager.FinishEntityLoad(entity, this);
                  }
              }
  
              private void FinishEntitiesInitialization()
              {
                  foreach (var entity in Entities)
                  {
                      _serverEntityManager.FinishEntityInitialization(entity);
                  }
              }
  
              private void FinishEntitiesStartup()
              {
                  foreach (var entity in Entities)
                  {
                      _serverEntityManager.UpdateEntityTree(entity);
                  }
  
                  foreach (var entity in Entities)
                  {
                      _serverEntityManager.FinishEntityStartup(entity);
                  }
  
                  foreach (var entity in Entities)
                  {
                      _serverEntityManager.UpdateEntityTree(entity);
                  }
              }
  
              // Serialization
              public void RegisterGrid(IMapGrid grid)
              {
                  if (GridIDMap.ContainsKey(grid.Index))
                  {
                      throw new InvalidOperationException();
                  }
  
                  Grids.Add(grid);
                  GridIDMap.Add(grid.Index, GridIDMap.Count);
              }
  
              public YamlNode Serialize()
              {
                  WriteMetaSection();
                  WriteTileMapSection();
                  WriteGridSection();
  
                  PopulateEntityList();
                  WriteEntitySection();
  
                  return RootNode;
              }
  
              private void WriteMetaSection()
              {
                  var meta = new YamlMappingNode();
                  RootNode.Add("meta", meta);
                  meta.Add("format", MapFormatVersion.ToString(CultureInfo.InvariantCulture));
                  // TODO: Make these values configurable.
                  meta.Add("name", "DemoStation");
                  meta.Add("author", "Space-Wizards");
  
                  var isPostInit = false;
                  foreach (var grid in Grids)
                  {
                      if (_pauseManager.IsMapInitialized(grid.ParentMapId))
                      {
                          isPostInit = true;
                          break;
                      }
                  }
  
                  meta.Add("postmapinit", isPostInit ? "true" : "false");
              }
  
              private void WriteTileMapSection()
              {
                  var tileMap = new YamlMappingNode();
                  RootNode.Add("tilemap", tileMap);
                  foreach (var tileDefinition in _tileDefinitionManager)
                  {
                      tileMap.Add(tileDefinition.TileId.ToString(CultureInfo.InvariantCulture), tileDefinition.Name);
                  }
              }
  
              private void WriteGridSection()
              {
                  var grids = new YamlSequenceNode();
                  RootNode.Add("grids", grids);
  
                  foreach (var grid in Grids)
                  {
                      var entry = YamlGridSerializer.SerializeGrid(grid);
                      grids.Add(entry);
                  }
              }
  
              private void PopulateEntityList()
              {
                  foreach (var entity in _serverEntityManager.GetEntities())
                  {
                      if (IsMapSavable(entity))
                      {
                          var uid = uidCounter++;
                          EntityUidMap.Add(entity.Uid, uid);
                          Entities.Add(entity);
                      }
                  }
              }
  
              private void WriteEntitySection()
              {
                  var entities = new YamlSequenceNode();
                  RootNode.Add("entities", entities);
  
                  foreach (var entity in Entities)
                  {
                      CurrentWritingEntity = entity;
                      var mapping = new YamlMappingNode
                      {
                          {"uid", EntityUidMap[entity.Uid].ToString(CultureInfo.InvariantCulture)}
                      };
  
                      if (entity.Prototype != null)
                      {
                          mapping.Add("type", entity.Prototype.ID);
                      }
  
                      var components = new YamlSequenceNode();
                      // See engine#636 for why the Distinct() call.
                      foreach (var component in entity.GetAllComponents())
                      {
                          var compMapping = new YamlMappingNode();
                          CurrentWritingComponent = component.Name;
                          var compSerializer = YamlObjectSerializer.NewWriter(compMapping, this);
  
                          component.ExposeData(compSerializer);
  
                          // Don't need to write it if nothing was written!
                          if (compMapping.Children.Count != 0)
                          {
                              // Something actually got written!
                              compMapping.Add("type", component.Name);
                              components.Add(compMapping);
                          }
                      }
  
                      if (components.Children.Count != 0)
                      {
                          mapping.Add("components", components);
                      }
  
                      entities.Add(mapping);
                  }
              }
  
              public override bool TryNodeToType(YamlNode node, Type type, [NotNullWhen(true)] out object? obj)
              {
                  if (type == typeof(GridId))
                  {
                      if (node.AsString() == "null")
                      {
                          obj = GridId.Invalid;
                          return true;
                      }
  
                      var val = node.AsInt();
                      if (val >= Grids.Count)
                      {
                          Logger.ErrorS("map", "Error in map file: found local grid ID '{0}' which does not exist.", val);
                      }
                      else
                      {
                          obj = Grids[val].Index;
                          return true;
                      }
                  }
                  if (type == typeof(EntityUid))
                  {
                      if (node.AsString() == "null")
                      {
                          obj = EntityUid.Invalid;
                          return true;
                      }
  
                      var val = node.AsInt();
                      if (val >= Entities.Count)
                      {
                          Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.", val);
                      }
                      else
                      {
                          obj = UidEntityMap[val];
                          return true;
                      }
                  }
                  if (typeof(IEntity).IsAssignableFrom(type))
                  {
                      var val = node.AsInt();
                      if (val >= Entities.Count)
                      {
                          Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.", val);
                      }
                      else
                      {
                          obj = Entities[val];
                          return true;
                      }
                  }
                  obj = null;
                  return false;
              }
  
              public override bool TryTypeToNode(object obj, [NotNullWhen(true)] out YamlNode? node)
              {
                  switch (obj)
                  {
                      case GridId gridId:
                          if (!GridIDMap.TryGetValue(gridId, out var gridMapped))
                          {
                              Logger.WarningS("map", "Cannot write grid ID '{0}', falling back to nullspace.", gridId);
                              break;
                          }
                          else
                          {
                              node = new YamlScalarNode(gridMapped.ToString(CultureInfo.InvariantCulture));
                              return true;
                          }
  
                      case EntityUid entityUid:
                          if (!EntityUidMap.TryGetValue(entityUid, out var entityUidMapped))
                          {
                              // Terrible hack to mute this warning on the grids themselves when serializing blueprints.
                              if (!IsBlueprintMode || !CurrentWritingEntity!.HasComponent<MapGridComponent>() ||
                                  CurrentWritingComponent != "Transform")
                              {
                                  Logger.WarningS("map", "Cannot write entity UID '{0}'.", entityUid);
                              }
  
                              node = new YamlScalarNode("null");
                              return true;
                          }
                          else
                          {
                              node = new YamlScalarNode(entityUidMapped.ToString(CultureInfo.InvariantCulture));
                              return true;
                          }
  
                      case IEntity entity:
                          if (!EntityUidMap.TryGetValue(entity.Uid, out var entityMapped))
                          {
                              Logger.WarningS("map", "Cannot write entity UID '{0}'.", entity.Uid);
                              break;
                          }
                          else
                          {
                              node = new YamlScalarNode(entityMapped.ToString(CultureInfo.InvariantCulture));
                              return true;
                          }
                  }
                  node = null;
                  return false;
              }
  
              // Create custom object serializers that will correctly allow data to be overriden by the map file.
              ObjectSerializer IEntityLoadContext.GetComponentSerializer(string componentName, YamlMappingNode? protoData)
              {
                  if (CurrentReadingEntityComponents == null)
                  {
                      throw new InvalidOperationException();
                  }
  
                  var list = new List<YamlMappingNode>();
                  if (CurrentReadingEntityComponents.TryGetValue(componentName, out var mapping))
                  {
                      list.Add(mapping);
                  }
  
                  if (protoData != null)
                  {
                      list.Add(protoData);
                  }
  
                  return YamlObjectSerializer.NewReader(list, this);
              }
  
              public IEnumerable<string> GetExtraComponentTypes()
              {
                  return CurrentReadingEntityComponents!.Keys;
              }
  
              public override bool IsValueDefault<T>(string field, T value, WithFormat<T> format)
              {
                  if (CurrentWritingEntity!.Prototype == null)
                  {
                      // No prototype, can't be default.
                      return false;
                  }
  
                  if (!CurrentWritingEntity.Prototype.Components.TryGetValue(CurrentWritingComponent!, out var compData))
                  {
                      // This component was added mid-game.
                      return false;
                  }
  
                  var testSer = YamlObjectSerializer.NewReader(compData);
                  if (testSer.TryReadDataFieldCached(field, format, out var prototypeVal))
                  {
                      if (value == null)
                      {
                          return prototypeVal == null;
                      }
  
                      return YamlObjectSerializer.IsSerializedEqual(value, prototypeVal);
                  }
  
                  return false;
              }
  
              private bool IsMapSavable(IEntity entity)
              {
                  if (entity.Prototype?.MapSavable == false || !GridIDMap.ContainsKey(entity.Transform.GridID))
                  {
                      return false;
                  }
  
                  // Don't serialize things parented to un savable things.
                  // For example clothes inside a person.
                  var current = entity.Transform;
                  while (current.Parent != null)
                  {
                      if (current.Parent.Owner.Prototype?.MapSavable == false)
                      {
                          return false;
                      }
  
                      current = current.Parent;
                  }
  
                  return true;
              }
          }
  
          /// <summary>
          ///     Does basic pre-deserialization checks on map file load.
          ///     For example, let's not try to use maps with multiple grids as blueprints, shall we?
          /// </summary>
          private class MapData
          {
              public YamlStream Stream { get; }
  
              public YamlNode RootNode => Stream.Documents[0].RootNode;
              public int GridCount { get; }
  
              public MapData(TextReader reader)
              {
                  var stream = new YamlStream();
                  stream.Load(reader);
  
                  if (stream.Documents.Count < 1)
                  {
                      throw new InvalidDataException("Stream has no YAML documents.");
                  }
  
                  // Kinda wanted to just make this print a warning and pick [0] but screw that.
                  // What is this, a hug box?
                  if (stream.Documents.Count > 1)
                  {
                      throw new InvalidDataException("Stream too many YAML documents. Map files store exactly one.");
                  }
  
                  Stream = stream;
                  GridCount = ((YamlSequenceNode)RootNode["grids"]).Children.Count;
              }
          }
      }
  }
  