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.ComponentModel;
  using System.Drawing;
  using System.Drawing.Imaging;
  using System.Globalization;
  using System.Linq;
  using System.Reflection;
  using System.Text.RegularExpressions;
  using OpenRA.Graphics;
  using OpenRA.Primitives;
  
  namespace OpenRA
  {
  	public static class FieldLoader
  	{
  		public static Func<string, Type, string, object> InvalidValueAction = (s, t, f) =>
  		{
  			throw new InvalidOperationException("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()];
  
  			Dictionary<string, MiniYaml> md = null;
  
  			foreach (var fli in loadInfo)
  			{
  				object val;
  
  				if (fli.Loader != null)
  					val = fli.Loader(my);
  				else
  				{
  					if (md == null)
  						md = my.ToDictionary();
  
  					if (!TryGetValueFromYaml(fli.YamlName, fli.Field, md, out val))
  						continue;
  				}
  
  				fli.Field.SetValue(self, val);
  			}
  		}
  
  		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;
  
  			if (yaml.Nodes.Count == 0)
  			{
  				ret = GetValue(field.Name, field.FieldType, yaml.Value, field);
  				return true;
  			}
  
  			throw new InvalidOperationException("TryGetValueFromYaml: unable to load field {0} (of type {1})".F(yamlName, field.FieldType));
  		}
  
  		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)
  		{
  			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 (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 (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])
  					return Regex.Replace(value, "@[^@]+@", m => Translate(m.Value.Substring(1, m.Value.Length - 2)), RegexOptions.Compiled);
  				return value;
  			}
  			else if (fieldType == typeof(Color))
  			{
  				var parts = value.Split(',');
  				if (parts.Length == 3)
  					return Color.FromArgb(
  						Exts.ParseIntegerInvariant(parts[0]).Clamp(0, 255),
  						Exts.ParseIntegerInvariant(parts[1]).Clamp(0, 255),
  						Exts.ParseIntegerInvariant(parts[2]).Clamp(0, 255));
  				if (parts.Length == 4)
  					return Color.FromArgb(
  						Exts.ParseIntegerInvariant(parts[0]).Clamp(0, 255),
  						Exts.ParseIntegerInvariant(parts[1]).Clamp(0, 255),
  						Exts.ParseIntegerInvariant(parts[2]).Clamp(0, 255),
  						Exts.ParseIntegerInvariant(parts[3]).Clamp(0, 255));
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(Color[]))
  			{
  				var parts = value.Split(',');
  
  				if (parts.Length % 4 != 0)
  					return InvalidValueAction(value, fieldType, fieldName);
  
  				var colors = new Color[parts.Length / 4];
  
  				for (var i = 0; i < colors.Length; i++)
  				{
  					colors[i] = Color.FromArgb(
  						Exts.ParseIntegerInvariant(parts[4 * i]).Clamp(0, 255),
  						Exts.ParseIntegerInvariant(parts[4 * i + 1]).Clamp(0, 255),
  						Exts.ParseIntegerInvariant(parts[4 * i + 2]).Clamp(0, 255),
  						Exts.ParseIntegerInvariant(parts[4 * i + 3]).Clamp(0, 255));
  				}
  
  				return colors;
  			}
  			else if (fieldType == typeof(HSLColor))
  			{
  				var parts = value.Split(',');
  
  				// Allow old ColorRamp format to be parsed as HSLColor
  				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(WRange))
  			{
  				WRange res;
  				if (WRange.TryParse(value, out res))
  					return res;
  
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(WVec))
  			{
  				var parts = value.Split(',');
  				if (parts.Length == 3)
  				{
  					WRange rx, ry, rz;
  					if (WRange.TryParse(parts[0], out rx) && WRange.TryParse(parts[1], out ry) && WRange.TryParse(parts[2], out rz))
  						return new WVec(rx, ry, rz);
  				}
  
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(WVec[]))
  			{
  				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)
  				{
  					WRange rx, ry, rz;
  					if (WRange.TryParse(parts[3 * i], out rx)
  							&& WRange.TryParse(parts[3 * i + 1], out ry)
  							&& WRange.TryParse(parts[3 * i + 2], out rz))
  						vecs[i] = new WVec(rx, ry, rz);
  				}
  
  				return vecs;
  			}
  			else if (fieldType == typeof(WPos))
  			{
  				var parts = value.Split(',');
  				if (parts.Length == 3)
  				{
  					WRange rx, ry, rz;
  					if (WRange.TryParse(parts[0], out rx) && WRange.TryParse(parts[1], out ry) && WRange.TryParse(parts[2], out rz))
  						return new WPos(rx, ry, rz);
  				}
  
  				return InvalidValueAction(value, fieldType, fieldName);
  			}
  			else if (fieldType == typeof(WAngle))
  			{
  				int res;
  
gt: 				if (Exts.TryParseIntegerInvariant(value, out res))