using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using FileHelpers.Helpers;

namespace FileHelpers.Dynamic
{
#if !NETCOREAPP2_0
	using System.CodeDom.Compiler;

	/// <summary>The MAIN class to work with runtime defined records.</summary>
	public abstract class ClassBuilder
	{
		//---------------------
		//->  STATIC METHODS

		#region LoadFromString

		/// <summary>Compiles the source code passed and returns the FIRST Type of the assembly. (Code in C#)</summary>
		/// <param name="classStr">The Source Code of the class in C#</param>
		/// <returns>The Type generated by runtime compilation of the class source.</returns>
		public static Type ClassFromString(string classStr)
		{
			return ClassFromString(classStr, "");
		}

		/// <summary>Compiles the source code passed and returns the FIRST Type of the assembly.</summary>
		/// <param name="classStr">The Source Code of the class in the specified language</param>
		/// <returns>The Type generated by runtime compilation of the class source.</returns>
		/// <param name="lang">One of the .NET Languages</param>
		public static Type ClassFromString(string classStr, NetLanguage lang)
		{
			return ClassFromString(classStr, "", lang);
		}

		/// <summary>Compiles the source code passed and returns the Type with the name className. (Code in C#)</summary>
		/// <param name="classStr">The Source Code of the class in C#</param>
		/// <param name="className">The Name of the Type that must be returned</param>
		/// <returns>The Type generated by runtime compilation of the class source.</returns>
		public static Type ClassFromString(string classStr, string className)
		{
			return ClassFromString(classStr, className, NetLanguage.CSharp);
		}

		private static List<string> mReferences;
		private static readonly object mReferencesLock = new object();

		/// <summary>Compiles the source code passed and returns the Type with the name className.</summary>
		/// <param name="classStr">The Source Code of the class in the specified language</param>
		/// <param name="className">The Name of the Type that must be returned</param>
		/// <param name="lang">One of the .NET Languages</param>
		/// <param name="additionalReferences">List of assemblies to be added in the dynamic compilation </param>
		/// <returns>The Type generated by runtime compilation of the class source.</returns>
		public static Type ClassFromString(string classStr, string className, NetLanguage lang, List<Assembly> additionalReferences = null)
		{
			if (classStr.Length < 4)
			{
				throw new BadUsageException(
					"There is not enough text to be a proper class, load your class and try again");
			}

			var cp = new CompilerParameters();

			//cp.ReferencedAssemblies.Add("System.dll");
			//cp.ReferencedAssemblies.Add("System.Data.dll");
			//cp.ReferencedAssemblies.Add(typeof(ClassBuilder).Assembly.GetModules()[0].FullyQualifiedName);

			bool mustAddSystemData = false;
			lock (mReferencesLock)
			{

				if (mReferences == null)
				{
					mReferences = new List<string>();
					var added = new Dictionary<string, bool>();

					foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
					{
						Module module = assembly.GetModules()[0];
						if (module.Name == "mscorlib.dll" ||
							module.Name == "<Unknown>")
							continue;

						if (module.Name == "System.Data.dll")
							mustAddSystemData = true;

						if (!module.Name.Equals("System.Data.dll", StringComparison.OrdinalIgnoreCase) &&
							!module.Name.Equals("System.Core.dll", StringComparison.OrdinalIgnoreCase) &&
							!module.Name.Equals("System.dll", StringComparison.OrdinalIgnoreCase) &&
							!module.Name.Equals("FileHelpers.dll", StringComparison.OrdinalIgnoreCase) &&
							!module.Name.Equals("System.Xml.dll", StringComparison.OrdinalIgnoreCase)
							)
						{
							continue;
						}

						var moduleName = module.Name.ToLower();

						if (added.ContainsKey(moduleName))
							continue;
						added.Add(moduleName, true);

						if (File.Exists(module.FullyQualifiedName))
							mReferences.Add(module.FullyQualifiedName);
					}

				}

				if (additionalReferences != null)
				{
					foreach (var additionalReference in additionalReferences)
					{
						var moduleName = additionalReference.GetModules()[0].FullyQualifiedName;

						foreach (var reference in mReferences)
						{
							if (reference.Equals(moduleName, StringComparison.OrdinalIgnoreCase))
							{
								moduleName = "";
								break;
							}
						}

						if (moduleName.Length > 0)
							mReferences.Add(moduleName);
					}
				}



			}

			cp.ReferencedAssemblies.AddRange(mReferences.ToArray());

			cp.GenerateExecutable = false;
			cp.GenerateInMemory = true;
			cp.IncludeDebugInformation = false;


			var code = new StringBuilder();

			switch (lang)
			{
				case NetLanguage.CSharp:
					code.Append("using System; using FileHelpers; using System.Collections; using System.Collections.Generic;");
					if (mustAddSystemData)
						code.Append(" using System.Data;");
					break;

				case NetLanguage.VbNet:

					if (
						CultureInfo.CurrentCulture.CompareInfo.IndexOf(classStr,
							"Imports System",
							CompareOptions.IgnoreCase) == -1)
						code.Append("Imports System\n");

					if (
						CultureInfo.CurrentCulture.CompareInfo.IndexOf(classStr,
							"Imports FileHelpers",
							CompareOptions.IgnoreCase) == -1)
						code.Append("Imports FileHelpers\n");

					if (mustAddSystemData &&
						CultureInfo.CurrentCulture.CompareInfo.IndexOf(classStr,
							"Imports System.Data",
							CompareOptions.IgnoreCase) == -1)
						code.Append("Imports System.Data\n");

					break;
			}

			code.Append(classStr);

			CodeDomProvider prov = null;

			switch (lang)
			{
				case NetLanguage.CSharp:
					prov = CodeDomProvider.CreateProvider("cs");
					break;

				case NetLanguage.VbNet:
					prov = CodeDomProvider.CreateProvider("vb");
					break;
			}

			var cr = prov.CompileAssemblyFromSource(cp, code.ToString());

			if (cr.Errors.HasErrors)
			{
				var error = new StringBuilder();
				error.Append("Error Compiling Expression: " + StringHelper.NewLine);
				foreach (CompilerError err in cr.Errors)
					error.AppendFormat("Line {0}: {1}\n", err.Line, err.ErrorText);
				throw new DynamicCompilationException(error.ToString(), classStr, cr.Errors);
			}

			//            Assembly.Load(cr.CompiledAssembly.);
			if (className != "")
				return cr.CompiledAssembly.GetType(className, true, true);
			else
			{
				Type[] ts = cr.CompiledAssembly.GetTypes();
				if (ts.Length > 0)
				{
					foreach (var t in ts)
					{
						if (t.FullName.StartsWith("My.My") == false &&
							t.IsDefined(typeof(TypedRecordAttribute), false))
							return t;
					}
				}

				throw new BadUsageException("The compiled assembly does not have any type inside.");
			}
		}

		#endregion

		//public bool AddCurrentDomainReferences { get; set; }

		#region CreateFromFile

		/// <summary>
		/// Create a class from a source file.
		/// </summary>
		/// <param name="filename">The filename with the source of the class.</param>
		/// <returns>The compiled class.</returns>
		public static Type ClassFromSourceFile(string filename)
		{
			return ClassFromSourceFile(filename, "");
		}

		/// <summary>
		/// Create a class from a source file.
		/// </summary>
		/// <param name="filename">The filename with the source of the class.</param>
		/// <param name="lang">The language used to compile the class.</param>
		/// <returns>The compiled class.</returns>
		public static Type ClassFromSourceFile(string filename, NetLanguage lang)
		{
			return ClassFromSourceFile(filename, "", lang);
		}

		/// <summary>
		/// Create a class from a source file.
		/// </summary>
		/// <param name="filename">The filename with the source of the class.</param>
		/// <param name="className">The name of the class to return.</param>
		/// <returns>The compiled class.</returns>
		public static Type ClassFromSourceFile(string filename, string className)
		{
			return ClassFromSourceFile(filename, className, NetLanguage.CSharp);
		}

		/// <summary>
		/// Create a class from a source file.
		/// </summary>
		/// <param name="filename">The filename with the source of the class.</param>
		/// <param name="className">The name of the class to return.</param>
		/// <param name="lang">The language used to compile the class.</param>
		/// <returns>The compiled class.</returns>
		public static Type ClassFromSourceFile(string filename, string className, NetLanguage lang)
		{
			var reader = new StreamReader(filename);
			string classDef = reader.ReadToEnd();
			reader.Close();

			return ClassFromString(classDef, className, lang);
		}


		/// <summary>
		/// Create a class from a encrypted source file with the default password
		/// </summary>
		/// <param name="filename">The filename with the source of the class.</param>
		/// <returns>The compiled class.</returns>
		public static Type ClassFromBinaryFile(string filename)
		{
			return ClassFromBinaryFile(filename, "", NetLanguage.CSharp);
		}

		/// <summary>
		/// Create a class from a encrypted source file with the default password
		/// </summary>
		/// <param name="filename">The filename with the source of the class.</param>
		/// <param name="lang">The language used to compile the class.</param>
		/// <returns>The compiled class.</returns>
		public static Type ClassFromBinaryFile(string filename, NetLanguage lang)
		{
			return ClassFromBinaryFile(filename, "", lang);
		}

		/// <summary>
		/// Create a class from a encrypted source file with the default password
		/// </summary>
		/// <param name="filename">The filename with the source of the class.</param>
		/// <param name="lang">The language used to compile the class.</param>
		/// <param name="className">The name of the class to return.</param>
		/// <returns>The compiled class.</returns>
		public static Type ClassFromBinaryFile(string filename, string className, NetLanguage lang)
		{
			return ClassFromBinaryFile(filename,
				className,
				lang,
				"withthefilehelpers1.0.0youcancodewithoutproblems1.5.0");
		}

		/// <summary>
		/// Create a class from a encrypted source file with the given password
		/// </summary>
		/// <remarks>Edited by: Shreyas Narasimhan (17 March 2010)</remarks>
		/// <param name="filename">The filename with the source of the class.</param>
		/// <param name="className">The name of the class to return.</param>
		/// <param name="lang">The language used to compile the class.</param>
		/// <param name="password">The password used to decrypt the file</param>
		/// <returns></returns>
		public static Type ClassFromBinaryFile(string filename, string className, NetLanguage lang, string password)
		{
			var reader = new StreamReader(filename);
			string classDef = reader.ReadToEnd();
			reader.Close();

			classDef = Decrypt(classDef, password);

			return ClassFromString(classDef, className, lang);
		}

		/// <summary>
		/// Create a class from a XML file generated with the Wizard or 
		/// saved using the SaveToXml Method.
		/// </summary>
		/// <param name="filename">The filename with the XML definition.</param>
		/// <returns>The compiled class.</returns>
		public static Type ClassFromXmlFile(string filename)
		{
			ClassBuilder cb = LoadFromXml(filename);
			return cb.CreateRecordClass();
		}

		/// <summary>
		/// Encrypt the class source code with the default password and write it to a file.
		/// </summary>
		/// <param name="filename">The file name to write to.</param>
		/// <param name="classSource">The class source  to write to</param>
		public static void ClassToBinaryFile(string filename, string classSource)
		{
			ClassToBinaryFile(filename, classSource, "withthefilehelpers1.0.0youcancodewithoutproblems1.5.0");
		}

		/// <summary>
		/// Encrypt the class source code with the given password and write it to a file.
		/// </summary>
		/// <remarks>Edited by: Shreyas Narasimhan (17 March 2010)</remarks>
		/// <param name="filename">The file name to write to</param>
		/// <param name="classSource">The class source  to write to</param>
		/// <param name="password">The password with which to encrypt the file</param>
		public static void ClassToBinaryFile(string filename, string classSource, string password)
		{
			classSource = Encrypt(classSource, password);
			var writer = new StreamWriter(filename);
			writer.Write(classSource);
			writer.Close();
		}

		#endregion

		/// <summary>
		/// Additional references that are required to compile the type built with this instance.
		/// </summary>
		public List<Assembly> AdditionalReferences { get; private set; }

		#region SaveToFile

		/// <summary>Write the source code of the current class to a file. (In C#)</summary>
		/// <param name="filename">The file to write to.</param>
		public void SaveToSourceFile(string filename)
		{
			SaveToSourceFile(filename, NetLanguage.CSharp);
		}

		/// <summary>Write the source code of the current class to a file. (In the specified language)</summary>
		/// <param name="filename">The file to write to.</param>
		/// <param name="lang">The .NET Language used to write the source code.</param>
		public void SaveToSourceFile(string filename, NetLanguage lang)
		{
			var writer = new StreamWriter(filename);
			writer.Write(GetClassSourceCode(lang));
			writer.Close();
		}

		/// <summary>Write the ENCRYPTED source code of the current class to a file. (In C#)</summary>
		/// <param name="filename">The file to write to.</param>
		public void SaveToBinaryFile(string filename)
		{
			SaveToBinaryFile(filename, NetLanguage.CSharp);
		}

		/// <summary>Write the ENCRYPTED source code of the current class to a file. (In C#)</summary>
		/// <param name="filename">The file to write to.</param>
		/// <param name="lang">The .NET Language used to write the source code.</param>
		public void SaveToBinaryFile(string filename, NetLanguage lang)
		{
			var writer = new StreamWriter(filename);
			writer.Write(GetClassBinaryCode(lang));
			writer.Close();
		}

		#endregion

		/// <summary>
		/// Create a class with 'classname'
		/// </summary>
		/// <param name="className">Name of class to create</param>
		internal ClassBuilder(string className)
		{
			className = className.Trim();
			if (ValidIdentifierValidator.ValidIdentifier(className) == false)
			{
				throw new FileHelpersException(Messages.Errors.InvalidIdentifier
					.Identifier(className)
					.Text);
			}

			mClassName = className;
			AdditionalReferences = new List<Assembly>();
		}

		/// <summary>Generate the runtime record class to be used by the engines.</summary>
		/// <returns>The generated record class</returns>
		public Type CreateRecordClass()
		{
			string classCode = GetClassSourceCode(NetLanguage.CSharp);
			return ClassFromString(classCode, "", NetLanguage.CSharp, AdditionalReferences);
		}


		//--------------
		//->  Fields 

		#region Fields

		/// <summary>Removes all the Fields of the current class.</summary>
		public void ClearFields()
		{
			mFields.Clear();
		}

		/// <summary>
		/// List of all the fields in the class
		/// </summary>
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		internal ArrayList mFields = new ArrayList();

		/// <summary>
		/// Add a field to the class
		/// </summary>
		/// <param name="field">Field details to add</param>
		internal void AddFieldInternal(FieldBuilder field)
		{
			field.mFieldIndex = mFields.Add(field);
			field.mClassBuilder = this;
		}

		/// <summary>Returns the current fields of the class.</summary>
		public FieldBuilder[] Fields
		{
			get { return (FieldBuilder[])mFields.ToArray(typeof(FieldBuilder)); }
		}

		/// <summary>Returns the current number of fields.</summary>
		public int FieldCount
		{
			get { return mFields.Count; }
		}


		/// <summary>Return the field at the specified index.</summary>
		/// <param name="index">The index of the field.</param>
		/// <returns>The field at the specified index.</returns>
		public FieldBuilder FieldByIndex(int index)
		{
			return (FieldBuilder)mFields[index];
		}

		#endregion

		#region ClassName

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private string mClassName;

		/// <summary>Gets or sets the name of the Class.</summary>
		public string ClassName
		{
			get { return mClassName; }
			set { mClassName = value; }
		}

		#endregion

		//----------------------------
		//->  ATTRIBUTE MAPPING

		#region IgnoreFirstLines

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private int mIgnoreFirstLines = 0;

		/// <summary>Indicates the number of FIRST LINES or heading records to be ignored by the engines.</summary>
		public int IgnoreFirstLines
		{
			get { return mIgnoreFirstLines; }
			set { mIgnoreFirstLines = value; }
		}

		#endregion

		#region IgnoreLastLines

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private int mIgnoreLastLines = 0;

		/// <summary>Indicates the number of LAST LINES or trailing records to be ignored by the engines.</summary>
		public int IgnoreLastLines
		{
			get { return mIgnoreLastLines; }
			set { mIgnoreLastLines = value; }
		}

		#endregion

		#region IgnoreEmptyLines

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool mIgnoreEmptyLines = false;

		/// <summary>Indicates that the engines must ignore the empty lines in the files.</summary>
		public bool IgnoreEmptyLines
		{
			get { return mIgnoreEmptyLines; }
			set { mIgnoreEmptyLines = value; }
		}

		#endregion

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool mGenerateProperties = false;

		/// <summary>Indicates if this ClassBuilder generates also the property accessors (Perfect for DataBinding)</summary>
		public bool GenerateProperties
		{
			get { return mGenerateProperties; }
			set { mGenerateProperties = value; }
		}


		/// <summary>
		/// Returns the ENCRYPTED code for the current class in the specified language.
		/// </summary>
		/// <param name="lang">The language for the return code.</param>
		/// <returns>The ENCRYPTED code for the class that are currently building.</returns>
		public string GetClassBinaryCode(NetLanguage lang)
		{
			return Encrypt(GetClassSourceCode(lang), "withthefilehelpers1.0.0youcancodewithoutproblems1.5.0");
		}

		/// <summary>
		/// Returns the source code for the current class in the specified language.
		/// </summary>
		/// <param name="lang">The language for the return code.</param>
		/// <returns>The Source Code for the class that are currently building.</returns>
		public string GetClassSourceCode(NetLanguage lang)
		{
			ValidateClass();

			var sb = new StringBuilder(100);

			BeginNamespace(lang, sb);

			var attbs = new AttributesBuilder(lang);

			AddAttributesInternal(attbs);
			AddAttributesCode(attbs, lang);

			sb.Append(attbs.GetAttributesCode());

			switch (lang)
			{
				case NetLanguage.VbNet:
					sb.Append(GetVisibility(lang, mVisibility) + GetSealed(lang) + "Class " + mClassName);
					sb.Append(StringHelper.NewLine);
					break;
				case NetLanguage.CSharp:
					sb.Append(GetVisibility(lang, mVisibility) + GetSealed(lang) + "class " + mClassName);
					sb.Append(StringHelper.NewLine);
					sb.Append("{");
					break;
			}

			sb.Append(StringHelper.NewLine);
			sb.Append(StringHelper.NewLine);

			foreach (FieldBuilder field in mFields)
			{
				sb.Append(field.GetFieldCode(lang));
				sb.Append(StringHelper.NewLine);
			}


			sb.Append(StringHelper.NewLine);

			switch (lang)
			{
				case NetLanguage.VbNet:
					sb.Append("End Class");
					break;
				case NetLanguage.CSharp:
					sb.Append("}");
					break;
			}

			EndNamespace(lang, sb);

			return sb.ToString();
		}

		private void ValidateClass()
		{
			if (ClassName.Trim().Length == 0)
				throw new FileHelpersException(Messages.Errors.EmptyClassName.Text);

			for (int i = 0; i < mFields.Count; i++)
			{
				if (((FieldBuilder)mFields[i]).FieldName.Trim().Length == 0)
				{
					throw new FileHelpersException(Messages.Errors.EmptyFieldName
						.Position((i + 1).ToString())
						.Text);
				}

				if (((FieldBuilder)mFields[i]).FieldType.Trim().Length == 0)
				{
					throw new FileHelpersException(Messages.Errors.EmptyFieldType
						.Position((i + 1).ToString())
						.Text);
				}
			}
		}

		/// <summary>
		/// Store the attributes for adding when we write the code to text
		/// </summary>
		/// <param name="attbs">Where attributes are stored</param>
		/// <param name="lang"></param>
		internal abstract void AddAttributesCode(AttributesBuilder attbs, NetLanguage lang);

		private void AddAttributesInternal(AttributesBuilder attbs)
		{
			if (mIgnoreFirstLines != 0)
				attbs.AddAttribute("IgnoreFirst(" + mIgnoreFirstLines.ToString() + ")");

			if (mIgnoreLastLines != 0)
				attbs.AddAttribute("IgnoreLast(" + mIgnoreLastLines.ToString() + ")");

			if (mIgnoreEmptyLines)
				attbs.AddAttribute("IgnoreEmptyLines()");

			if (mRecordConditionInfo.Condition != FileHelpers.RecordCondition.None)
			{
				attbs.AddAttribute("ConditionalRecord(RecordCondition." + mRecordConditionInfo.Condition.ToString() +
								   ", \"" + mRecordConditionInfo.Selector + "\")");
			}

			if (!string.IsNullOrEmpty(mIgnoreCommentInfo.CommentMarker))
			{
				attbs.AddAttribute("IgnoreCommentedLines(\"" + mIgnoreCommentInfo.CommentMarker + "\", " +
								   mIgnoreCommentInfo.InAnyPlace.ToString().ToLower() + ")");
			}
		}

		#region "  EncDec  "

		private static byte[] Encrypt(byte[] clearData, byte[] key, byte[] iv)
		{
			var ms = new MemoryStream();
			Rijndael alg = Rijndael.Create();
			alg.Key = key;
			alg.IV = iv;
			var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);
			cs.Write(clearData, 0, clearData.Length);
			cs.Close();
			byte[] encryptedData = ms.ToArray();
			return encryptedData;
		}

		private static string Encrypt(string clearText, string Password)
		{
			byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);

			var pdb = new PasswordDeriveBytes(Password,
				new byte[] {
					0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d,
					0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76
				});
			byte[] encryptedData = Encrypt(clearBytes,
				pdb.GetBytes(32),
				pdb.GetBytes(16));
			return Convert.ToBase64String(encryptedData);
		}


		// Decrypt a byte array into a byte array using a key and an IV 
		private static byte[] Decrypt(byte[] cipherData, byte[] key, byte[] iv)
		{
			var ms = new MemoryStream();
			Rijndael alg = Rijndael.Create();
			alg.Key = key;
			alg.IV = iv;

			var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write);

			cs.Write(cipherData, 0, cipherData.Length);
			cs.Close();

			var decryptedData = ms.ToArray();

			return decryptedData;
		}

		private static string Decrypt(string cipherText, string password)
		{
			var cipherBytes = Convert.FromBase64String(cipherText);
			var pdb = new PasswordDeriveBytes(password,
				new byte[] {
					0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64,
					0x65, 0x76
				});
			var decryptedData = Decrypt(cipherBytes, pdb.GetBytes(32), pdb.GetBytes(16));
			return Encoding.Unicode.GetString(decryptedData);
		}

		#endregion

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private string mCommentText = null;

		/// <summary>Comment text placed above the class definition</summary>
		public string CommentText
		{
			get { return mCommentText; }
			set { mCommentText = value; }
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private NetVisibility mVisibility = NetVisibility.Public;

		/// <summary>The Visibility for the class.</summary>
		public NetVisibility Visibility
		{
			get { return mVisibility; }
			set { mVisibility = value; }
		}


		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool mSealedClass = true;

		/// <summary>Indicates if the generated class must be sealed.</summary>
		public bool SealedClass
		{
			get { return mSealedClass; }
			set { mSealedClass = value; }
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private string mNamespace = "";

		/// <summary>The namespace used when creating the class.</summary>
		public string Namespace
		{
			get { return mNamespace; }
			set { mNamespace = value; }
		}

		/// <summary>
		/// Convert visibility type to word needed by the language
		/// </summary>
		/// <param name="lang">Language C# or Visual Basic</param>
		/// <param name="visibility">Public,  internal, protected, etc</param>
		/// <returns>Visibility correct for the language</returns>
		internal static string GetVisibility(NetLanguage lang, NetVisibility visibility)
		{
			switch (lang)
			{
				case NetLanguage.CSharp:
					switch (visibility)
					{
						case NetVisibility.Public:
							return "public ";
						case NetVisibility.Private:
							return "private ";
						case NetVisibility.Internal:
							return "internal ";
						case NetVisibility.Protected:
							return "protected ";
					}
					break;

				case NetLanguage.VbNet:
					switch (visibility)
					{
						case NetVisibility.Public:
							return "Public ";
						case NetVisibility.Private:
							return "Private ";
						case NetVisibility.Internal:
							return "Friend ";
						case NetVisibility.Protected:
							return "Protected ";
					}
					break;
			}

			return "";
		}

		/// <summary>
		/// Get the sealed prefix,  if needed
		/// </summary>
		/// <param name="lang">Language we are writing in</param>
		/// <returns>nothing, sealed or NotInheritable</returns>
		private string GetSealed(NetLanguage lang)
		{
			if (mSealedClass == false)
				return "";

			switch (lang)
			{
				case NetLanguage.CSharp:
					return "sealed ";

				case NetLanguage.VbNet:
					return "NotInheritable ";
			}

			return "";
		}

		/// <summary>
		/// Add the namespace to the class details
		/// </summary>
		/// <param name="lang">Language type we are generating</param>
		/// <param name="sb">class text</param>
		private void BeginNamespace(NetLanguage lang, StringBuilder sb)
		{
			if (mNamespace == "")
				return;

			switch (lang)
			{
				case NetLanguage.CSharp:
					sb.Append("namespace ");
					sb.Append(mNamespace);
					sb.Append(StringHelper.NewLine);
					sb.Append("{");
					break;

				case NetLanguage.VbNet:
					sb.Append("Namespace ");
					sb.Append(mNamespace);
					sb.Append(StringHelper.NewLine);
					break;
			}

			sb.Append(StringHelper.NewLine);
		}

		/// <summary>
		/// Add the end of namespace to text
		/// </summary>
		/// <param name="lang">language we are generating</param>
		/// <param name="sb">class text to add to</param>
		private void EndNamespace(NetLanguage lang, StringBuilder sb)
		{
			if (mNamespace == "")
				return;

			sb.Append(StringHelper.NewLine);

			switch (lang)
			{
				case NetLanguage.CSharp:
					sb.Append("}");
					break;

				case NetLanguage.VbNet:
					sb.Append("End Namespace");
					break;
			}
		}


		/// <summary>
		/// Loads the XML representation of a ClassBuilder inheritor and return
		/// it. (for XML saved with SaveToXml method)
		/// </summary>
		/// <remarks>
		/// ClassBuilder inheritors: <see cref="DelimitedClassBuilder"/> or <see cref="FixedLengthClassBuilder"/>
		/// </remarks>
		/// <param name="xml">The XML representation of the record class.</param>
		/// <returns>A new instance of a ClassBuilder inheritor: <see cref="DelimitedClassBuilder"/> or <see cref="FixedLengthClassBuilder"/> </returns>
		public static ClassBuilder LoadFromXmlString(string xml)
		{
			var document = new XmlDocument();
			document.Load(new StringReader(xml));

			return LoadFromXml(document);
		}

		/// <summary>
		/// Loads the XML representation of a ClassBuilder inheritor and return
		/// it. (for XML saved with SaveToXml method)
		/// </summary>
		/// <remarks>
		/// ClassBuilder inheritors: <see cref="DelimitedClassBuilder"/> or <see cref="FixedLengthClassBuilder"/>
		/// </remarks>
		/// <param name="document">The XML document with the representation of the record class.</param>
		/// <returns>A new instance of a ClassBuilder inheritor: <see cref="DelimitedClassBuilder"/> or <see cref="FixedLengthClassBuilder"/> </returns>
		public static ClassBuilder LoadFromXml(XmlDocument document)
		{
			ClassBuilder res = null;

			string classtype = document.DocumentElement.LocalName;

			if (classtype == "DelimitedClass")
				res = DelimitedClassBuilder.LoadXmlInternal(document);
			else
				res = FixedLengthClassBuilder.LoadXmlInternal(document);

			XmlNode node = document.DocumentElement["IgnoreLastLines"];
			if (node != null)
				res.IgnoreLastLines = int.Parse(node.InnerText);

			node = document.DocumentElement["IgnoreFirstLines"];
			if (node != null)
				res.IgnoreFirstLines = int.Parse(node.InnerText);

			node = document.DocumentElement["IgnoreEmptyLines"];
			if (node != null)
				res.IgnoreEmptyLines = true;

			node = document.DocumentElement["CommentMarker"];
			if (node != null)
				res.IgnoreCommentedLines.CommentMarker = node.InnerText;

			node = document.DocumentElement["CommentInAnyPlace"];
			if (node != null)
				res.IgnoreCommentedLines.InAnyPlace = bool.Parse(node.InnerText.ToLower());

			node = document.DocumentElement["SealedClass"];
			res.SealedClass = node != null;

			node = document.DocumentElement["Namespace"];
			if (node != null)
				res.Namespace = node.InnerText;

			node = document.DocumentElement["Visibility"];
			if (node != null)
				res.Visibility = (NetVisibility)Enum.Parse(typeof(NetVisibility), node.InnerText);

			node = document.DocumentElement["RecordCondition"];
			if (node != null)
				res.RecordCondition.Condition = (RecordCondition)Enum.Parse(typeof(RecordCondition), node.InnerText);

			node = document.DocumentElement["RecordConditionSelector"];
			if (node != null)
				res.RecordCondition.Selector = node.InnerText;

			node = document.DocumentElement["CommentText"];
			if (node != null)
				res.CommentText = node.InnerText;

			res.ReadClassElements(document);

			node = document.DocumentElement["Fields"];
			XmlNodeList nodes;

			if (classtype == "DelimitedClass")
				nodes = node.SelectNodes("/DelimitedClass/Fields/Field");
			else
				nodes = node.SelectNodes("/FixedLengthClass/Fields/Field");

			foreach (XmlNode n in nodes)
				res.ReadField(n);

			return res;
		}

		/// <summary>
		/// Loads the XML representation of a ClassBuilder inheritor and return
		/// it. (for XML saved with SaveToXml method)
		/// </summary>
		/// <remarks>
		/// ClassBuilder inheritors: <see cref="DelimitedClassBuilder"/> or <see cref="FixedLengthClassBuilder"/>
		/// </remarks>
		/// <param name="filename">A file with the XML representation of the record class.</param>
		/// <returns>A new instance of a ClassBuilder inheritor: <see cref="DelimitedClassBuilder"/> or <see cref="FixedLengthClassBuilder"/> </returns>
		public static ClassBuilder LoadFromXml(string filename)
		{
			var document = new XmlDocument();
			document.Load(filename);

			return LoadFromXml(document);
		}


		/// <summary>
		/// Creates the XML representation of the current record class.
		/// </summary>
		/// <returns>
		/// The representation of the current record class as xml string.
		/// </returns>
		public string SaveToXmlString()
		{
			var sb = new StringBuilder();

			using (var writer = new StringWriter(sb))
				SaveToXml(writer);

			return sb.ToString();
		}


		/// <summary>
		/// Saves to a file the XML representation of the current record class.
		/// </summary>
		/// <param name="filename">A file name to write to.</param>
		public void SaveToXml(string filename)
		{
			using (var stream = new FileStream(filename, FileMode.Create))
				SaveToXml(stream);
		}


		/// <summary>
		/// Saves to an Stream the XML representation of the current record class.
		/// </summary>
		/// <param name="stream">Stream to be written.</param>
		public void SaveToXml(Stream stream)
		{
			using (TextWriter writer = new StreamWriter(stream))
				SaveToXml(writer);
		}

		/// <summary>
		/// Save to a TextWriter the XML representation of the current record class.
		/// </summary>
		/// <param name="writer">The TextWriter for the output Stream.</param>
		public void SaveToXml(TextWriter writer)
		{
			var xml = new XmlHelper();

			xml.BeginWriteStream(writer);

			WriteHeaderElement(xml);

			xml.WriteElement("ClassName", ClassName);
			xml.WriteElement("Namespace", Namespace, "");

			xml.WriteElement("SealedClass", SealedClass);
			xml.WriteElement("Visibility", Visibility.ToString(), "Public");

			xml.WriteElement("IgnoreEmptyLines", IgnoreEmptyLines);
			xml.WriteElement("IgnoreFirstLines", IgnoreFirstLines.ToString(), "0");
			xml.WriteElement("IgnoreLastLines", IgnoreLastLines.ToString(), "0");

			xml.WriteElement("CommentMarker", IgnoreCommentedLines.CommentMarker, "");
			xml.WriteElement("CommentInAnyPlace",
				IgnoreCommentedLines.InAnyPlace.ToString().ToLower(),
				true.ToString().ToLower());

			xml.WriteElement("RecordCondition", RecordCondition.Condition.ToString(), "None");
			xml.WriteElement("RecordConditionSelector", RecordCondition.Selector, "");

			WriteExtraElements(xml);

			xml.Writer.WriteStartElement("Fields");

			for (int i = 0; i < mFields.Count; i++)
				((FieldBuilder)mFields[i]).SaveToXml(xml);

			xml.Writer.WriteEndElement();

			xml.Writer.WriteEndElement();
			xml.EndWrite();
		}

		/// <summary>
		/// write header information for this class
		/// </summary>
		/// <param name="writer">writer to write on</param>
		internal abstract void WriteHeaderElement(XmlHelper writer);

		/// <summary>
		/// write any extra information for the class
		/// </summary>
		/// <param name="writer">writer to put xml on</param>
		internal abstract void WriteExtraElements(XmlHelper writer);

		/// <summary>
		/// read class elements from xml document
		/// </summary>
		/// <param name="document">document to read from</param>
		internal abstract void ReadClassElements(XmlDocument document);

		/// <summary>
		/// read attribute information for class
		/// </summary>
		/// <param name="node"></param>
		internal abstract void ReadField(XmlNode node);

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private readonly RecordConditionInfo mRecordConditionInfo = new RecordConditionInfo();

		/// <summary>Allow to tell the engine what records must be included or excluded while reading.</summary>
		public RecordConditionInfo RecordCondition
		{
			get { return mRecordConditionInfo; }
		}


		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private readonly IgnoreCommentInfo mIgnoreCommentInfo = new IgnoreCommentInfo();

		/// <summary>Indicates that the engine must ignore the lines with this comment marker.</summary>
		public IgnoreCommentInfo IgnoreCommentedLines
		{
			get { return mIgnoreCommentInfo; }
		}


		/// <summary>Allow to tell the engine what records must be included or excluded while reading.</summary>
		[EditorBrowsable(EditorBrowsableState.Advanced)]
		public sealed class RecordConditionInfo
		{
			internal RecordConditionInfo() { }

			private RecordCondition mRecordCondition = FileHelpers.RecordCondition.None;

			/// <summary>Allow to tell the engine what records must be included or excluded while reading.</summary>
			public RecordCondition Condition
			{
				get { return mRecordCondition; }
				set { mRecordCondition = value; }
			}

			private string mRecordConditionSelector = "";

			/// <summary>The selector used by the <see cref="RecordCondition"/>.</summary>
			public string Selector
			{
				get { return mRecordConditionSelector; }
				set { mRecordConditionSelector = value; }
			}
		}

		/// <summary>Indicates that the engine must ignore the lines with this comment marker.</summary>
		[EditorBrowsable(EditorBrowsableState.Advanced)]
		public sealed class IgnoreCommentInfo
		{
			internal IgnoreCommentInfo() { }

			/// <summary>
			/// <para>Indicates that the engine must ignore the lines with this
			/// comment marker.</para>
			/// <para>An empty string or null indicates that the engine does
			/// not look for comments</para>
			/// </summary>
			public string CommentMarker
			{
				get { return mMarker; }
				set
				{
					if (value != null)
						value = value.Trim();

					mMarker = value;
				}
			}

			private string mMarker = "";

			/// <summary>Indicates if the comment can have spaces or tabs at left (true by default)</summary>
			public bool InAnyPlace
			{
				get { return mInAnyPlace; }
				set { mInAnyPlace = value; }
			}

			private bool mInAnyPlace = true;
		}

		/// <summary>
		/// Break down a class into a generic or a full type name
		/// </summary>
		/// <param name="type">type to get type of</param>
		/// <returns>Type of the class as a string</returns>
		internal static string TypeToString(Type type)
		{
			if (type.IsGenericType)
			{
				var sb = new StringBuilder();
				sb.Append(type.FullName.Substring(0, type.FullName.IndexOf("`", StringComparison.Ordinal)));
				sb.Append("<");

				Type[] args = type.GetGenericArguments();

				for (int i = 0; i < args.Length; i++)
				{
					if (i > 0)
						sb.Append(",");

					sb.Append(TypeToString(args[i]));
				}
				sb.Append(">");

				return sb.ToString();
			}
			else
				return type.FullName;
		}
	}
#else
	using System.Linq;
	using System.Runtime.Loader;
	using Microsoft.CodeAnalysis;
	using Microsoft.CodeAnalysis.CSharp;
	using Microsoft.CodeAnalysis.VisualBasic;
	using Microsoft.Extensions.DependencyModel;

	/// <summary>The MAIN class to work with runtime defined records.</summary>
	public abstract class ClassBuilder
    {
        //---------------------
        //->  STATIC METHODS

	#region LoadFromString

        /// <summary>Compiles the source code passed and returns the FIRST Type of the assembly. (Code in C#)</summary>
        /// <param name="classStr">The Source Code of the class in C#</param>
        /// <returns>The Type generated by runtime compilation of the class source.</returns>
        public static Type ClassFromString(string classStr)
        {
            return ClassFromString(classStr, "");
        }

        /// <summary>Compiles the source code passed and returns the FIRST Type of the assembly.</summary>
        /// <param name="classStr">The Source Code of the class in the specified language</param>
        /// <returns>The Type generated by runtime compilation of the class source.</returns>
        /// <param name="lang">One of the .NET Languages</param>
        public static Type ClassFromString(string classStr, NetLanguage lang)
        {
            return ClassFromString(classStr, "", lang);
        }

        /// <summary>Compiles the source code passed and returns the Type with the name className. (Code in C#)</summary>
        /// <param name="classStr">The Source Code of the class in C#</param>
        /// <param name="className">The Name of the Type that must be returned</param>
        /// <returns>The Type generated by runtime compilation of the class source.</returns>
        public static Type ClassFromString(string classStr, string className)
        {
            return ClassFromString(classStr, className, NetLanguage.CSharp);
        }

        private static List<string> mReferences;
        private static readonly object mReferencesLock = new object();

        /// <summary>Compiles the source code passed and returns the Type with the name className.</summary>
        /// <param name="classStr">The Source Code of the class in the specified language</param>
        /// <param name="className">The Name of the Type that must be returned</param>
        /// <param name="lang">One of the .NET Languages</param>
        /// <param name="additionalReferences">List of assemblies to be added in the dynamic compilation </param>
        /// <returns>The Type generated by runtime compilation of the class source.</returns>
        public static Type ClassFromString(string classStr, string className, NetLanguage lang, List<Assembly> additionalReferences = null)
        {
            if (classStr.Length < 4) {
                throw new BadUsageException(
                    "There is not enough text to be a proper class, load your class and try again");
            }

			var code = new StringBuilder();

            switch (lang) {
                case NetLanguage.CSharp:
                    code.Append("using System; using FileHelpers; using System.Collections; using System.Collections.Generic; using System.Data");
                    break;

                case NetLanguage.VbNet:

                    if (
                        CultureInfo.CurrentCulture.CompareInfo.IndexOf(classStr,
                            "Imports System",
                            CompareOptions.IgnoreCase) == -1)
                        code.Append("Imports System\n");

                    if (
                        CultureInfo.CurrentCulture.CompareInfo.IndexOf(classStr,
                            "Imports FileHelpers",
                            CompareOptions.IgnoreCase) == -1)
                        code.Append("Imports FileHelpers\n");

                    if (CultureInfo.CurrentCulture.CompareInfo.IndexOf(classStr,
                            "Imports System.Data",
                            CompareOptions.IgnoreCase) == -1)
                        code.Append("Imports System.Data\n");

                    break;
            }

            code.Append(classStr);

			var compileTimeReferences = from library in DependencyContext.Default.CompileLibraries
		        from path in library.ResolveReferencePaths()
		        select AssemblyMetadata.CreateFromFile(path)
		        into metadata
		        select metadata.GetReference();

	        // Assumption: there is no need to distinguish System.Data and System.Xml in .NET Core,
	        //             as they are all part of the same pile. Instead we'll rely on the existing compile-time
	        //             references, and we'll only add FileHelpers itself to dynamic compilation

			var references = new HashSet<MetadataReference>(compileTimeReferences)
	        {
		        MetadataReference.CreateFromFile(typeof(ClassBuilder).Assembly.Location)	// FileHelpers.dll
	        };

	        foreach (var additionalReference in additionalReferences ?? Enumerable.Empty<Assembly>())
	        {
				var reference = MetadataReference.CreateFromFile(additionalReference.Location);
		        if (reference != null)
			        references.Add(reference);
			}

			SyntaxTree syntaxTree;
	        Compilation compilation;

			switch (lang)
	        {
		        case NetLanguage.CSharp:
			        
			        syntaxTree = CSharpSyntaxTree.ParseText(code.ToString());
			        compilation = CSharpCompilation.Create(
				        $"{Guid.NewGuid()}.dll",
				        new[] { syntaxTree },
				        references,
				        new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

					break;
		        case NetLanguage.VbNet:
			        syntaxTree = VisualBasicSyntaxTree.ParseText(code.ToString());
			        compilation = VisualBasicCompilation.Create(
				        $"{Guid.NewGuid()}.dll",
				        new[] { syntaxTree },
				        references,
				        new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
					break;
				default:
					throw new NotSupportedException();
	        }

			Assembly assembly;

			using (var peStream = new MemoryStream())
			{
				using (var pdbStream = new MemoryStream())
				{
					var result = compilation.Emit(peStream, pdbStream);
					var errors = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error).ToList();

					if (errors.Any())
					{
						var error = new StringBuilder();
						error.Append("Error Compiling Expression: " + StringHelper.NewLine);

						foreach (Diagnostic err in errors)
							error.AppendFormat("Line {0}: {1}\n", err.Location.GetLineSpan().StartLinePosition, err.GetMessage());
						throw new DynamicCompilationException(error.ToString(), classStr, errors);
					}

					peStream.Seek(0, SeekOrigin.Begin);

					assembly = AssemblyLoadContext.Default.LoadFromStream(peStream, pdbStream);
				}
			}
			
            if (className != "")
                return assembly.GetType(className, true, true);
            else {
                Type[] ts = assembly.GetTypes();
                if (ts.Length > 0) {
                    foreach (var t in ts) {
                        if (t.FullName.StartsWith("My.My") == false &&
                            t.IsDefined(typeof (TypedRecordAttribute), false))
                            return t;
                    }
                }

                throw new BadUsageException("The compiled assembly does not have any type inside.");
            }
        }

	#endregion

        //public bool AddCurrentDomainReferences { get; set; }

	#region CreateFromFile

        /// <summary>
        /// Create a class from a source file.
        /// </summary>
        /// <param name="filename">The filename with the source of the class.</param>
        /// <returns>The compiled class.</returns>
        public static Type ClassFromSourceFile(string filename)
        {
            return ClassFromSourceFile(filename, "");
        }

        /// <summary>
        /// Create a class from a source file.
        /// </summary>
        /// <param name="filename">The filename with the source of the class.</param>
        /// <param name="lang">The language used to compile the class.</param>
        /// <returns>The compiled class.</returns>
        public static Type ClassFromSourceFile(string filename, NetLanguage lang)
        {
            return ClassFromSourceFile(filename, "", lang);
        }

        /// <summary>
        /// Create a class from a source file.
        /// </summary>
        /// <param name="filename">The filename with the source of the class.</param>
        /// <param name="className">The name of the class to return.</param>
        /// <returns>The compiled class.</returns>
        public static Type ClassFromSourceFile(string filename, string className)
        {
            return ClassFromSourceFile(filename, className, NetLanguage.CSharp);
        }

        /// <summary>
        /// Create a class from a source file.
        /// </summary>
        /// <param name="filename">The filename with the source of the class.</param>
        /// <param name="className">The name of the class to return.</param>
        /// <param name="lang">The language used to compile the class.</param>
        /// <returns>The compiled class.</returns>
        public static Type ClassFromSourceFile(string filename, string className, NetLanguage lang)
        {
            var reader = new StreamReader(filename);
            string classDef = reader.ReadToEnd();
            reader.Close();

            return ClassFromString(classDef, className, lang);
        }


        /// <summary>
        /// Create a class from a encrypted source file with the default password
        /// </summary>
        /// <param name="filename">The filename with the source of the class.</param>
        /// <returns>The compiled class.</returns>
        public static Type ClassFromBinaryFile(string filename)
        {
            return ClassFromBinaryFile(filename, "", NetLanguage.CSharp);
        }

        /// <summary>
        /// Create a class from a encrypted source file with the default password
        /// </summary>
        /// <param name="filename">The filename with the source of the class.</param>
        /// <param name="lang">The language used to compile the class.</param>
        /// <returns>The compiled class.</returns>
        public static Type ClassFromBinaryFile(string filename, NetLanguage lang)
        {
            return ClassFromBinaryFile(filename, "", lang);
        }

        /// <summary>
        /// Create a class from a encrypted source file with the default password
        /// </summary>
        /// <param name="filename">The filename with the source of the class.</param>
        /// <param name="lang">The language used to compile the class.</param>
        /// <param name="className">The name of the class to return.</param>
        /// <returns>The compiled class.</returns>
        public static Type ClassFromBinaryFile(string filename, string className, NetLanguage lang)
        {
            return ClassFromBinaryFile(filename,
                className,
                lang,
                "withthefilehelpers1.0.0youcancodewithoutproblems1.5.0");
        }

        /// <summary>
        /// Create a class from a encrypted source file with the given password
        /// </summary>
        /// <remarks>Edited by: Shreyas Narasimhan (17 March 2010)</remarks>
        /// <param name="filename">The filename with the source of the class.</param>
        /// <param name="className">The name of the class to return.</param>
        /// <param name="lang">The language used to compile the class.</param>
        /// <param name="password">The password used to decrypt the file</param>
        /// <returns></returns>
        public static Type ClassFromBinaryFile(string filename, string className, NetLanguage lang, string password)
        {
            var reader = new StreamReader(filename);
            string classDef = reader.ReadToEnd();
            reader.Close();

            classDef = Decrypt(classDef, password);

            return ClassFromString(classDef, className, lang);
        }

        /// <summary>
        /// Create a class from a XML file generated with the Wizard or 
        /// saved using the SaveToXml Method.
        /// </summary>
        /// <param name="filename">The filename with the XML definition.</param>
        /// <returns>The compiled class.</returns>
        public static Type ClassFromXmlFile(string filename)
        {
            ClassBuilder cb = LoadFromXml(filename);
            return cb.CreateRecordClass();
        }

        /// <summary>
        /// Encrypt the class source code with the default password and write it to a file.
        /// </summary>
        /// <param name="filename">The file name to write to.</param>
        /// <param name="classSource">The class source  to write to</param>
        public static void ClassToBinaryFile(string filename, string classSource)
        {
            ClassToBinaryFile(filename, classSource, "withthefilehelpers1.0.0youcancodewithoutproblems1.5.0");
        }

        /// <summary>
        /// Encrypt the class source code with the given password and write it to a file.
        /// </summary>
        /// <remarks>Edited by: Shreyas Narasimhan (17 March 2010)</remarks>
        /// <param name="filename">The file name to write to</param>
        /// <param name="classSource">The class source  to write to</param>
        /// <param name="password">The password with which to encrypt the file</param>
        public static void ClassToBinaryFile(string filename, string classSource, string password)
        {
            classSource = Encrypt(classSource, password);
            var writer = new StreamWriter(filename);
            writer.Write(classSource);
            writer.Close();
        }

	#endregion

        public List<Assembly> AdditionalReferences { get; private set; }

	#region SaveToFile

        /// <summary>Write the source code of the current class to a file. (In C#)</summary>
        /// <param name="filename">The file to write to.</param>
        public void SaveToSourceFile(string filename)
        {
            SaveToSourceFile(filename, NetLanguage.CSharp);
        }

        /// <summary>Write the source code of the current class to a file. (In the specified language)</summary>
        /// <param name="filename">The file to write to.</param>
        /// <param name="lang">The .NET Language used to write the source code.</param>
        public void SaveToSourceFile(string filename, NetLanguage lang)
        {
            var writer = new StreamWriter(filename);
            writer.Write(GetClassSourceCode(lang));
            writer.Close();
        }

        /// <summary>Write the ENCRYPTED source code of the current class to a file. (In C#)</summary>
        /// <param name="filename">The file to write to.</param>
        public void SaveToBinaryFile(string filename)
        {
            SaveToBinaryFile(filename, NetLanguage.CSharp);
        }

        /// <summary>Write the ENCRYPTED source code of the current class to a file. (In C#)</summary>
        /// <param name="filename">The file to write to.</param>
        /// <param name="lang">The .NET Language used to write the source code.</param>
        public void SaveToBinaryFile(string filename, NetLanguage lang)
        {
            var writer = new StreamWriter(filename);
            writer.Write(GetClassBinaryCode(lang));
            writer.Close();
        }

	#endregion

        /// <summary>
        /// Create a class with 'classname'
        /// </summary>
        /// <param name="className">Name of class to create</param>
        internal ClassBuilder(string className)
        {
            className = className.Trim();
            if (ValidIdentifierValidator.ValidIdentifier(className) == false) {
                throw new FileHelpersException(Messages.Errors.InvalidIdentifier
                    .Identifier(className)
                    .Text);
            }

            mClassName = className;
            AdditionalReferences = new List<Assembly>();
        }

        /// <summary>Generate the runtime record class to be used by the engines.</summary>
        /// <returns>The generated record class</returns>
        public Type CreateRecordClass()
        {
            string classCode = GetClassSourceCode(NetLanguage.CSharp);
            return ClassFromString(classCode, "", NetLanguage.CSharp, AdditionalReferences);
        }


        //--------------
        //->  Fields 

	#region Fields

        /// <summary>Removes all the Fields of the current class.</summary>
        public void ClearFields()
        {
            mFields.Clear();
        }

        /// <summary>
        /// List of all the fields in the class
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        internal ArrayList mFields = new ArrayList();

        /// <summary>
        /// Add a field to the class
        /// </summary>
        /// <param name="field">Field details to add</param>
        internal void AddFieldInternal(FieldBuilder field)
        {
            field.mFieldIndex = mFields.Add(field);
            field.mClassBuilder = this;
        }

        /// <summary>Returns the current fields of the class.</summary>
        public FieldBuilder[] Fields
        {
            get { return (FieldBuilder[]) mFields.ToArray(typeof (FieldBuilder)); }
        }

        /// <summary>Returns the current number of fields.</summary>
        public int FieldCount
        {
            get { return mFields.Count; }
        }


        /// <summary>Return the field at the specified index.</summary>
        /// <param name="index">The index of the field.</param>
        /// <returns>The field at the specified index.</returns>
        public FieldBuilder FieldByIndex(int index)
        {
            return (FieldBuilder) mFields[index];
        }

	#endregion

	#region ClassName

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private string mClassName;

        /// <summary>Gets or sets the name of the Class.</summary>
        public string ClassName
        {
            get { return mClassName; }
            set { mClassName = value; }
        }

	#endregion

        //----------------------------
        //->  ATTRIBUTE MAPPING

	#region IgnoreFirstLines

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private int mIgnoreFirstLines = 0;

        /// <summary>Indicates the number of FIRST LINES or heading records to be ignored by the engines.</summary>
        public int IgnoreFirstLines
        {
            get { return mIgnoreFirstLines; }
            set { mIgnoreFirstLines = value; }
        }

	#endregion

	#region IgnoreLastLines

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private int mIgnoreLastLines = 0;

        /// <summary>Indicates the number of LAST LINES or trailing records to be ignored by the engines.</summary>
        public int IgnoreLastLines
        {
            get { return mIgnoreLastLines; }
            set { mIgnoreLastLines = value; }
        }

	#endregion

	#region IgnoreEmptyLines

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private bool mIgnoreEmptyLines = false;

        /// <summary>Indicates that the engines must ignore the empty lines in the files.</summary>
        public bool IgnoreEmptyLines
        {
            get { return mIgnoreEmptyLines; }
            set { mIgnoreEmptyLines = value; }
        }

	#endregion

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private bool mGenerateProperties = false;

        /// <summary>Indicates if this ClassBuilder generates also the property accessors (Perfect for DataBinding)</summary>
        public bool GenerateProperties
        {
            get { return mGenerateProperties; }
            set { mGenerateProperties = value; }
        }


        /// <summary>
        /// Returns the ENCRYPTED code for the current class in the specified language.
        /// </summary>
        /// <param name="lang">The language for the return code.</param>
        /// <returns>The ENCRYPTED code for the class that are currently building.</returns>
        public string GetClassBinaryCode(NetLanguage lang)
        {
            return Encrypt(GetClassSourceCode(lang), "withthefilehelpers1.0.0youcancodewithoutproblems1.5.0");
        }

        /// <summary>
        /// Returns the source code for the current class in the specified language.
        /// </summary>
        /// <param name="lang">The language for the return code.</param>
        /// <returns>The Source Code for the class that are currently building.</returns>
        public string GetClassSourceCode(NetLanguage lang)
        {
            ValidateClass();

            var sb = new StringBuilder(100);

            BeginNamespace(lang, sb);

            var attbs = new AttributesBuilder(lang);

            AddAttributesInternal(attbs);
            AddAttributesCode(attbs, lang);

            sb.Append(attbs.GetAttributesCode());

            switch (lang) {
                case NetLanguage.VbNet:
                    sb.Append(GetVisibility(lang, mVisibility) + GetSealed(lang) + "Class " + mClassName);
                    sb.Append(StringHelper.NewLine);
                    break;
                case NetLanguage.CSharp:
                    sb.Append(GetVisibility(lang, mVisibility) + GetSealed(lang) + "class " + mClassName);
                    sb.Append(StringHelper.NewLine);
                    sb.Append("{");
                    break;
            }

            sb.Append(StringHelper.NewLine);
            sb.Append(StringHelper.NewLine);

            foreach (FieldBuilder field in mFields) {
                sb.Append(field.GetFieldCode(lang));
                sb.Append(StringHelper.NewLine);
            }


            sb.Append(StringHelper.NewLine);

            switch (lang) {
                case NetLanguage.VbNet:
                    sb.Append("End Class");
                    break;
                case NetLanguage.CSharp:
                    sb.Append("}");
                    break;
            }

            EndNamespace(lang, sb);

            return sb.ToString();
        }

        private void ValidateClass()
        {
            if (ClassName.Trim().Length == 0)
                throw new FileHelpersException(Messages.Errors.EmptyClassName.Text);

            for (int i = 0; i < mFields.Count; i++) {
                if (((FieldBuilder) mFields[i]).FieldName.Trim().Length == 0) {
                    throw new FileHelpersException(Messages.Errors.EmptyFieldName
                        .Position((i + 1).ToString())
                        .Text);
                }

                if (((FieldBuilder) mFields[i]).FieldType.Trim().Length == 0) {
                    throw new FileHelpersException(Messages.Errors.EmptyFieldType
                        .Position((i + 1).ToString())
                        .Text);
                }
            }
        }

        /// <summary>
        /// Store the attributes for adding when we write the code to text
        /// </summary>
        /// <param name="attbs">Where attributes are stored</param>
        /// <param name="lang"></param>
        internal abstract void AddAttributesCode(AttributesBuilder attbs, NetLanguage lang);

        private void AddAttributesInternal(AttributesBuilder attbs)
        {
            if (mIgnoreFirstLines != 0)
                attbs.AddAttribute("IgnoreFirst(" + mIgnoreFirstLines.ToString() + ")");

            if (mIgnoreLastLines != 0)
                attbs.AddAttribute("IgnoreLast(" + mIgnoreLastLines.ToString() + ")");

            if (mIgnoreEmptyLines)
                attbs.AddAttribute("IgnoreEmptyLines()");

            if (mRecordConditionInfo.Condition != FileHelpers.RecordCondition.None) {
                attbs.AddAttribute("ConditionalRecord(RecordCondition." + mRecordConditionInfo.Condition.ToString() +
                                   ", \"" + mRecordConditionInfo.Selector + "\")");
            }

            if (!string.IsNullOrEmpty(mIgnoreCommentInfo.CommentMarker)) {
                attbs.AddAttribute("IgnoreCommentedLines(\"" + mIgnoreCommentInfo.CommentMarker + "\", " +
                                   mIgnoreCommentInfo.InAnyPlace.ToString().ToLower() + ")");
            }
        }

	#region "  EncDec  "

        private static byte[] Encrypt(byte[] clearData, byte[] key, byte[] iv)
        {
            var ms = new MemoryStream();
            Rijndael alg = Rijndael.Create();
            alg.Key = key;
            alg.IV = iv;
            var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);
            cs.Write(clearData, 0, clearData.Length);
            cs.Close();
            byte[] encryptedData = ms.ToArray();
            return encryptedData;
        }

        private static string Encrypt(string clearText, string Password)
        {
            byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);

            var pdb = new PasswordDeriveBytes(Password,
                new byte[] {
                    0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d,
                    0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76
                });
            byte[] encryptedData = Encrypt(clearBytes,
                pdb.GetBytes(32),
                pdb.GetBytes(16));
            return Convert.ToBase64String(encryptedData);
        }


        // Decrypt a byte array into a byte array using a key and an IV 
        private static byte[] Decrypt(byte[] cipherData, byte[] key, byte[] iv)
        {
            var ms = new MemoryStream();
            Rijndael alg = Rijndael.Create();
            alg.Key = key;
            alg.IV = iv;

            var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write);

            cs.Write(cipherData, 0, cipherData.Length);
            cs.Close();

            var decryptedData = ms.ToArray();

            return decryptedData;
        }

        private static string Decrypt(string cipherText, string password)
        {
            var cipherBytes = Convert.FromBase64String(cipherText);
            var pdb = new PasswordDeriveBytes(password,
                new byte[] {
                    0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64,
                    0x65, 0x76
                });
            var decryptedData = Decrypt(cipherBytes, pdb.GetBytes(32), pdb.GetBytes(16));
            return Encoding.Unicode.GetString(decryptedData);
        }

	#endregion

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private string mCommentText = null;

        /// <summary>Comment text placed above the class definition</summary>
        public string CommentText
        {
            get { return mCommentText; }
            set { mCommentText = value; }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private NetVisibility mVisibility = NetVisibility.Public;

        /// <summary>The Visibility for the class.</summary>
        public NetVisibility Visibility
        {
            get { return mVisibility; }
            set { mVisibility = value; }
        }


        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private bool mSealedClass = true;

        /// <summary>Indicates if the generated class must be sealed.</summary>
        public bool SealedClass
        {
            get { return mSealedClass; }
            set { mSealedClass = value; }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private string mNamespace = "";

        /// <summary>The namespace used when creating the class.</summary>
        public string Namespace
        {
            get { return mNamespace; }
            set { mNamespace = value; }
        }

        /// <summary>
        /// Convert visibility type to word needed by the language
        /// </summary>
        /// <param name="lang">Language C# or Visual Basic</param>
        /// <param name="visibility">Public,  internal, protected, etc</param>
        /// <returns>Visibility correct for the language</returns>
        internal static string GetVisibility(NetLanguage lang, NetVisibility visibility)
        {
            switch (lang) {
                case NetLanguage.CSharp:
                    switch (visibility) {
                        case NetVisibility.Public:
                            return "public ";
                        case NetVisibility.Private:
                            return "private ";
                        case NetVisibility.Internal:
                            return "internal ";
                        case NetVisibility.Protected:
                            return "protected ";
                    }
                    break;

                case NetLanguage.VbNet:
                    switch (visibility) {
                        case NetVisibility.Public:
                            return "Public ";
                        case NetVisibility.Private:
                            return "Private ";
                        case NetVisibility.Internal:
                            return "Friend ";
                        case NetVisibility.Protected:
                            return "Protected ";
                    }
                    break;
            }

            return "";
        }

        /// <summary>
        /// Get the sealed prefix,  if needed
        /// </summary>
        /// <param name="lang">Language we are writing in</param>
        /// <returns>nothing, sealed or NotInheritable</returns>
        private string GetSealed(NetLanguage lang)
        {
            if (mSealedClass == false)
                return "";

            switch (lang) {
                case NetLanguage.CSharp:
                    return "sealed ";

                case NetLanguage.VbNet:
                    return "NotInheritable ";
            }

            return "";
        }

        /// <summary>
        /// Add the namespace to the class details
        /// </summary>
        /// <param name="lang">Language type we are generating</param>
        /// <param name="sb">class text</param>
        private void BeginNamespace(NetLanguage lang, StringBuilder sb)
        {
            if (mNamespace == "")
                return;

            switch (lang) {
                case NetLanguage.CSharp:
                    sb.Append("namespace ");
                    sb.Append(mNamespace);
                    sb.Append(StringHelper.NewLine);
                    sb.Append("{");
                    break;

                case NetLanguage.VbNet:
                    sb.Append("Namespace ");
                    sb.Append(mNamespace);
                    sb.Append(StringHelper.NewLine);
                    break;
            }

            sb.Append(StringHelper.NewLine);
        }

        /// <summary>
        /// Add the end of namespace to text
        /// </summary>
        /// <param name="lang">language we are generating</param>
        /// <param name="sb">class text to add to</param>
        private void EndNamespace(NetLanguage lang, StringBuilder sb)
        {
            if (mNamespace == "")
                return;

            sb.Append(StringHelper.NewLine);

            switch (lang) {
                case NetLanguage.CSharp:
                    sb.Append("}");
                    break;

                case NetLanguage.VbNet:
                    sb.Append("End Namespace");
                    break;
            }
        }


        /// <summary>
        /// Loads the XML representation of a ClassBuilder inheritor and return
        /// it. (for XML saved with SaveToXml method)
        /// </summary>
        /// <remarks>
        /// ClassBuilder inheritors: <see cref="DelimitedClassBuilder"/> or <see cref="FixedLengthClassBuilder"/>
        /// </remarks>
        /// <param name="xml">The XML representation of the record class.</param>
        /// <returns>A new instance of a ClassBuilder inheritor: <see cref="DelimitedClassBuilder"/> or <see cref="FixedLengthClassBuilder"/> </returns>
        public static ClassBuilder LoadFromXmlString(string xml)
        {
            var document = new XmlDocument();
            document.Load(new StringReader(xml));

            return LoadFromXml(document);
        }

        /// <summary>
        /// Loads the XML representation of a ClassBuilder inheritor and return
        /// it. (for XML saved with SaveToXml method)
        /// </summary>
        /// <remarks>
        /// ClassBuilder inheritors: <see cref="DelimitedClassBuilder"/> or <see cref="FixedLengthClassBuilder"/>
        /// </remarks>
        /// <param name="document">The XML document with the representation of the record class.</param>
        /// <returns>A new instance of a ClassBuilder inheritor: <see cref="DelimitedClassBuilder"/> or <see cref="FixedLengthClassBuilder"/> </returns>
        public static ClassBuilder LoadFromXml(XmlDocument document)
        {
            ClassBuilder res = null;

            string classtype = document.DocumentElement.LocalName;

            if (classtype == "DelimitedClass")
                res = DelimitedClassBuilder.LoadXmlInternal(document);
            else
                res = FixedLengthClassBuilder.LoadXmlInternal(document);

            XmlNode node = document.DocumentElement["IgnoreLastLines"];
            if (node != null)
                res.IgnoreLastLines = int.Parse(node.InnerText);

            node = document.DocumentElement["IgnoreFirstLines"];
            if (node != null)
                res.IgnoreFirstLines = int.Parse(node.InnerText);

            node = document.DocumentElement["IgnoreEmptyLines"];
            if (node != null)
                res.IgnoreEmptyLines = true;

            node = document.DocumentElement["CommentMarker"];
            if (node != null)
                res.IgnoreCommentedLines.CommentMarker = node.InnerText;

            node = document.DocumentElement["CommentInAnyPlace"];
            if (node != null)
                res.IgnoreCommentedLines.InAnyPlace = bool.Parse(node.InnerText.ToLower());

            node = document.DocumentElement["SealedClass"];
            res.SealedClass = node != null;

            node = document.DocumentElement["Namespace"];
            if (node != null)
                res.Namespace = node.InnerText;

            node = document.DocumentElement["Visibility"];
            if (node != null)
                res.Visibility = (NetVisibility) Enum.Parse(typeof (NetVisibility), node.InnerText);

            node = document.DocumentElement["RecordCondition"];
            if (node != null)
                res.RecordCondition.Condition = (RecordCondition) Enum.Parse(typeof (RecordCondition), node.InnerText);

            node = document.DocumentElement["RecordConditionSelector"];
            if (node != null)
                res.RecordCondition.Selector = node.InnerText;

            node = document.DocumentElement["CommentText"];
            if (node != null)
                res.CommentText = node.InnerText;

            res.ReadClassElements(document);

            node = document.DocumentElement["Fields"];
            XmlNodeList nodes;

            if (classtype == "DelimitedClass")
                nodes = node.SelectNodes("/DelimitedClass/Fields/Field");
            else
                nodes = node.SelectNodes("/FixedLengthClass/Fields/Field");

            foreach (XmlNode n in nodes)
                res.ReadField(n);

            return res;
        }

        /// <summary>
        /// Loads the XML representation of a ClassBuilder inheritor and return
        /// it. (for XML saved with SaveToXml method)
        /// </summary>
        /// <remarks>
        /// ClassBuilder inheritors: <see cref="DelimitedClassBuilder"/> or <see cref="FixedLengthClassBuilder"/>
        /// </remarks>
        /// <param name="filename">A file with the XML representation of the record class.</param>
        /// <returns>A new instance of a ClassBuilder inheritor: <see cref="DelimitedClassBuilder"/> or <see cref="FixedLengthClassBuilder"/> </returns>
        public static ClassBuilder LoadFromXml(string filename)
        {
            var document = new XmlDocument();
            document.Load(filename);

            return LoadFromXml(document);
        }


        /// <summary>
        /// Creates the XML representation of the current record class.
        /// </summary>
        /// <returns>
        /// The representation of the current record class as xml string.
        /// </returns>
        public string SaveToXmlString()
        {
            var sb = new StringBuilder();

            using (var writer = new StringWriter(sb))
                SaveToXml(writer);

            return sb.ToString();
        }


        /// <summary>
        /// Saves to a file the XML representation of the current record class.
        /// </summary>
        /// <param name="filename">A file name to write to.</param>
        public void SaveToXml(string filename)
        {
            using (var stream = new FileStream(filename, FileMode.Create))
                SaveToXml(stream);
        }


        /// <summary>
        /// Saves to an Stream the XML representation of the current record class.
        /// </summary>
        /// <param name="stream">Stream to be written.</param>
        public void SaveToXml(Stream stream)
        {
            using (TextWriter writer = new StreamWriter(stream))
                SaveToXml(writer);
        }

        /// <summary>
        /// Save to a TextWriter the XML representation of the current record class.
        /// </summary>
        /// <param name="writer">The TextWriter for the output Stream.</param>
        public void SaveToXml(TextWriter writer)
        {
            var xml = new XmlHelper();

            xml.BeginWriteStream(writer);

            WriteHeaderElement(xml);

            xml.WriteElement("ClassName", ClassName);
            xml.WriteElement("Namespace", this.Namespace, "");

            xml.WriteElement("SealedClass", this.SealedClass);
            xml.WriteElement("Visibility", this.Visibility.ToString(), "Public");

            xml.WriteElement("IgnoreEmptyLines", this.IgnoreEmptyLines);
            xml.WriteElement("IgnoreFirstLines", this.IgnoreFirstLines.ToString(), "0");
            xml.WriteElement("IgnoreLastLines", this.IgnoreLastLines.ToString(), "0");

            xml.WriteElement("CommentMarker", this.IgnoreCommentedLines.CommentMarker, "");
            xml.WriteElement("CommentInAnyPlace",
                this.IgnoreCommentedLines.InAnyPlace.ToString().ToLower(),
                true.ToString().ToLower());

            xml.WriteElement("RecordCondition", this.RecordCondition.Condition.ToString(), "None");
            xml.WriteElement("RecordConditionSelector", this.RecordCondition.Selector, "");

            WriteExtraElements(xml);

            xml.Writer.WriteStartElement("Fields");

            for (int i = 0; i < mFields.Count; i++)
                ((FieldBuilder) mFields[i]).SaveToXml(xml);

            xml.Writer.WriteEndElement();

            xml.Writer.WriteEndElement();
            xml.EndWrite();
        }

        /// <summary>
        /// write header information for this class
        /// </summary>
        /// <param name="writer">writer to write on</param>
        internal abstract void WriteHeaderElement(XmlHelper writer);

        /// <summary>
        /// write any extra information for the class
        /// </summary>
        /// <param name="writer">writer to put xml on</param>
        internal abstract void WriteExtraElements(XmlHelper writer);

        /// <summary>
        /// read class elements from xml document
        /// </summary>
        /// <param name="document">document to read from</param>
        internal abstract void ReadClassElements(XmlDocument document);

        /// <summary>
        /// read attribute information for class
        /// </summary>
        /// <param name="node"></param>
        internal abstract void ReadField(XmlNode node);

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly RecordConditionInfo mRecordConditionInfo = new RecordConditionInfo();

        /// <summary>Allow to tell the engine what records must be included or excluded while reading.</summary>
        public RecordConditionInfo RecordCondition
        {
            get { return mRecordConditionInfo; }
        }


        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly IgnoreCommentInfo mIgnoreCommentInfo = new IgnoreCommentInfo();

        /// <summary>Indicates that the engine must ignore the lines with this comment marker.</summary>
        public IgnoreCommentInfo IgnoreCommentedLines
        {
            get { return mIgnoreCommentInfo; }
        }


        /// <summary>Allow to tell the engine what records must be included or excluded while reading.</summary>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        public sealed class RecordConditionInfo
        {
            internal RecordConditionInfo() {}

            private RecordCondition mRecordCondition = FileHelpers.RecordCondition.None;

            /// <summary>Allow to tell the engine what records must be included or excluded while reading.</summary>
            public RecordCondition Condition
            {
                get { return mRecordCondition; }
                set { mRecordCondition = value; }
            }

            private string mRecordConditionSelector = "";

            /// <summary>The selector used by the <see cref="RecordCondition"/>.</summary>
            public string Selector
            {
                get { return mRecordConditionSelector; }
                set { mRecordConditionSelector = value; }
            }
        }

        /// <summary>Indicates that the engine must ignore the lines with this comment marker.</summary>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        public sealed class IgnoreCommentInfo
        {
            internal IgnoreCommentInfo() {}

            /// <summary>
            /// <para>Indicates that the engine must ignore the lines with this
            /// comment marker.</para>
            /// <para>An empty string or null indicates that the engine does
            /// not look for comments</para>
            /// </summary>
            public string CommentMarker
            {
                get { return mMarker; }
                set
                {
                    if (value != null)
                        value = value.Trim();

                    mMarker = value;
                }
            }

            private string mMarker = "";

            /// <summary>Indicates if the comment can have spaces or tabs at left (true by default)</summary>
            public bool InAnyPlace
            {
                get { return mInAnyPlace; }
                set { mInAnyPlace = value; }
            }

            private bool mInAnyPlace = true;
        }

        /// <summary>
        /// Break down a class into a generic or a full type name
        /// </summary>
        /// <param name="type">type to get type of</param>
        /// <returns>Type of the class as a string</returns>
        internal static string TypeToString(Type type)
        {
            if (type.IsGenericType) {
                var sb = new StringBuilder();
                sb.Append(type.FullName.Substring(0, type.FullName.IndexOf("`", StringComparison.Ordinal)));
                sb.Append("<");

                Type[] args = type.GetGenericArguments();

                for (int i = 0; i < args.Length; i++) {
                    if (i > 0)
                        sb.Append(",");

                    sb.Append(TypeToString(args[i]));
                }
                sb.Append(">");

                return sb.ToString();
            }
            else
                return type.FullName;
        }
    }
#endif
}