context: #region Copyright & License Information
  /*
   * Copyright 2007-2017 The OpenRA Developers (see AUTHORS)
   * This file is part of OpenRA, which is free software. It is made
   * available to you under the terms of the GNU General Public License
   * as published by the Free Software Foundation, either version 3 of
   * the License, or (at your option) any later version. For more
   * information, see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.IO;
  using System.Linq;
  using System.Text;
  using OpenRA.FileSystem;
  using OpenRA.Graphics;
  using OpenRA.Mods.Common.FileFormats;
  using OpenRA.Mods.Common.Traits;
  using OpenRA.Primitives;
  
  namespace OpenRA.Mods.Common.UtilityCommands
  {
  	public abstract class ImportLegacyMapCommand
  	{
  		public readonly int MapSize;
  
  		public ImportLegacyMapCommand(int mapSize)
  		{
  			MapSize = mapSize;
  		}
  
  		public ModData ModData;
  		public Map Map;
  		public List<string> Players = new List<string>();
  		public MapPlayers MapPlayers;
  		bool singlePlayer;
  		int spawnCount;
  
  		protected bool ValidateArguments(string[] args)
  		{
  			return args.Length >= 2;
  		}
  
  		protected void Run(Utility utility, string[] args)
  		{
  			// HACK: The engine code assumes that Game.modData is set.
  			Game.ModData = ModData = utility.ModData;
  
  			var filename = args[1];
  			using (var stream = File.OpenRead(filename))
  			{
  				var file = new IniFile(stream);
  				var basic = file.GetSection("Basic");
  
  				var player = basic.GetValue("Player", string.Empty);
  				if (!string.IsNullOrEmpty(player))
  					singlePlayer = !player.StartsWith("Multi");
  
  				var mapSection = file.GetSection("Map");
  
  				var format = GetMapFormatVersion(basic);
  				ValidateMapFormat(format);
  
  				// The original game isn't case sensitive, but we are.
  				var tileset = GetTileset(mapSection).ToUpperInvariant();
  				if (!ModData.DefaultTileSets.ContainsKey(tileset))
  					throw new InvalidDataException("Unknown tileset {0}".F(tileset));
  
  				Map = new Map(ModData, ModData.DefaultTileSets[tileset], MapSize, MapSize)
  				{
  					Title = basic.GetValue("Name", Path.GetFileNameWithoutExtension(filename)),
  					Author = "Westwood Studios",
  				};
  
  				Map.RequiresMod = ModData.Manifest.Id;
  
  				SetBounds(Map, mapSection);
  
  				ReadPacks(file, filename);
  				ReadTrees(file);
  
  				LoadVideos(file, "BASIC");
  				LoadBriefing(file);
  
  				ReadActors(file);
  
  				LoadSmudges(file, "SMUDGE");
  
  				var waypoints = file.GetSection("Waypoints");
  				LoadWaypoints(waypoints);
  
  				// Create default player definitions only if there are no players to import
  				MapPlayers = new MapPlayers(Map.Rules, Players.Count == 0 ? spawnCount : 0);
  				foreach (var p in Players)
  					LoadPlayer(file, p);
  
  				Map.PlayerDefinitions = MapPlayers.ToMiniYaml();
  			}
  
  			Map.FixOpenAreas();
  
  			var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap";
  
  			Map.Save(ZipFile.Create(dest, new Folder(".")));
  			Console.WriteLine(dest + " saved.");
  		}
  
  		/*
  		 * 1=Tiberium Dawn & Sole Survivor
  		 * 2=Red Alert (also with Counterstrike installed)
  		 * 3=Red Alert (with Aftermath installed)
  		 * 4=Tiberian Sun (including Firestorm) & Red Alert 2 (including Yuri's Revenge)
  		 */
  		static int GetMapFormatVersion(IniSection basicSection)
  		{
  			var iniFormat = basicSection.GetValue("NewINIFormat", "0");
  
  			var iniFormatVersion = 0;
  			Exts.TryParseIntegerInvariant(iniFormat, out iniFormatVersion);
  
  			return iniFormatVersion;
  		}
  
  		public abstract void ValidateMapFormat(int format);
  
  		void LoadBriefing(IniFile file)
  		{
  			var briefingSection = file.GetSection("Briefing", true);
  			if (briefingSection == null)
  				return;
  
  			var briefing = new StringBuilder();
  			foreach (var s in briefingSection)
  				briefing.AppendLine(s.Value);
  
  			if (briefing.Length == 0)
  				return;
  
  			var worldNode = Map.RuleDefinitions.Nodes.FirstOrDefault(n => n.Key == "World");
  			if (worldNode == null)
  			{
  				worldNode = new MiniYamlNode("World", new MiniYaml("", new List<MiniYamlNode>()));
  				Map.RuleDefinitions.Nodes.Add(worldNode);
  			}
  
  			var missionData = worldNode.Value.Nodes.FirstOrDefault(n => n.Key == "MissionData");
  			if (missionData == null)
  			{
  				missionData = new MiniYamlNode("MissionData", new MiniYaml("", new List<MiniYamlNode>()));
  				worldNode.Value.Nodes.Add(missionData);
  			}
  
  			missionData.Value.Nodes.Add(new MiniYamlNode("Briefing", briefing.Replace("\n", " ").ToString()));
  		}
  
  		static void SetBounds(Map map, IniSection mapSection)
  		{
  			var offsetX = Exts.ParseIntegerInvariant(mapSection.GetValue("X", "0"));
  			var offsetY = Exts.ParseIntegerInvariant(mapSection.GetValue("Y", "0"));
  			var width = Exts.ParseIntegerInvariant(mapSection.GetValue("Width", "0"));
  			var height = Exts.ParseIntegerInvariant(mapSection.GetValue("Height", "0"));
  
  			var tl = new PPos(offsetX, offsetY);
  			var br = new PPos(offsetX + width - 1, offsetY + height - 1);
  			map.SetBounds(tl, br);
  		}
  
  		public abstract void ReadPacks(IniFile file, string filename);
  
  		void LoadVideos(IniFile file, string section)
  		{
  			var videos = new List<MiniYamlNode>();
  			foreach (var s in file.GetSection(section))
  			{
  				if (s.Value != "x" && s.Value != "X" && s.Value != "<none>")
  				{
  					switch (s.Key)
  					{
  					case "Intro":
  						videos.Add(new MiniYamlNode("BackgroundVideo", s.Value.ToLower() + ".vqa"));
  						break;
  					case "Brief":
  						videos.Add(new MiniYamlNode("BriefingVideo", s.Value.ToLower() + ".vqa"));
  						break;
  					case "Action":
  						videos.Add(new MiniYamlNode("StartVideo", s.Value.ToLower() + ".vqa"));
  						break;
  					case "Win":
  						videos.Add(new MiniYamlNode("WinVideo", s.Value.ToLower() + ".vqa"));
  						break;
  					case "Lose":
  						videos.Add(new MiniYamlNode("LossVideo", s.Value.ToLower() + ".vqa"));
  						break;
  					}
  				}
  			}
  
  			if (videos.Any())
  			{
  				var worldNode = Map.RuleDefinitions.Nodes.FirstOrDefault(n => n.Key == "World");
  				if (worldNode == null)
  				{
  					worldNode = new MiniYamlNode("World", new MiniYaml("", new List<MiniYamlNode>()));
  					Map.RuleDefinitions.Nodes.Add(worldNode);
  				}
  
  				var missionData = worldNode.Value.Nodes.FirstOrDefault(n => n.Key == "MissionData");
  				if (missionData == null)
  				{
  					missionData = new MiniYamlNode("MissionData", new MiniYaml("", new List<MiniYamlNode>()));
  					worldNode.Value.Nodes.Add(missionData);
  				}
  
  				missionData.Value.Nodes.AddRange(videos);
  			}
  		}
  
  		public virtual void ReadActors(IniFile file)
  		{
  			LoadActors(file, "STRUCTURES", Players, MapSize, Map);
  			LoadActors(file, "UNITS", Players, MapSize, Map);
  			LoadActors(file, "INFANTRY", Players, MapSize, Map);
  		}
  
  		public abstract void LoadPlayer(IniFile file, string section);
  
  		static string Truncate(string s, int maxLength)
  		{
  			return s.Length <= maxLength ? s : s.Substring(0, maxLength);
  		}
  
  		static string GetTileset(IniSection mapSection)
  		{
  			// NOTE: The original isn't case sensitive, we are.
  			// NOTE: Tileset TEMPERAT exists in every C&C game.
  			return Truncate(mapSection.GetValue("Theater", "TEMPERAT"), 8).ToUpperInvariant();
  		}
  
  		static int2 LocationFromMapOffset(int offset, int mapSize)
  		{
  			return new int2(offset % mapSize, offset / mapSize);
  		}
  
  		void LoadWaypoints(IniSection waypointSection)
  		{
  			var actorCount = Map.ActorDefinitions.Count;
  			var wps = waypointSection
  				.Where(kv => Exts.ParseIntegerInvariant(kv.Value) > 0)
  				.Select(kv => Pair.New(Exts.ParseIntegerInvariant(kv.Key),
  					LocationFromMapOffset(Exts.ParseIntegerInvariant(kv.Value), MapSize)));
  
  			// Add waypoint actors skipping duplicate entries
  			foreach (var kv in wps.DistinctBy(location => location.Second))
  			{
  				if (!singlePlayer && kv.First <= 7)
  				{
  					var ar = new ActorReference("mpspawn")
  					{
  						new LocationInit((CPos)kv.Second),
  						new OwnerInit("Neutral")
  					};
  
  					Map.ActorDefinitions.Add(new MiniYamlNode("Actor" + actorCount++, ar.Save()));
  					spawnCount++;
  				}
  				else
  				{
  					var ar = new ActorReference("waypoint")
  					{
  						new LocationInit((CPos)kv.Second),
  						new OwnerInit("Neutral")
  					};
  
  					SaveWaypoint(kv.First, ar);
  				}
  			}
  		}
  
  		public virtual void SaveWaypoint(int waypointNumber, ActorReference waypointReference)
  		{
  			var waypointName = "waypoint" + waypointNumber;
  			Map.ActorDefinitions.Add(new MiniYamlNode(waypointName, waypointReference.Save()));
  		}
  
  		void LoadSmudges(IniFile file, string section)
  		{
  			var scorches = new List<MiniYamlNode>();
  			var craters = new List<MiniYamlNode>();
  			foreach (var s in file.GetSection(section, true))
  			{
  				// loc=type,loc,depth
  				var parts = s.Value.Split(',');
  				var loc = Exts.ParseIntegerInvariant(parts[1]);
  				var type = parts[0].ToLowerInvariant();
  				var key = "{0},{1}".F(loc % MapSize, loc / MapSize);
  				var value = "{0},{1}".F(type, parts[2]);
  
gt: 				var node = new MiniYamlNode(key, value);