// Project: XnaGraphicEngineContentProcessors, File: StringHelper.cs
// Namespace: XnaGraphicEngineContentProcessors.Helpers, Class: StringHelper
// Creation date: 22.11.2004 09:51
// Last modified: 19.01.2006 04:10
// Generated with Commenter by abi.exDream.com

#region Using directives
#if UNIT_TESTING // DEBUG
using NUnit.Framework;
#endif
using System;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.Diagnostics;
using System.Collections.Generic;
#endregion

namespace XnaGraphicEngineContentProcessors.Helpers
{
	/// <summary>
	/// StringHelper: Provides additional or simplified string functions.
	/// This class does also offer a lot of powerful functions and
	/// allows complicated string operations.
	/// Easy functions at the beginning, harder ones later.
	/// </summary>
	public class StringHelper
	{
		#region Constants
		public const string NewLine = "\r\n";
		#endregion

		#region Constructor (it is not allowed to instantiate)
		/// <summary>
		/// Don't allow instantiating this class.
		/// </summary>
		private StringHelper()
		{
		} // StringHelper()
		#endregion

		#region Comparing, Counting and Extraction
		/// <summary>
		/// Check if a string (s1, can be longer as s2) begins with another
		/// string (s2), only if s1 begins with the same string data as s2,
		/// true is returned, else false. The string compare is case insensitive.
		/// </summary>
		static public bool BeginsWith(string s1, string s2)
		{
			return String.Compare(s1, 0, s2, 0, s2.Length, true,
				CultureInfo.CurrentCulture) == 0;
		} // BeginsWith(s1, s2)

		/// <summary>
		/// Helps to compare strings, uses case insensitive comparison.
		/// String.Compare is also gay because we have always to check for == 0.
		/// </summary>
		static public bool Compare(string s1, string s2)
		{
			return String.Compare(s1, s2, true,
				CultureInfo.CurrentCulture) == 0;
		} // Compare(s1, s2)

		/// <summary>
		/// Helps to compare strings, uses case insensitive comparison.
		/// String.Compare is also gay because we have always to check for == 0.
		/// This overload allows multiple strings to be checked, if any of
		/// them matches we are good to go (e.g. ("hi", {"hey", "hello", "hi"})
		/// will return true).
		/// </summary>
		static public bool Compare(string s1, string[] anyMatch)
		{
			if (anyMatch == null)
				throw new ArgumentNullException("anyMatch",
					"Unable to execute method without valid anyMatch array.");

			foreach (string match in anyMatch)
				if (String.Compare(s1, match, true,
					CultureInfo.CurrentCulture) == 0)
					return true;
			return false;
		} // Compare(s1, anyMatch)

		/// <summary>
		/// Is a specific name in a list of strings?
		/// </summary>
		static public bool IsInList(
			string name,
			ArrayList list,
			bool ignoreCase)
		{
			if (list == null)
				throw new ArgumentNullException("list",
					"Unable to execute method without valid list.");

			foreach (string listEntry in list)
				if (String.Compare(name, listEntry, ignoreCase,
					CultureInfo.CurrentCulture) == 0)
					return true;
			return false;
		} // IsInList(name, list, ignoreCase)

		/// <summary>
		/// Is a specific name in a list of strings?
		/// </summary>
		static public bool IsInList(
			string name,
			string[] list,
			bool ignoreCase)
		{
			if (list == null)
				throw new ArgumentNullException("list",
					"Unable to execute method without valid list.");

			foreach (string listEntry in list)
				if (String.Compare(name, listEntry, ignoreCase,
					CultureInfo.CurrentCulture) == 0)
					return true;
			return false;
		} // IsInList(name, list, ignoreCase)

		/// <summary>
		/// Count words in a text (words are only separated by ' ' (spaces))
		/// </summary>
		static public int CountWords(string text)
		{
			if (text == null)
				throw new ArgumentNullException("text",
					"Unable to execute method without valid text.");

			return text.Split(new char[] { ' ' }).Length;
		} // CountWords(text)

		/// <summary>
		/// Compare char case insensitive
		/// </summary>
		/// <param name="c1">C 1</param>
		/// <param name="c2">C 2</param>
		/// <returns>Bool</returns>
		public static bool CompareCharCaseInsensitive(char c1, char c2)
		{
			return char.ToLower(c1) == char.ToLower(c2);
			// Another way (slower):
			// return String.Compare("" + c1, "" + c2, true) == 0;
		} // CompareCharCaseInsensitive(c1, c2)

		/// <summary>
		/// Get last word
		/// </summary>
		/// <param name="text">Text</param>
		/// <returns>String</returns>
		public static string GetLastWord(string text)
		{
			if (text == null)
				throw new ArgumentNullException("text",
					"Unable to execute method without valid text.");

			string[] words = text.Split(new char[] { ' ' });
			if (words.Length > 0)
				return words[words.Length - 1];
			return text;
		} // GetLastWord(text)

		/// <summary>
		/// Remove last word
		/// </summary>
		/// <param name="text">Text</param>
		/// <returns>String</returns>
		public static string RemoveLastWord(string text)
		{
			string lastWord = GetLastWord(text);
			// Fix 2004-10-08: new length can be 0 for killing first word
			if (text == lastWord)
				return "";
			else if (lastWord.Length == 0 || text.Length == 0 ||
				text.Length - lastWord.Length - 1 <= 0)
				return text;
			else
				return text.Substring(0, text.Length - lastWord.Length - 1);
		} // RemoveLastWord(text)

		/// <summary>
		/// Get all spaces and tabs at beginning
		/// </summary>
		/// <param name="text">Text</param>
		/// <returns>String</returns>
		static public string GetAllSpacesAndTabsAtBeginning(string text)
		{
			if (text == null)
				throw new ArgumentNullException("text",
					"Unable to execute method without valid text.");

			StringBuilder ret = new StringBuilder();
			for (int pos = 0; pos < text.Length; pos++)
			{
				if (text[pos] == ' ' ||
					text[pos] == '\t')
					ret.Append(text[pos]);
				else
					break;
			} // for (pos)
			return ret.ToString();
		} // GetAllSpacesAndTabsAtBeginning(text)

		/// <summary>
		/// Get tab depth
		/// </summary>
		/// <param name="text">Text</param>
		/// <returns>Int</returns>
		static public int GetTabDepth(string text)
		{
			for (int textPos = 0; textPos < text.Length; textPos++)
				if (text[textPos] != '\t')
					return textPos;
			return text.Length;
		} // GetTabDepth(text)

		/// <summary>
		/// Check string word length
		/// </summary>
		public static string CheckStringWordLength(
			string originalText, int maxLength)
		{
			if (originalText == null)
				throw new ArgumentNullException("originalText",
					"Unable to execute method without valid text.");

			string[] splitted = originalText.Split(new char[] { ' ' });
			string ret = "";
			foreach (string word in splitted)
			{
				if (word.Length <= maxLength)
					ret += word + " ";
				else
				{
					for (int i = 0; i < word.Length / maxLength; i++)
						ret += word.Substring(i * maxLength, maxLength) + " ";
				} // else
			} // foreach (word, splitted)
			return ret.TrimEnd();
		} // CheckStringWordLength(originalText, maxLength)
		#endregion

		#region String contains (for case insensitive compares)
		/// <summary>
		/// Is searchName contained in textToCheck, will check case insensitive,
		/// for a normal case sensitive test use textToCheck.Contains(searchName)
		/// </summary>
		/// <param name="textToCheck">Text to check</param>
		/// <param name="searchName">Search name</param>
		/// <returns>Bool</returns>
		public static bool Contains(string textToCheck, string searchName)
		{
			return textToCheck.ToLower().Contains(searchName.ToLower());
		} // Contains(textToCheck, searchName)

		/// <summary>
		/// Is any of the names in searchNames contained in textToCheck,
		/// will check case insensitive, for a normal case sensitive test
		/// use textToCheck.Contains(searchName).
		/// </summary>
		/// <param name="textToCheck">String to check</param>
		/// <param name="searchNames">Search names</param>
		/// <returns>Bool</returns>
		public static bool Contains(string textToCheck, string[] searchNames)
		{
			string stringToCheckLower = textToCheck.ToLower();
			foreach (string name in searchNames)
				if (stringToCheckLower.Contains(name.ToLower()))
					return true;
			// Nothing found, no searchNames is contained in textToCheck
			return false;
		} // Contains(textToCheck, searchNames)
		#endregion

		#region Write data
		/// <summary>
		/// Returns a string with the array data, byte array version.
		/// </summary>
		static public string WriteArrayData(byte[] byteArray)
		{
			StringBuilder ret = new StringBuilder();
			if (byteArray != null)
				for (int i = 0; i < byteArray.Length; i++)
					ret.Append((ret.Length == 0 ? "" : ", ") +
						byteArray[i].ToString(CultureInfo.InvariantCulture.NumberFormat));
			return ret.ToString();
		} // WriteArrayData(byteArray)

		/// <summary>
		/// Returns a string with the array data, int array version.
		/// </summary>
		static public string WriteArrayData(int[] intArray)
		{
			StringBuilder ret = new StringBuilder();
			if (intArray != null)
				for (int i = 0; i < intArray.Length; i++)
					ret.Append((ret.Length == 0 ? "" : ", ") +
						intArray[i].ToString(CultureInfo.InvariantCulture.NumberFormat));
			return ret.ToString();
		} // WriteArrayData(intArray)

		/// <summary>
		/// Returns a string with the array data, general array version.
		/// </summary>
		static public string WriteArrayData(Array array)
		{
			StringBuilder ret = new StringBuilder();
			if (array != null)
				for (int i = 0; i < array.Length; i++)
					ret.Append((ret.Length == 0 ? "" : ", ") +
						(array.GetValue(i) == null ?
						"null" : array.GetValue(i).ToString()));
			return ret.ToString();
		} // WriteArrayData(array)

		/// <summary>
		/// Returns a string with the array data, general array version
		/// with maxLength bounding (will return string with max. this
		/// number of entries).
		/// </summary>
		static public string WriteArrayData(Array array, int maxLength)
		{
			StringBuilder ret = new StringBuilder();
			if (array != null)
				for (int i = 0; i < array.Length && i < maxLength; i++)
					ret.Append((ret.Length == 0 ? "" : ", ") +
						array.GetValue(i).ToString());
			return ret.ToString();
		} // WriteArrayData(array, maxLength)

		/// <summary>
		/// Returns a string with the array data, ArrayList version.
		/// </summary>
		static public string WriteArrayData(ArrayList array)
		{
			StringBuilder ret = new StringBuilder();
			if (array != null)
				foreach (object obj in array)
					ret.Append((ret.Length == 0 ? "" : ", ") + obj.ToString());
			return ret.ToString();
		} // WriteArrayData(array)

		/// <summary>
		/// Returns a string with the array data, CollectionBase version.
		/// </summary>
		static public string WriteArrayData(CollectionBase collection)
		{
			StringBuilder ret = new StringBuilder();
			if (collection != null)
				foreach (object obj in collection)
					ret.Append((ret.Length == 0 ? "" : ", ") + obj.ToString());
			return ret.ToString();
		} // WriteArrayData(collection)

		/// <summary>
		/// Returns a string with the array data, StringCollection version.
		/// </summary>
		static public string WriteArrayData(StringCollection textCollection)
		{
			StringBuilder ret = new StringBuilder();
			if (textCollection != null)
				foreach (string s in textCollection)
					ret.Append((ret.Length == 0 ? "" : ", ") + s);
			return ret.ToString();
		} // WriteArrayData(textCollection)

		/// <summary>
		/// Returns a string with the array data, enumerable class version.
		/// </summary>
		static public string WriteArrayData(IEnumerable enumerableClass)
		{
			StringBuilder ret = new StringBuilder();
			if (enumerableClass != null)
				foreach (object obj in enumerableClass)
					ret.Append((ret.Length == 0 ? "" : ", ") + obj.ToString());
			return ret.ToString();
		} // WriteArrayData(enumerableClass)

		/// <summary>
		/// Write into space string, useful for writing parameters without
		/// knowing the length of each string, e.g. when writing numbers
		/// (-1, 1.45, etc.). You can use this function to give all strings
		/// the same width in a table. Maybe there is already a string function
		/// for this, but I don't found any useful stuff.
		/// </summary>
		static public string WriteIntoSpaceString(string message, int spaces)
		{
			if (message == null)
				throw new ArgumentNullException("message",
					"Unable to execute method without valid text.");

			// Msg is already that long or longer?
			if (message.Length >= spaces)
				return message;

			// Create string with number of specified spaces
			char[] ret = new char[spaces];

			// Copy data
			int i;
			for (i = 0; i < message.Length; i++)
				ret[i] = message[i];
			// Fill rest with spaces
			for (i = message.Length; i < spaces; i++)
				ret[i] = ' ';

			// Return result
			return new string(ret);
		} // WriteIntoSpaceString(message, spaces)

		/// <summary>
		/// Write Iso Date (Year-Month-Day)
		/// </summary>
		public static string WriteIsoDate(DateTime date)
		{
			return date.Year + "-" +
				date.Month.ToString("00") + "-" +
				date.Day.ToString("00");
		} // WriteIsoDate(date)

		/// <summary>
		/// Write Iso Date and time (Year-Month-Day Hour:Minute)
		/// </summary>
		public static string WriteIsoDateAndTime(DateTime date)
		{
			return date.Year + "-" +
				date.Month.ToString("00") + "-" +
				date.Day.ToString("00") + " " +
				date.Hour.ToString("00") + ":" +
				date.Minute.ToString("00");
		} // WriteIsoDateAndTime(date)

		/// <summary>
		/// Write internet time
		/// </summary>
		/// <param name="time">Time</param>
		/// <param name="daylightSaving">Daylight saving</param>
		/// <returns>String</returns>
		public static string WriteInternetTime(
			DateTime time,
			bool daylightSaving)
		{
			return "@" + ((float)((int)(time.ToUniversalTime().AddHours(
				daylightSaving ? 1 : 0).TimeOfDay.
				TotalSeconds * 100000 / (24 * 60 * 60))) / 100.0f).ToString(
				NumberFormatInfo.InvariantInfo);
		} // WriteInternetTime(time, daylightSaving)
		#endregion

		#region Convert methods
		/// <summary>
		/// Convert string data to int array, string must be in the form
		/// "1, 3, 8, 7", etc. WriteArrayData is the complementar function.
		/// </summary>
		/// <returns>int array, will be null if string is invalid!</returns>
		static public int[] ConvertStringToIntArray(string s)
		{
			// Invalid?
			if (s == null || s.Length == 0)
				return null;

			string[] splitted = s.Split(new char[] { ' ' });
			int[] ret = new int[splitted.Length];
			for (int i = 0; i < ret.Length; i++)
			{
				try
				{
					ret[i] = Convert.ToInt32(splitted[i]);
				} // try
				catch { } // ignore
			} // for (i)
			return ret;
		} // ConvertStringToIntArray(str)

		/// <summary>
		/// Convert string data to float array, string must be in the form
		/// "1.5, 3.534, 8.76, 7.49", etc. WriteArrayData is the complementar
		/// function.
		/// </summary>
		/// <returns>float array, will be null if string is invalid!</returns>
		static public float[] ConvertStringToFloatArray(string s)
		{
			// Invalid?
			if (s == null || s.Length == 0)
				return null;

			string[] splitted = s.Split(new char[] { ' ' });
			float[] ret = new float[splitted.Length];
			for (int i = 0; i < ret.Length; i++)
			{
				try
				{
					ret[i] = Convert.ToSingle(splitted[i]);
				} // try
				catch { } // ignore
			} // for (i)
			return ret;
		} // ConvertStringToIntArray(str)
		#endregion

		#region File stuff
		/// <summary>
		/// Extracts filename from full path+filename, cuts of extension
		/// if cutExtension is true. Can be also used to cut of directories
		/// from a path (only last one will remain).
		/// </summary>
		static public string ExtractFilename(string pathFile, bool cutExtension)
		{
			if (pathFile == null)
				return "";

			string[] fileName = pathFile.Split(new char[] { '\\' });
			if (fileName.Length == 0)
			{
				if (cutExtension)
					return CutExtension(pathFile);
				return pathFile;
			} // if (fileName.Length)

			if (cutExtension)
				return CutExtension(fileName[fileName.Length - 1]);
			return fileName[fileName.Length - 1];
		} // ExtractFilename(pathFile, cutExtension)

		/// <summary>
		/// Get directory of path+File, if only a path is given we will cut off
		/// the last sub path!
		/// </summary>
		static public string GetDirectory(string pathFile)
		{
			if (pathFile == null)
				return "";
			int i = pathFile.LastIndexOf("\\");
			if (i >= 0 && i < pathFile.Length)
				// Return directory
				return pathFile.Substring(0, i);
			// No sub directory found (parent of some dir is "")
			return "";
		} // GetDirectory(pathFile)

		/// <summary>
		/// Same as GetDirectory(): Get directory of path+File,
		/// if only a path is given we will cut of the last sub path!
		/// </summary>
		static public string CutOneFolderOff(string path)
		{
			// GetDirectory does exactly what we need!
			return GetDirectory(path);
		} // CutOneFolderOff(path)

		/// <summary>
		/// Splits a path into all parts of its directories,
		/// e.g. "maps\\sub\\kekse" becomes
		/// {"maps\\sub\\kekse","maps\\sub","maps"}
		/// </summary>
		static public string[] SplitDirectories(string path)
		{
			ArrayList localList = new ArrayList();
			localList.Add(path);
			do
			{
				path = CutOneFolderOff(path);
				if (path.Length > 0)
					localList.Add(path);
			} while (path.Length > 0);

			return (string[])localList.ToArray(typeof(string));
		} // SplitDirectories(path)

		/// <summary>
		/// Remove first directory of path (if one exists).
		/// e.g. "maps\\mymaps\\hehe.map" becomes "mymaps\\hehe.map"
		/// Also used to cut first folder off, especially useful for relative
		/// paths. e.g. "maps\\test" becomes "test"
		/// </summary>
		static public string RemoveFirstDirectory(string path)
		{
			int i = path.IndexOf("\\");
			if (i >= 0 && i < path.Length)
				// Return rest of path
				return path.Substring(i + 1);
			// No first directory found, just return original path
			return path;
		} // RemoveFirstDirectory(path)

		/// <summary>
		/// Check if a folder is a direct sub folder of a main folder.
		/// True is only returned if this is a direct sub folder, not if
		/// it is some sub folder few levels below.
		/// </summary>
		static public bool IsDirectSubfolder(string subfolder, string mainFolder)
		{
			// First check if subFolder is really a sub folder of mainFolder
			if (subfolder != null &&
				subfolder.StartsWith(mainFolder))
			{
				// Same order?
				if (subfolder.Length < mainFolder.Length + 1)
					// Then it ain't a sub folder!
					return false;
				// Ok, now check if this is direct sub folder or some sub folder
				// of mainFolder sub folder
				string folder = subfolder.Remove(0, mainFolder.Length + 1);
				// Check if this is really a direct sub folder
				for (int i = 0; i < folder.Length; i++)
					if (folder[i] == '\\')
						// No, this is a sub folder of mainFolder sub folder
						return false;
				// Ok, this is a direct sub folder of mainFolder!
				return true;
			} // if (subFolder)
			// Not even any sub folder!
			return false;
		} // IsDirectSubFolder(subFolder, mainFolder)

		/// <summary>
		/// Cut of extension, e.g. "hi.txt" becomes "hi"
		/// </summary>
		static public string CutExtension(string file)
		{
			if (file == null)
				return "";
			int l = file.LastIndexOf('.');
			if (l > 0)
				return file.Remove(l, file.Length - l);
			return file;
		} // CutExtension(file)

		/// <summary>
		/// Get extension (the stuff behind that '.'),
		/// e.g. "test.bmp" will return "bmp"
		/// </summary>
		static public string GetExtension(string file)
		{
			if (file == null)
				return "";
			int l = file.LastIndexOf('.');
			if (l > 0 && l < file.Length)
				return file.Remove(0, l + 1);
			return "";
		} // GetExtension(file)
		#endregion

		#region String splitting and getting it back together
		/// <summary>
		/// Performs basically the same job as String.Split, but does
		/// trim all parts, no empty parts are returned, e.g.
		/// "hi  there" returns "hi", "there", String.Split would return
		/// "hi", "", "there".
		/// </summary>
		public static string[] SplitAndTrim(string text, char separator)
		{
			ArrayList ret = new ArrayList();
			string[] splitted = text.Split(new char[] { separator });
			foreach (string s in splitted)
				if (s.Length > 0)
					ret.Add(s);
			return (string[])ret.ToArray(typeof(string));
		} // SplitAndTrim(text, separator)

		/// <summary>
		/// Splits a multi line string to several strings and
		/// returns the result as a string array.
		/// Will also remove any \r, \n or space character
		/// at the end of each line!
		/// </summary>
		public static string[] SplitMultilineText(string text)
		{
			if (text == null)
				throw new ArgumentNullException("text",
					"Unable to execute method without valid text.");

			ArrayList ret = new ArrayList();
			// Supports any format, only \r, only \n, normal \n\r,
			// crazy \r\n or even mixed \n\r with any format
			string[] splitted1 = text.Split(new char[] { '\n' });
			string[] splitted2 = text.Split(new char[] { '\r' });
			string[] splitted =
				splitted1.Length >= splitted2.Length ?
			splitted1 : splitted2;

			foreach (string s in splitted)
			{
				// Never add any \r or \n to the single lines
				if (s.EndsWith("\r") ||
					s.EndsWith("\n"))
					ret.Add(s.Substring(0, s.Length - 1));
				else if (s.StartsWith("\n") ||
					s.StartsWith("\r"))
					ret.Add(s.Substring(1));
				else
					ret.Add(s);
			} // foreach (s, splitted)

			return (string[])ret.ToArray(typeof(string));
		} // SplitMultiLineText(text)

		/// <summary>
		/// Build string from lines
		/// </summary>
		/// <param name="lines">Lines</param>
		/// <param name="startLine">Start line</param>
		/// <param name="startOffset">Start offset</param>
		/// <param name="endLine">End line</param>
		/// <param name="endOffset">End offset</param>
		/// <param name="separator">Separator</param>
		/// <returns>String</returns>
		static public string BuildStringFromLines(
			string[] lines,
			int startLine, int startOffset,
			int endLine, int endOffset,
			string separator)
		{
			if (lines == null)
				throw new ArgumentNullException("lines",
					"Unable to execute method without valid lines.");

			// Check if all values are in range (correct if not)
			if (startLine >= lines.Length)
				startLine = lines.Length - 1;
			if (endLine >= lines.Length)
				endLine = lines.Length - 1;
			if (startLine < 0)
				startLine = 0;
			if (endLine < 0)
				endLine = 0;
			if (startOffset >= lines[startLine].Length)
				startOffset = lines[startLine].Length - 1;
			if (endOffset >= lines[endLine].Length)
				endOffset = lines[endLine].Length - 1;
			if (startOffset < 0)
				startOffset = 0;
			if (endOffset < 0)
				endOffset = 0;

			StringBuilder builder = new StringBuilder((endLine - startLine) * 80);
			for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++)
			{
				if (lineNumber == startLine)
					builder.Append(lines[lineNumber].Substring(startOffset));
				else if (lineNumber == endLine)
					builder.Append(lines[lineNumber].Substring(0, endOffset + 1));
				else
					builder.Append(lines[lineNumber]);

				if (lineNumber != endLine)
					builder.Append(separator);
			} // for (lineNumber)
			return builder.ToString();
		} // BuildStringFromLines(lines, startLine, startOffset)

		static public string BuildStringFromLines(
			string[] lines, string separator)
		{
			StringBuilder builder = new StringBuilder(lines.Length * 80);
			for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++)
			{
				builder.Append(lines[lineNumber]);
				if (lineNumber != lines.Length - 1)
					builder.Append(separator);
			} // for (lineNumber)
			return builder.ToString();
		} // BuildStringFromLines(lines, separator)

		/// <summary>
		/// Build string from lines
		/// </summary>
		/// <param name="lines">Lines</param>
		/// <returns>String</returns>
		static public string BuildStringFromLines(string[] lines)
		{
			return BuildStringFromLines(lines, NewLine);
		} // BuildStringFromLines(lines)

		/// <summary>
		/// Build string from lines
		/// </summary>
		/// <param name="lines">Lines</param>
		/// <param name="startLine">Start line</param>
		/// <param name="endLine">End line</param>
		/// <param name="separator">Separator</param>
		/// <returns>String</returns>
		static public string BuildStringFromLines(
			string[] lines,
			int startLine,
			int endLine,
			string separator)
		{
			// Check if all values are in range (correct if not)
			if (startLine < 0)
				startLine = 0;
			if (endLine < 0)
				endLine = 0;
			if (startLine >= lines.Length)
				startLine = lines.Length - 1;
			if (endLine >= lines.Length)
				endLine = lines.Length - 1;

			StringBuilder builder = new StringBuilder((endLine - startLine) * 80);
			for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++)
			{
				builder.Append(lines[lineNumber]);
				if (lineNumber != endLine)
					builder.Append(separator);
			} // for (lineNumber)
			return builder.ToString();
		} // BuildStringFromLines(lines, startLine, endLine)

		/// <summary>
		/// Cut modes
		/// </summary>
		public enum CutMode
		{
			Begin,
			End,
			BothEnds
		} // enum CutMode

		/// <summary>
		/// Maximum string length
		/// </summary>
		/// <param name="originalText">Original text</param>
		/// <param name="maxLength">Maximum length</param>
		/// <param name="cutMode">Cut mode</param>
		/// <returns>String</returns>
		public static string MaxStringLength(string originalText,
			int maxLength, CutMode cutMode)
		{
			if (originalText.Length <= maxLength)
				return originalText;

			if (cutMode == CutMode.Begin)
				return originalText.Substring(
					originalText.Length - maxLength, maxLength);
			else if (cutMode == CutMode.End)
				return originalText.Substring(0, maxLength);
			else // logic: if ( cutMode == CutModes.BothEnds )
				return originalText.Substring(
					(originalText.Length - maxLength) / 2, maxLength);
		} // MaxStringLength(originalText, maxLength, cutMode)

		/// <summary>
		/// Get left part of everything to the left of the first
		/// occurrence of a character.
		/// </summary>
		public static string GetLeftPartAtFirstOccurence(
			string sourceText, char ch)
		{
			if (sourceText == null)
				throw new ArgumentNullException("sourceText",
					"Unable to execute this method without valid string.");

			int index = sourceText.IndexOf(ch);
			if (index == -1)
				return sourceText;

			return sourceText.Substring(0, index);
		} // GetLeftPartAtFirstOccurence(sourceText, ch)

		/// <summary>
		/// Get right part of everything to the right of the first
		/// occurrence of a character.
		/// </summary>
		public static string GetRightPartAtFirstOccurrence(
			string sourceText, char ch)
		{
			if (sourceText == null)
				throw new ArgumentNullException("sourceText",
					"Unable to execute this method without valid string.");

			int index = sourceText.IndexOf(ch);
			if (index == -1)
				return "";

			return sourceText.Substring(index + 1);
		} // GetRightPartAtFirstOccurrence(sourceText, ch)

		/// <summary>
		/// Get left part of everything to the left of the last
		/// occurrence of a character.
		/// </summary>
		public static string GetLeftPartAtLastOccurrence(
			string sourceText, char ch)
		{
			if (sourceText == null)
				throw new ArgumentNullException("sourceText",
					"Unable to execute this method without valid string.");

			int index = sourceText.LastIndexOf(ch);
			if (index == -1)
				return sourceText;

			return sourceText.Substring(0, index);
		} // GetLeftPartAtLastOccurrence(sourceText, ch)

		/// <summary>
		/// Get right part of everything to the right of the last
		/// occurrence of a character.
		/// </summary>
		public static string GetRightPartAtLastOccurrence(
			string sourceText, char ch)
		{
			if (sourceText == null)
				throw new ArgumentNullException("sourceText",
					"Unable to execute this method without valid string.");

			int index = sourceText.LastIndexOf(ch);
			if (index == -1)
				return sourceText;

			return sourceText.Substring(index + 1);
		} // GetRightPartAtLastOccurrence(sourceText, ch)

		/// <summary>
		/// Create password string
		/// </summary>
		/// <param name="originalText">Original text</param>
		/// <returns>String</returns>
		public static string CreatePasswordString(string originalText)
		{
			if (originalText == null)
				throw new ArgumentNullException("originalText",
					"Unable to execute this method without valid string.");

			string passwordString = "";
			for (int i = 0; i < originalText.Length; i++)
				passwordString += "*";
			return passwordString;
		} // CreatePasswordString(originalText)

		/// <summary>
		/// Helper function to convert letter to lowercase. Could someone
		/// tell me the reason why there is no function for that in char?
		/// </summary>
		public static char ToLower(char letter)
		{
			return (char)letter.ToString().ToLower(
				CultureInfo.InvariantCulture)[0];
		} // ToLower(letter)

		/// <summary>
		/// Helper function to convert letter to uppercase. Could someone
		/// tell me the reason why there is no function for that in char?
		/// </summary>
		public static char ToUpper(char letter)
		{
			return (char)letter.ToString().ToUpper(
				CultureInfo.InvariantCulture)[0];
		} // ToUpper(letter)

		/// <summary>
		/// Helper function to check if this is an lowercase letter.
		/// </summary>
		public static bool IsLowercaseLetter(char letter)
		{
			return letter == ToLower(letter);
		} // IsLowercaseLetter(letter)

		/// <summary>
		/// Helper function to check if this is an uppercase letter.
		/// </summary>
		public static bool IsUppercaseLetter(char letter)
		{
			return letter == ToUpper(letter);
		} // IsUppercaseLetter(letter)

		/// <summary>
		/// Helper function for SplitFunctionNameToWordString to detect
		/// abbreviations in the function name
		/// </summary>
		private static int GetAbbreviationLengthInFunctionName(
			string functionName, int startPos)
		{
			StringBuilder abbreviation = new StringBuilder();
			// Go through string until we reach a lower letter or it ends
			for (int pos = startPos; pos < functionName.Length; pos++)
			{
				// Quit if its not an uppercase letter
				if (StringHelper.IsUppercaseLetter(functionName[pos]) == false)
					break;
				// Else just add letter
				abbreviation.Append(functionName[pos]);
			} // for (pos)

			// Abbreviation has to be at least 2 letters long.
			if (abbreviation.Length >= 2)
			{
				// If not at end of functionName, last letter belongs to next name,
				// e.g. "TW" is not a abbreviation in "HiMrTWhatsUp",
				// "AB" isn't one either in "IsABall",
				// but "PQ" is in "PQList" and "AB" is in "MyAB"
				if (startPos + abbreviation.Length >= functionName.Length)
					// Ok, then return full abbreviation length
					return abbreviation.Length;
				// Else return length - 1 because of next word
				return abbreviation.Length - 1;
			} // if (abbreviation.Length)

			// No Abbreviation, just return 1
			return 1;
		} // GetAbbreviationLengthInFunctionName(functionName, startPos)

		/// <summary>
		/// Checks if letter is space ' ' or any punctuation (. , : ; ' " ! ?)
		/// </summary>
		public static bool IsSpaceOrPunctuation(char letter)
		{
			return
				letter == ' ' ||
				letter == '.' ||
				letter == ',' ||
				letter == ':' ||
				letter == ';' ||
				letter == '\'' ||
				letter == '\"' ||
				letter == '!' ||
				letter == '?' ||
				letter == '*';
		} // IsSpaceOrPunctuation(letter)

		/// <summary>
		/// Splits a function name to words, e.g.
		/// "MakeDamageOnUnit" gets "Make damage on unit".
		/// Will also detect abbreviation like TCP and leave them
		/// intact, e.g. "CreateTCPListener" gets "Create TCP listener".
		/// </summary>
		public static string SplitFunctionNameToWordString(string functionName)
		{
			if (functionName == null ||
				functionName.Length == 0)
				return "";

			string ret = "";
			// Go through functionName and find big letters!
			for (int pos = 0; pos < functionName.Length; pos++)
			{
				char letter = functionName[pos];
				// First letter is always big!
				if (pos == 0 ||
					pos == 1 && StringHelper.IsUppercaseLetter(functionName[1]) &&
					StringHelper.IsUppercaseLetter(functionName[0]) ||
					pos == 2 && StringHelper.IsUppercaseLetter(functionName[2]) &&
					StringHelper.IsUppercaseLetter(functionName[1]) &&
					StringHelper.IsUppercaseLetter(functionName[0]))
					ret += StringHelper.ToUpper(letter);
					// Found uppercase letter?
				else if (StringHelper.IsUppercaseLetter(letter) &&
					//also support numbers and other symbols not lower/upper letter:
					//StringHelper.IsLowercaseLetter(letter) == false &&
					// But don't allow space or any punctuation (. , : ; ' " ! ?)
					StringHelper.IsSpaceOrPunctuation(letter) == false &&
					ret.EndsWith(" ") == false)
				{
					// Could be new word, but we have to check if its an abbreviation
					int abbreviationLength = GetAbbreviationLengthInFunctionName(
						functionName, pos);
					// Found valid abbreviation?
					if (abbreviationLength > 1)
					{
						// Then add it
						ret += " " + functionName.Substring(pos, abbreviationLength);
						// And advance pos (abbreviation is longer than 1 letter)
						pos += abbreviationLength - 1;
					} // if (abbreviationLength)
						// Else just add new word (in lower letter)
					else
						ret += " " + StringHelper.ToLower(letter);
				} // else if
				else
					// Just add letter
					ret += letter;
			} // for (pos)
			return ret;
		} // SplitFunctionNameToWordString(functionName)
		#endregion
		
		#region Remove character
		/// <summary>
		/// Remove character from text.
		/// </summary>
		/// <param name="text">Text</param>
		/// <param name="characterToBeRemoved">Character to be removed</param>
		public static void RemoveCharacter(ref string text,
			char characterToBeRemoved)
		{
			if (text == null)
				throw new ArgumentNullException("text",
					"Unable to execute method without valid text.");

			if (text.Contains(characterToBeRemoved.ToString()))
				text = text.Replace(characterToBeRemoved.ToString(), "");
		} // RemoveCharacter(text, characterToBeRemoved)
		#endregion

		#region Kb/mb name generator
		/// <summary>
		/// Write bytes, KB, MB, GB, TB message.
		/// 1 KB = 1024 Bytes
		/// 1 MB = 1024 KB = 1048576 Bytes
		/// 1 GB = 1024 MB = 1073741824 Bytes
		/// 1 TB = 1024 GB = 1099511627776 Bytes
		/// E.g. 100 will return "100 Bytes"
		/// 2048 will return "2.00 KB"
		/// 2500 will return "2.44 KB"
		/// 1534905 will return "1.46 MB"
		/// 23045904850904 will return "20.96 TB"
		/// </summary>
		public static string WriteBigByteNumber(
			long bigByteNumber, string decimalSeperator)
		{
			if (bigByteNumber < 0)
				return "-" + WriteBigByteNumber(-bigByteNumber);

			if (bigByteNumber <= 999)
				return bigByteNumber + " Bytes";
			if (bigByteNumber <= 999 * 1024)
			{
				double fKB = (double)bigByteNumber / 1024.0;
				return (int)fKB + decimalSeperator +
					((int)(fKB * 100.0f) % 100).ToString("00") + " KB";
			} // if
			if (bigByteNumber <= 999 * 1024 * 1024)
			{
				double fMB = (double)bigByteNumber / (1024.0 * 1024.0);
				return (int)fMB + decimalSeperator +
					((int)(fMB * 100.0f) % 100).ToString("00") + " MB";
			} // if
			// this is very big ^^ will not fit into int
			if (bigByteNumber <= 999L * 1024L * 1024L * 1024L)
			{
				double fGB = (double)bigByteNumber / (1024.0 * 1024.0 * 1024.0);
				return (int)fGB + decimalSeperator +
					((int)(fGB * 100.0f) % 100).ToString("00") + " GB";
			} // if
			//if ( num <= 999*1024*1024*1024*1024 )
			//{
			double fTB = (double)bigByteNumber / (1024.0 * 1024.0 * 1024.0 * 1024.0);
			return (int)fTB + decimalSeperator +
				((int)(fTB * 100.0f) % 100).ToString("00") + " TB";
			//} // if
		} // WriteBigByteNumber(num, decimalSeperator)

		/// <summary>
		/// Write bytes, KB, MB, GB, TB message.
		/// 1 KB = 1024 Bytes
		/// 1 MB = 1024 KB = 1048576 Bytes
		/// 1 GB = 1024 MB = 1073741824 Bytes
		/// 1 TB = 1024 GB = 1099511627776 Bytes
		/// E.g. 100 will return "100 Bytes"
		/// 2048 will return "2.00 KB"
		/// 2500 will return "2.44 KB"
		/// 1534905 will return "1.46 MB"
		/// 23045904850904 will return "20.96 TB"
		/// </summary>
		public static string WriteBigByteNumber(long bigByteNumber)
		{
			string decimalSeperator = CultureInfo.CurrentCulture.
				NumberFormat.CurrencyDecimalSeparator;
			return WriteBigByteNumber(bigByteNumber, decimalSeperator);
		} // WriteBigByteNumber(num)
		#endregion

		#region Unit Testing
#if UNIT_TESTING // DEBUG
		/// <summary>
		/// String helper tests
		/// </summary>
		[TestFixture]
		public class StringHelperTests
		{
			/// <summary>
			/// Test is in list
			/// </summary>
			[Test]
			public void TestIsInList()
			{
				Assert.IsTrue(IsInList("whats",
					new string[] { "hi", "whats", "up?" }, false));
				Assert.IsFalse(IsInList("no way",
					new string[] { "omg", "no no", "there is no way!" }, false));
			} // TestIsInList()

			/// <summary>
			/// Test write array
			/// </summary>
			[Test]
			public void TestWriteArray()
			{
				Assert.AreEqual(
					"3, 5, 10",
					WriteArrayData(new int[] { 3, 5, 10 }));
				Assert.AreEqual(
					"one, after, another",
					WriteArrayData(new string[] { "one", "after", "another" }));
				List<string> genericList = new List<string>();
				genericList.Add("whats");
				genericList.AddRange(new string[] { "going", "on" });
				Assert.AreEqual(
					"whats, going, on",
					WriteArrayData(genericList));
			} // TestWriteArray()

			/// <summary>
			/// Test split string and combine again
			/// </summary>
			[Test]
			public void TestSplitStringAndCombineAgain()
			{
				string testText =
					@"using System;

namespace TestVs2003
{
	public class Form1 : System.Windows.Forms.Form
	{
		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main() 
		{
			Application.Run(new Form1());
		}
	}
}";
				// Test splitting lines and combine them back together
				string[] lines = SplitMultilineText(testText);
				Assert.IsTrue(lines.Length > 0, "no lines were returned");
				string reconstructedText = BuildStringFromLines(lines);
				Assert.AreEqual(testText, reconstructedText,
					"testText is not equal to reconstructedText");

				// Also test other functions
				string[] words = SplitAndTrim(testText, ' ');
				Assert.AreEqual(words.Length, CountWords(testText), "words.Length");
				Assert.AreEqual(testText, BuildStringFromLines(words, " "));
				// Call with some invalid values
				BuildStringFromLines(words, 1, -3, 4, 6, " ");
				BuildStringFromLines(words, 1, 3, 400, 6, " ");
				BuildStringFromLines(words, 1, 3, 4, 600, " ");
				// Check is we can build a part back together
				// Check if we can build a part back together
				Assert.AreEqual(
					testText.Substring(10, 20),
					BuildStringFromLines(lines, 0, 10, 2, 12, NewLine));

				// Get section code and check if we got everything right
				Assert.AreEqual(
					@"static void Main() 
		{
			Application.Run(new Form1());
		}",
					BuildStringFromLines(lines, 10, 2, 13, 3, NewLine));

				// Also check if we change some line we still got the same
				// number of resulting lines.
				lines[13] += " // Test comment";
				Assert.AreEqual(lines.Length,
					SplitMultilineText(BuildStringFromLines(lines, NewLine)).Length);
			} // TestSplitStringAndCombineAgain()

			/// <summary>
			/// Test split multiline text
			/// </summary>
			[Test]
			public void TestSplitMultilineText()
			{
				Assert.AreEqual(4,
					StringHelper.SplitMultilineText(
					@"test
now an empty lie

thats it").Length);
				// Test few empty lines
				Assert.AreEqual(6,
					StringHelper.SplitMultilineText(
					"hi\n\n\n\n\rthere\n").Length);
				// Test all combination of '\r' and '\n'
				Assert.AreEqual(2,
					StringHelper.SplitMultilineText(
					"test1" + '\r' + "test2").Length);
				Assert.AreEqual(2,
					StringHelper.SplitMultilineText(
					"test1" + '\n' + "test2").Length);
				Assert.AreEqual(2,
					StringHelper.SplitMultilineText(
					"test1" + "\n\r" + "test2").Length);
				Assert.AreEqual(2,
					StringHelper.SplitMultilineText(
					"test1" + "\r\n" + "test2").Length);
				// Also test if mixing normal "\n\r" with '\r' or '\n'
				Assert.AreEqual(3,
					StringHelper.SplitMultilineText(
					"test1" + '\n' + "" + '\r' + "test2" +
					'\r' + "test3").Length);
				Assert.AreEqual(3,
					StringHelper.SplitMultilineText(
					"test1" + '\n' + "" + '\r' + "test2" +
					'\n' + "test3").Length);
			} // TestSplitMultilineText()

			/// <summary>
			/// Test get tab depth
			/// </summary>
			[Test]
			public void TestGetTabDepth()
			{
				Assert.AreEqual(4,
					StringHelper.GetTabDepth("\t\t\t\tWhats up?"));
			} // TestGetTabDepth()

			/// <summary>
			/// Test is lowercase or uppercase letter
			/// </summary>
			[Test]
			public void TestIsLowercaseOrUppercaseLetter()
			{
				Assert.AreEqual(true, IsLowercaseLetter('u'));
				Assert.AreEqual(true, IsUppercaseLetter('U'));
				// Well, numbers don't care if they upper or lower letters.
				Assert.AreEqual(true, IsLowercaseLetter('3'));
				Assert.AreEqual(true, IsUppercaseLetter('3'));
			} // TestIsLowercaseOrUppercaseLetter()

			/// <summary>
			/// Test split function name to word string
			/// </summary>
			[Test]
			public void TestSplitFunctionNameToWordString()
			{
				Assert.AreEqual(
					"Make damage on unit",
					SplitFunctionNameToWordString("MakeDamageOnUnit"));
				Assert.AreEqual(
					"This is a test",
					SplitFunctionNameToWordString("ThisIsATest"));
				Assert.AreEqual(
					"Create TCP listener",
					SplitFunctionNameToWordString("CreateTCPListener"));
				Assert.AreEqual(
					"Lower letter test",
					SplitFunctionNameToWordString("lowerLetterTest"));
				Assert.AreEqual(
					"Test 3D render function",
					SplitFunctionNameToWordString("Test3DRenderFunction"));
				Assert.AreEqual(
					"Check if CD is inserted",
					SplitFunctionNameToWordString("CheckIfCDIsInserted"));

				// Test with text that already got spaces (we expect same stuff
				// returned)
				Assert.AreEqual(
					"Ha ha, I have spaces already",
					SplitFunctionNameToWordString("ha ha, I have spaces already"));
				// Test mixed text
				Assert.AreEqual(
					"Hey what means What the hell?",
					SplitFunctionNameToWordString("Hey what means WhatTheHell?"));

				// Test some crazy strings to test if we get no exceptions
				SplitFunctionNameToWordString("\0\t\n\raoeu\b\t");
				SplitFunctionNameToWordString("@#%#@$<RRROEU<Y$G SINDTBRPI\"'45453'");
			} // TestSplitFunctionNameToWordString()

		#region Test get extension
			/// <summary>
			/// Test the GetExtension method.
			/// </summary>
			[Test]
			public void TestGetExtension()
			{
				Assert.AreEqual("bmp",
					StringHelper.GetExtension("SomeBitmap.bmp"));
				Assert.AreEqual("xml",
					StringHelper.GetExtension("SomeApplication.config.xml"));
			} // TestGetExtension()
			#endregion
		} // class StringHelperTests
#endif
		#endregion
	} // class StringHelper
} // namespace XnaGraphicEngineContentProcessors.Helpers