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.Drawing.Imaging;
  using System.IO;
  using System.Linq;
  using System.Windows.Forms;
  using System.Xml;
  using OpenRA.Graphics;
  
  namespace OpenRA.TilesetBuilder
  {
  	public partial class FormBuilder : Form
  	{
  		string srcfile;
  		int size;
  		TerrainTypeInfo[] terrainType;
  		Dictionary<ushort, TerrainTemplateInfo> templates;
  
  		void CreateNewTileset()
  		{
  			Show();
  			using (var formNew = new FormNew())
  				if (DialogResult.OK == formNew.ShowDialog())
  				{
  					srcfile = formNew.ImageFile;
  					size = formNew.TileSize;
  
  					var bitmap = new Bitmap(srcfile);
  					if (!formNew.PaletteFromImage)
  					{
  						var terrainPalette = new ImmutablePalette(formNew.PaletteFile, new int[0]);
  
  						ColorPalette pal;
  						using (var b = new Bitmap(1, 1, PixelFormat.Format8bppIndexed))
  							pal = b.Palette;
  
  						for (var i = 0; i < 256; i++)
  							pal.Entries[i] = Color.FromArgb(terrainPalette.GetColor(i).ToArgb());
  
  						bitmap.Palette = pal;
  					}
  
  					InitializeSurface(bitmap);
  				}
  		}
  
  		public FormBuilder(string src, string tsize, bool autoExport, string outputDir)
  		{
  			InitializeComponent();
  			LoadTerrainDefinitions();
  
  			size = int.Parse(tsize);
  
  			if (src.Length > 0)
  			{
  				srcfile = src;
  				var bitmap = new Bitmap(srcfile);
  				InitializeSurface(bitmap);
  			}
  			else
  				CreateNewTileset();
  
  			if (autoExport)
  			{
  				Export(outputDir);
  				Application.Exit();
  			}
  		}
  
  		void LoadTileset()
  		{
  			try
  			{
  				var doc = new XmlDocument();
  				doc.Load(Path.ChangeExtension(srcfile, "tsx"));
  
  				foreach (var e in doc.SelectNodes("//name").OfType<XmlElement>())
  					txtTilesetName.Text = e.GetAttribute("value");
  
  				foreach (var e in doc.SelectNodes("//terrain").OfType<XmlElement>())
  					surface1.TerrainTypes[int.Parse(e.GetAttribute("x")),
  						int.Parse(e.GetAttribute("y"))] = int.Parse(e.GetAttribute("t"));
  
  				foreach (var e in doc.SelectNodes("//template").OfType<XmlElement>())
  					surface1.Templates.Add(new Template
  					{
  						Cells = e.SelectNodes("./cell").OfType<XmlElement>()
  							.Select(f => new int2(int.Parse(f.GetAttribute("x")), int.Parse(f.GetAttribute("y"))))
  							.ToDictionary(a => a, a => true)
  					});
  			}
  			catch { }
  		}
  
  		void InitializeSurface(Bitmap bitmap)
  		{
  			surface1.TileSize = size;
  			surface1.Image = bitmap;
  			surface1.TilesPerRow = surface1.Image.Size.Width / surface1.TileSize;
  			surface1.Image.SetResolution(96, 96); // people keep being noobs about DPI, and GDI+ cares.
  			surface1.TerrainTypes = new int[surface1.Image.Width / size, surface1.Image.Height / size]; /* all passable by default */
  			surface1.Templates = new List<Template>();
  			surface1.Size = surface1.Image.Size;
  			surface1.Enabled = true;
  			LoadTileset();
  		}
  
  		void Save()
  		{
  			using (var w = XmlWriter.Create(Path.ChangeExtension(srcfile, "tsx"),
  				new XmlWriterSettings { Indent = true, IndentChars = "  " }))
  			{
  				var tilesetName = txtTilesetName.Text;
  
  				if (tilesetName.Length < 1)
  					tilesetName = "Temperat";
  
  				w.WriteStartDocument();
  
  				w.WriteStartElement("tileset");
  				w.WriteStartElement("name");
  				w.WriteAttributeString("value", tilesetName);
  				w.WriteEndElement();
  
  				for (var i = 0; i <= surface1.TerrainTypes.GetUpperBound(0); i++)
  					for (var j = 0; j <= surface1.TerrainTypes.GetUpperBound(1); j++)
  						if (surface1.TerrainTypes[i, j] != 0)
  						{
  							w.WriteStartElement("terrain");
  							w.WriteAttributeString("x", i.ToString());
  							w.WriteAttributeString("y", j.ToString());
  							w.WriteAttributeString("t", surface1.TerrainTypes[i, j].ToString());
  							w.WriteEndElement();
  						}
  
  				foreach (var t in surface1.Templates)
  				{
  					w.WriteStartElement("template");
  
  					foreach (var c in t.Cells.Keys)
  					{
  						w.WriteStartElement("cell");
  						w.WriteAttributeString("x", c.X.ToString());
  						w.WriteAttributeString("y", c.Y.ToString());
  						w.WriteEndElement();
  					}
  
  					w.WriteEndElement();
  				}
  
  				w.WriteEndElement();
  				w.WriteEndDocument();
  			}
  		}
  
  		void LoadTerrainDefinitions()
  		{
  			var terrainDefinition = new Dictionary<string, TerrainTypeInfo>();
  			var yaml = MiniYaml.FromFile("defaults.yaml").FirstOrDefault(y => y.Key == "Terrain").Value.Nodes;
  			terrainDefinition = yaml.Select(n => new TerrainTypeInfo(n.Value)).ToDictionary(t => t.Type);
  
  			surface1.Icon = new Bitmap[terrainDefinition.Keys.Count];
  			terrainType = new TerrainTypeInfo[terrainDefinition.Keys.Count];
  			templates = new Dictionary<ushort, TerrainTemplateInfo>();
  
  			var title = this.Text;
  			surface1.UpdateMouseTilePosition +=
  				(x, y, tileNr) =>
  				{
  					Text = "{0} - {1} ({2,3}, {3,3}) tileNr: {4,3}".F(title, txtTilesetName.Text, x, y, tileNr);
  				};
  
  			surface1.Enabled = false;
  			var i = 0;
  			foreach (var deftype in terrainDefinition)
  			{
  				var icon = new Bitmap(16, 16);
  
  				// Loop through the images pixels to reset color.
  				for (var x = 0; x < icon.Width; x++)
  				{
  					for (var y = 0; y < icon.Height; y++)
  					{
  						var newColor = deftype.Value.Color;
  						icon.SetPixel(x, y, Color.FromArgb(newColor.ToArgb()));
  					}
  				}
  
  				surface1.Icon[i] = icon;
  				terrainType[i] = deftype.Value;
  
  				var terrainTypeButton = new ToolStripButton(deftype.Key, icon, TerrainTypeSelectorClicked);
  				terrainTypeButton.ToolTipText = deftype.Key;
  				terrainTypeButton.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
  				terrainTypeButton.Tag = i.ToString();
  				terrainTypeButton.ImageAlign = ContentAlignment.MiddleLeft;
  				terrainTypes.Items.Add(terrainTypeButton);
  				i++;
  			}
  		}
  
  		void TerrainTypeSelectorClicked(object sender, EventArgs e)
  		{
  			var tsb = (ToolStripButton)sender;
  			surface1.InputMode = tsb.Tag as string;
  			foreach (var innerTsb in tsb.Owner.Items.OfType<ToolStripButton>())
  				innerTsb.Checked = false;
  			tsb.Checked = true;
  		}
  
  		void SaveClicked(object sender, EventArgs e) { Save(); }
  		void ShowOverlaysClicked(object sender, EventArgs e)
  		{
  			surface1.ShowTerrainTypes = ((ToolStripButton)sender).Checked;
  			surface1.Invalidate();
  		}
  
  		void ExportClicked(object sender, EventArgs e)
  		{
  			Export("Tilesets");
  		}
  
  		void DumpClicked(object sender, EventArgs e)
  		{
  			ExportTemplateToTileNumberMapping();
  		}
  
  		string ExportPalette(List<Color> p, string file)
  		{
  			while (p.Count < Palette.Size) p.Add(Color.Black); // pad the palette out with extra blacks
  			var paletteData = p.Take(Palette.Size).SelectMany(
  				c => new byte[] { (byte)(c.R >> 2), (byte)(c.G >> 2), (byte)(c.B >> 2) }).ToArray();
  			File.WriteAllBytes(file, paletteData);
  			return file;
  		}
  
  		string ExportTemplate(Template t, int n, string suffix, string dir)
  		{
  			var tileSize = size;
  			var filename = Path.Combine(dir, "{0}{1:00}{2}".F(txtTilesetName.Text, n, suffix));
  			var totalTiles = t.Width * t.Height;
  
  			var ms = new MemoryStream();
  			using (var bw = new BinaryWriter(ms))
  			{
  				bw.Write((ushort)tileSize);
  				bw.Write((ushort)tileSize);
  				bw.Write((uint)totalTiles);
  				bw.Write((ushort)t.Width);
  				bw.Write((ushort)t.Height);
  				bw.Write((uint)0);			// filesize placeholder
  				bw.Flush();
  				bw.Write((uint)ms.Position + 24);	// image start
  				bw.Write((uint)0);			// 0 (32bits)
  				bw.Write((uint)0x2c730f8c);		// magic?
  				bw.Write((uint)0);			// flags start
  				bw.Write((uint)0);			// walk start
  				bw.Write((uint)0);			// index start
  
  				var src = surface1.Image.Clone(new Rectangle(0, 0, surface1.Image.Width, surface1.Image.Height),
  					surface1.Image.PixelFormat);
  
  				var data = src.LockBits(new Rectangle(0, 0, src.Width, src.Height),
  					ImageLockMode.ReadOnly, src.PixelFormat);
  
  				unsafe
  				{
  					var p = (byte*)data.Scan0;
  
  
gt: 					for (var v = 0; v < t.Height; v++)