context: #region Copyright & License Information
  /*
   * Copyright 2007-2015 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. For more information,
   * see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Drawing;
  using System.Globalization;
  using System.IO;
  using System.Linq;
  using System.Text;
  using OpenRA.FileFormats;
  using OpenRA.FileSystem;
  using OpenRA.Graphics;
  using OpenRA.Primitives;
  using OpenRA.Traits;
  
  namespace OpenRA.Mods.Common.UtilityCommands
  {
  	class LegacyMapImporter
  	{
  		// Mapping from ra overlay index to type string
  		static string[] redAlertOverlayNames =
  		{
  			"sbag", "cycl", "brik", "fenc", "wood",
  			"gold01", "gold02", "gold03", "gold04",
  			"gem01", "gem02", "gem03", "gem04",
  			"v12", "v13", "v14", "v15", "v16", "v17", "v18",
  			"fpls", "wcrate", "scrate", "barb", "sbag",
  		};
  
  		static Dictionary<string, Pair<byte, byte>> overlayResourceMapping = new Dictionary<string, Pair<byte, byte>>()
  		{
  			// RA Gold & Gems
  			{ "gold01", new Pair<byte, byte>(1, 0) },
  			{ "gold02", new Pair<byte, byte>(1, 1) },
  			{ "gold03", new Pair<byte, byte>(1, 2) },
  			{ "gold04", new Pair<byte, byte>(1, 3) },
  			{ "gem01", new Pair<byte, byte>(2, 0) },
  			{ "gem02", new Pair<byte, byte>(2, 1) },
  			{ "gem03", new Pair<byte, byte>(2, 2) },
  			{ "gem04", new Pair<byte, byte>(2, 3) },
  
  			// CnC Tiberium
  			{ "ti1", new Pair<byte, byte>(1, 0) },
  			{ "ti2", new Pair<byte, byte>(1, 1) },
  			{ "ti3", new Pair<byte, byte>(1, 2) },
  			{ "ti4", new Pair<byte, byte>(1, 3) },
  			{ "ti5", new Pair<byte, byte>(1, 4) },
  			{ "ti6", new Pair<byte, byte>(1, 5) },
  			{ "ti7", new Pair<byte, byte>(1, 6) },
  			{ "ti8", new Pair<byte, byte>(1, 7) },
  			{ "ti9", new Pair<byte, byte>(1, 8) },
  			{ "ti10", new Pair<byte, byte>(1, 9) },
  			{ "ti11", new Pair<byte, byte>(1, 10) },
  			{ "ti12", new Pair<byte, byte>(1, 11) },
  		};
  
  		static Dictionary<string, string> overlayActorMapping = new Dictionary<string, string>() {
  			// Fences
  			{ "sbag", "sbag" },
  			{ "cycl", "cycl" },
  			{ "brik", "brik" },
  			{ "fenc", "fenc" },
  			{ "wood", "wood" },
  
  			// Fields
  			{ "v12", "v12" },
  			{ "v13", "v13" },
  			{ "v14", "v14" },
  			{ "v15", "v15" },
  			{ "v16", "v16" },
  			{ "v17", "v17" },
  			{ "v18", "v18" },
  
  			// Crates
  //			{ "wcrate", "crate" },
  //			{ "scrate", "crate" },
  		};
  
  		// TODO: fix this -- will have bitrotted pretty badly.
  		static Dictionary<string, HSLColor> namedColorMapping = new Dictionary<string, HSLColor>()
  		{
  			{ "gold", HSLColor.FromRGB(246, 214, 121) },
  			{ "blue", HSLColor.FromRGB(226, 230, 246) },
  			{ "red", HSLColor.FromRGB(255, 20, 0) },
  			{ "neutral", HSLColor.FromRGB(238, 238, 238) },
  			{ "orange", HSLColor.FromRGB(255, 230, 149) },
  			{ "teal", HSLColor.FromRGB(93, 194, 165) },
  			{ "salmon", HSLColor.FromRGB(210, 153, 125) },
  			{ "green", HSLColor.FromRGB(160, 240, 140) },
  			{ "white", HSLColor.FromRGB(255, 255, 255) },
  			{ "black", HSLColor.FromRGB(80, 80, 80) },
  		};
  
  		static string Truncate(string s, int maxLength)
  		{
  			return s.Length <= maxLength ? s : s.Substring(0, maxLength);
  		}
  
  		int mapSize;
  		int actorCount = 0;
  		Map map;
  		Ruleset rules;
  		List<string> players = new List<string>();
  		Action<string> errorHandler;
  		MapPlayers mapPlayers;
  
  		LegacyMapImporter(string filename, Ruleset rules, Action<string> errorHandler)
  		{
  			this.rules = rules;
  			this.errorHandler = errorHandler;
  
  			ConvertIniMap(filename);
  		}
  
  		public static Map Import(string filename, string mod, Ruleset rules, Action<string> errorHandler)
  		{
  			var map = new LegacyMapImporter(filename, rules, errorHandler).map;
  			map.RequiresMod = mod;
  			map.FixOpenAreas(rules);
  			return map;
  		}
  
  		enum IniMapFormat { RedAlert = 3 } // otherwise, cnc (2 variants exist, we don't care to differentiate)
  
  		public void ConvertIniMap(string iniFile)
  		{
  			using (var stream = GlobalFileSystem.Open(iniFile))
  			{
  				var file = new IniFile(stream);
  				var basic = file.GetSection("Basic");
  				var mapSection = file.GetSection("Map");
  				var legacyMapFormat = (IniMapFormat)Exts.ParseIntegerInvariant(basic.GetValue("NewINIFormat", "0"));
  				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"));
  				mapSize = (legacyMapFormat == IniMapFormat.RedAlert) ? 128 : 64;
  
  				var tileset = Truncate(mapSection.GetValue("Theater", "TEMPERAT"), 8);
  				map = new Map(rules.TileSets[tileset], mapSize, mapSize)
  				{
  					Title = basic.GetValue("Name", Path.GetFileNameWithoutExtension(iniFile)),
  					Author = "Westwood Studios"
  				};
  
  				var tl = new PPos(offsetX, offsetY);
  				var br = new PPos(offsetX + width - 1, offsetY + height - 1);
  				map.SetBounds(tl, br);
  
  				if (legacyMapFormat == IniMapFormat.RedAlert)
  				{
  					UnpackRATileData(ReadPackedSection(file.GetSection("MapPack")));
  					UnpackRAOverlayData(ReadPackedSection(file.GetSection("OverlayPack")));
  					ReadRATrees(file);
  				}
  				else
  				{
  					// CnC
  					using (var s = GlobalFileSystem.Open(iniFile.Substring(0, iniFile.Length - 4) + ".bin"))
  						UnpackCncTileData(s);
  					ReadCncOverlay(file);
  					ReadCncTrees(file);
  				}
  
  				LoadVideos(file, "BASIC");
  				LoadActors(file, "STRUCTURES");
  				LoadActors(file, "UNITS");
  				LoadActors(file, "INFANTRY");
  				LoadActors(file, "SHIPS");
  				LoadSmudges(file, "SMUDGE");
  
  				var wps = file.GetSection("Waypoints")
  						.Where(kv => Exts.ParseIntegerInvariant(kv.Value) > 0)
  						.Select(kv => Pair.New(Exts.ParseIntegerInvariant(kv.Key),
  							LocationFromMapOffset(Exts.ParseIntegerInvariant(kv.Value), mapSize)));
  
  				// Add waypoint actors
  				foreach (var kv in wps)
  				{
  					if (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()));
  					}
  					else
  					{
  						var ar = new ActorReference("waypoint")
  						{
  							new LocationInit((CPos)kv.Second),
  							new OwnerInit("Neutral")
  						};
  
  						map.ActorDefinitions.Add(new MiniYamlNode("waypoint" + kv.First, ar.Save()));
  					}
  				}
  
  				// Create default player definitions only if there are no players to import
  				mapPlayers = new MapPlayers(map.Rules, (players.Count == 0) ? map.SpawnPoints.Value.Length : 0);
  				foreach (var p in players)
  					LoadPlayer(file, p, legacyMapFormat == IniMapFormat.RedAlert);
  				map.PlayerDefinitions = mapPlayers.ToMiniYaml();
  			}
  		}
  
  		static int2 LocationFromMapOffset(int offset, int mapSize)
  		{
  			return new int2(offset % mapSize, offset / mapSize);
  		}
  
  		static MemoryStream ReadPackedSection(IniSection mapPackSection)
  		{
  			var sb = new StringBuilder();
  			for (var i = 1;; i++)
  			{
  				var line = mapPackSection.GetValue(i.ToString(), null);
  
gt: 				if (line == null)