context: #region Copyright & License Information
  /*
   * Copyright 2007-2016 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.ComponentModel;
  using System.Drawing;
  using System.Drawing.Imaging;
  using System.Globalization;
  using System.Linq;
  using System.Reflection;
  using System.Runtime.Serialization;
  using System.Text.RegularExpressions;
  using OpenRA.Graphics;
  using OpenRA.Primitives;
  
  namespace OpenRA
  {
  	public static class FieldLoader
  	{
  		[Serializable]
  		public class MissingFieldsException : YamlException
  		{
  			public readonly string[] Missing;
  			public readonly string Header;
  			public override string Message
  			{
  				get
  				{
  					return (string.IsNullOrEmpty(Header) ? "" : Header + ": ") + Missing[0]
  						+ string.Concat(Missing.Skip(1).Select(m => ", " + m));
  				}
  			}
  
  			public MissingFieldsException(string[] missing, string header = null, string headerSingle = null) : base(null)
  			{
  				Header = missing.Length > 1 ? header : headerSingle ?? header;
  				Missing = missing;
  			}
  
  			public override void GetObjectData(SerializationInfo info, StreamingContext context)
  			{
  				base.GetObjectData(info, context);
  				info.AddValue("Missing", Missing);
  				info.AddValue("Header", Header);
  			}
  		}
  
  		public static Func<string, Type, string, object> InvalidValueAction = (s, t, f) =>
  		{
  			throw new YamlException("FieldLoader: Cannot parse `{0}` into `{1}.{2}` ".F(s, f, t));
  		};
  
  		public static Action<string, Type> UnknownFieldAction = (s, f) =>
  		{
  			throw new NotImplementedException("FieldLoader: Missing field `{0}` on `{1}`".F(s, f.Name));
  		};
  
  		static readonly ConcurrentCache<Type, FieldLoadInfo[]> TypeLoadInfo =
  			new ConcurrentCache<Type, FieldLoadInfo[]>(BuildTypeLoadInfo);
  		static readonly ConcurrentCache<MemberInfo, bool> MemberHasTranslateAttribute =
  			new ConcurrentCache<MemberInfo, bool>(member => member.HasAttribute<TranslateAttribute>());
  
  		static readonly object TranslationsLock = new object();
  		static Dictionary<string, string> translations;
  
  		public static void Load(object self, MiniYaml my)
  		{
  			var loadInfo = TypeLoadInfo[self.GetType()];
  			var missing = new List<string>();
  
  			Dictionary<string, MiniYaml> md = null;
  
  			foreach (var fli in loadInfo)
  			{
  				object val;
  
  				if (md == null)
  					md = my.ToDictionary();
  				if (fli.Loader != null)
  				{
  					if (!fli.Attribute.Required || md.ContainsKey(fli.YamlName))
  						val = fli.Loader(my);
  					else
  					{
  						missing.Add(fli.YamlName);
  						continue;
  					}
  				}
  				else
  				{
  					if (!TryGetValueFromYaml(fli.YamlName, fli.Field, md, out val))
  					{
  						if (fli.Attribute.Required)
  							missing.Add(fli.YamlName);
  						continue;
  					}
  				}
  
  				fli.Field.SetValue(self, val);
  			}
  
  			if (missing.Any())
  				throw new MissingFieldsException(missing.ToArray());
  		}
  
  		static bool TryGetValueFromYaml(string yamlName, FieldInfo field, Dictionary<string, MiniYaml> md, out object ret)
  		{
  			ret = null;
  
  			MiniYaml yaml;
  			if (!md.TryGetValue(yamlName, out yaml))
  				return false;
  
  			ret = GetValue(field.Name, field.FieldType, yaml, field);
  			return true;
  		}
  
  		public static T Load<T>(MiniYaml y) where T : new()
  		{
  			var t = new T();
  			Load(t, y);
  			return t;
  		}
  
  		public static void LoadField(object target, string key, string value)
  		{
  			const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
  
  			key = key.Trim();
  
  			var field = target.GetType().GetField(key, Flags);
  			if (field != null)
  			{
  				var sa = field.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
  				if (!sa.FromYamlKey)
  					field.SetValue(target, GetValue(field.Name, field.FieldType, value, field));
  				return;
  			}
  
  			var prop = target.GetType().GetProperty(key, Flags);
  			if (prop != null)
  			{
  				var sa = prop.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
  				if (!sa.FromYamlKey)
  					prop.SetValue(target, GetValue(prop.Name, prop.PropertyType, value, prop), null);
  				return;
  			}
  
  			UnknownFieldAction(key, target.GetType());
  		}
  
  		public static T GetValue<T>(string field, string value)
  		{
  			return (T)GetValue(field, typeof(T), value, null);
  		}
  
  		public static object GetValue(string fieldName, Type fieldType, string value)
  		{
  			return GetValue(fieldName, fieldType, value, null);
  		}
  
  		public static object GetValue(string fieldName, Type fieldType, string value, MemberInfo field)
  		{
  			return GetValue(fieldName, fieldType, new MiniYaml(value), field);
  		}
  
  		public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field)
  		{
  			var value = yaml.Value;
  			if (value != null) value = value.Trim();
  
  			if (fieldType == typeof(int))
  			{
  				int res;
  				if (Exts.TryParseIntegerInvariant(value, out res))
  					return res;
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(ushort))
  			{
  				ushort res;
  				if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out res))
  					return res;
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  
  			if (fieldType == typeof(long))
  			{
  				long res;
  				if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out res))
  					return res;
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(float))
  			{
  				float res;
  				if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
  					return res * (value.Contains('%') ? 0.01f : 1f);
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(decimal))
  			{
  				decimal res;
  				if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
  					return res * (value.Contains('%') ? 0.01m : 1m);
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(string))
  			{
  				if (field != null && MemberHasTranslateAttribute[field] && value != null)
  					return Regex.Replace(value, "@[^@]+@", m => Translate(m.Value.Substring(1, m.Value.Length - 2)), RegexOptions.Compiled);
  				return value;
  			}
  			else if (fieldType == typeof(Color))
  			{
  				Color color;
  				if (value != null && HSLColor.TryParseRGB(value, out color))
  					return color;
  
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(Color[]))
  			{
  				if (value != null)
  				{
  					var parts = value.Split(',');
  					var colors = new Color[parts.Length];
  
  					for (var i = 0; i < colors.Length; i++)
  						if (!HSLColor.TryParseRGB(parts[i], out colors[i]))
  							return InvalidValueAction(value, fieldType, fieldName);
  
  					return colors;
  				}
  
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(HSLColor))
  			{
  				if (value != null)
  				{
  					Color rgb;
  					if (HSLColor.TryParseRGB(value, out rgb))
  						return new HSLColor(rgb);
  
  					// Allow old HSLColor/ColorRamp formats to be parsed as HSLColor
  					var parts = value.Split(',');
  					if (parts.Length == 3 || parts.Length == 4)
  						return new HSLColor(
  							(byte)Exts.ParseIntegerInvariant(parts[0]).Clamp(0, 255),
  							(byte)Exts.ParseIntegerInvariant(parts[1]).Clamp(0, 255),
  							(byte)Exts.ParseIntegerInvariant(parts[2]).Clamp(0, 255));
  				}
  
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(Hotkey))
  			{
  				Hotkey res;
  				if (Hotkey.TryParse(value, out res))
  					return res;
  
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(WDist))
  			{
  				WDist res;
  				if (WDist.TryParse(value, out res))
  					return res;
  
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(WVec))
  			{
  				if (value != null)
  				{
  					var parts = value.Split(',');
  					if (parts.Length == 3)
  					{
  						WDist rx, ry, rz;
  						if (WDist.TryParse(parts[0], out rx) && WDist.TryParse(parts[1], out ry) && WDist.TryParse(parts[2], out rz))
  							return new WVec(rx, ry, rz);
  					}
  				}
  
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(WVec[]))
  			{
  				if (value != null)
  				{
  					var parts = value.Split(',');
  
  					if (parts.Length % 3 != 0)
  						return InvalidValueAction(value, fieldType, fieldName);
  
  					var vecs = new WVec[parts.Length / 3];
  
  					for (var i = 0; i < vecs.Length; ++i)
  					{
  						WDist rx, ry, rz;
  						if (WDist.TryParse(parts[3 * i], out rx) && WDist.TryParse(parts[3 * i + 1], out ry) && WDist.TryParse(parts[3 * i + 2], out rz))
  							vecs[i] = new WVec(rx, ry, rz);
  					}
  
  					return vecs;
  				}
  
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(WPos))
  			{
  
gt: 				if (value != null)