
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using System.Text.RegularExpressions;
using CommandLine;
using CommandLine.Text;
namespace Shallow
{
	class Shallow
	{

		class Options
		{

			[Option('l', "ListFile", HelpText = "file containg a list of files")]
			public string ListFile { get; set; }

			[Option('i', "inputFile", HelpText = "Input file.")]
			public string InputFile { get; set; }

			[Option('o',"outputFile", HelpText = "Output file.")]
			public string OutputFile{ get; set; }

			[Option("IndentSpacesPerTab", DefaultValue=4)]
			public int IndentSpacesPerTab { get; set; }

			[Option("DontUseTabs")]
			public bool DontUseTabs  { get; set; }

			[Option("DontForceLowerCaseKeywords")]
			public bool DontForceLowerCaseKeywords { get; set; }


			[Option("SpacesBeforeParameters",DefaultValue = 8)]
			public int SpacesBeforeParameters { get; set; }

			[Option("MinimumIndent",DefaultValue = 1)]
			public int MinimumIndent { get; set; }

			[Option("IndentRequireOneSpaceAfterLabel", DefaultValue = false)]
			public bool IndentRequireOneSpaceAfterLabel { get; set; }

            [Option("DontSaveChecksum", HelpText = "Don't save a checksum at the end of each file to speed up subsequent passes on unmodified files.")]
            public bool DontSaveChecksum { get; set; }

            // omitting long name, default --verbose
			[Option('v', "Verbose",DefaultValue = false,
			  HelpText = "Prints all messages to standard output.")]
			public bool Verbose { get; set; }

			[Option("DontInsertNewlineAfterLabels")]
			public bool DontInsertNewlineAfterLabels { get; set; }

			[Option("MultiThreaded")]
			public bool MultiThreaded { get; set; }

			[Option("DontInsertNewlineAfterLocalLabels")]
			public bool DontInsertNewlineAfterLocalLabels { get; set; }
	
			[Option("DontAddSizeToOpcodesAndKeyword")]
			public bool DontAddSizeToOpcodesAndKeyword { get; set; }

			[Option("DontAddColonAfterLabel")]
			public bool DontAddColonAfterLabel { get; set; }
			[Option("DontAddColonAfterLocalLabel")]
			public bool DontAddColonAfterLocalLabel { get; set; }

			[Option("DontRemoveColonAfterLabel")]
			public bool DontRemoveColonAfterLabel { get; set; }
			
            [Option("DontRemoveColonAfterLocalLabel")]
			public bool DontRemoveColonAfterLocalLabel { get; set; }

	
			
			[Option('d', "AllowDangerousOperations",
				HelpText = "Allows certain operations that may break code (Specifically: Newline after label or local label may break code or data is auto aligned. If you use an assembler like vasm, it will warn if there is any auto alignment happening, so fix up all of those issues before using this function")]
			public bool AllowDangerousOperations{ get; set; }

			[ParserState]
			public IParserState LastParserState { get; set; }

			[HelpOption]
			public string GetUsage()
			{
				return HelpText.AutoBuild(this,
				  (HelpText current) => HelpText.DefaultParsingErrorsHandler(this, current));
			}
		}


		static RegexOptions regexOptions = RegexOptions.IgnorePatternWhitespace;
		static string commentStarString=@"^\* .* $";
        static string commentSemicolonString = @"; .* $";
        static string stringString = @" "" (?: [^""\r\n\\] | \\.)* "" " + "|" + @"' (?: [^'\r\n\\] | \\.)* '";

        static Regex regexComment = new Regex(commentSemicolonString + "  |  " + commentStarString, regexOptions);
        static Regex regexString = new Regex(stringString, regexOptions);

		static Regex regexWhiteSpaceAtBeginningOfLine = new Regex(@"^\s+", regexOptions);

		static Regex regexBeginIndent = new Regex(@"(?<= (?: \s | \:))(?i)\b(?:else|elseif|macro|rept|IF|IF1|IF2|IFB|IFC|IFD|IFEQ|IFGE|IFGT|IFLE|IFLT|IFNB|IFNC|IFND|IFNE)\b", regexOptions);
		static Regex regexEndIndent = new Regex(@"(?<= (?: \s | \:))(?i)\b(?:else|elseif|endif|endc|endm|endr)\b", regexOptions);

		static Regex regexOpcodes = new Regex(@"(?<= (?: \s | \:))(?i)\b(?:abcd|add|adda|addi|addq|addx|and|andi|asl|asr|bcc|bcs|beq|bge|bgt|bhi|bhs|ble|blk|blo|bls|blt|bmi|bne|bpl|bvc|bvs|bf|bt|bchg|bclr|bra|bset|bsr|btst|bkpt|chk|clr|cmp|cmpa|cmpi|cmpm|dbcc|dbcs|dbeq|dbge|dbgt|dbhi|dbhs|dble|dblk|dblo|dbls|dblt|dbmi|dbne|dbpl|dbvc|dbvs|dbf|dbt|dbra|divs|divsl|divu|divul|eor|eori|exg|ext|illegal|jmp|jsr|lea|link|lsl|lsr|move|movea|movec|movem|movep|moveq|muls|mulu|nbcd|neg|negx|nop|not|or|ori|pea|reset|rol|ror|roxl|roxr|rte|rtr|rts|sbcd|scc|scs|seq|sge|sgt|shi|sle|sls|slt|smi|sne|spl|svc|svs|sf|st|stop|sub|suba|subi|subq|subx|swap|tas|trap|trapv|tst|unlk)\b\b(?:.b|.s|.w|.l|(?!\.))\b", regexOptions);
		static Regex regexDataKeywords = new Regex(@"(?<= (?: \s | \:))(?i)\b(?:dc|dcb|ds|blk)\b\b(?:.b|.s|.w|.l|(?!\.))\b", regexOptions);
		static string groupAlignKeywordsString = @"(?<= (?: \s | \:))(?i)\b(?:rs|rsreset|rsset|so|setso|clrso|equ|equr|set)\b\b(?:.b|.s|.w|.l|(?!\.))\b";
		static string nonGroupAlignKeywordsString = @"(?<= (?: \s | \:))(?i)\b(?:reg|xref|xdef|cnop|align|even|odd|opt)\b\b(?:.b|.s|.w|.l|(?!\.))\b";
		static Regex regexOtherKeywords = new Regex(groupAlignKeywordsString + "|" + nonGroupAlignKeywordsString, regexOptions);
		static Regex regexSizeKeywords = new Regex(@"(?<!^) (?i) \b(?: .b | .s | .w | .l )\b", regexOptions);
		static Regex regexRegisters = new Regex(@"(?<!^) (?i) \b(?: d[0-7] | a[0-7] | USP | SSP | PC | sp )\b \b(?: .b | .s | .w | .l | (?!\.))\b", regexOptions);
		static Regex regexPreprocessor = new Regex(@"(?<= (?: \s | \:)) (?i) \b(?:  incdir | incbin | include | section | fail | org | rorg | offset | print | printt | printv | code | code_c | data | data_c | bss | bss_c | machine )\b", regexOptions);
		static Regex regexNonWhitespaceCharacter = new Regex(@"\S", regexOptions);
		static Regex regexAlwaysRemoveColonForThese = new Regex(@"\=", regexOptions);


		static Regex regexKeywordsMissingSize = new Regex(@"(?<= (?: \s | \:))(?i)\b(?:add|adda|addi|addq|addx|and|andi|asl|asr|clr|cmp|cmpa|cmpi|cmpm|eor|eori|ext|lsl|lsr|move|movea|movem|movep|neg|negx|not|or|ori|rol|ror|roxl|roxr|sub|suba|subi|subq|subx|tst|rs|so|dc|dcb|ds|blk)\b\b(?!\.)\b", regexOptions);

	
		static string sizeString = ".w";
		static string regexStringLabel = @"^(?:  \\@[0-9a-zA-Z_\\]+  |  [a-zA-Z_\\] [0-9a-zA-Z_\\@]*  )   (?:  \:  |  $  |  (?= \W)  )";
		static string regexStringLocalLabel = @"^(?:  \.\\@[0-9a-zA-Z_\\]+  |  \.[0-9a-zA-Z_\\@]+  )   (?:  \:  |  $  |  (?= \W)  )";

		static Regex regexLabelAtBeginningOfLine = new Regex(regexStringLabel, regexOptions);
		static Regex regexLocalLabelAtBeginningOfLine = new Regex(regexStringLocalLabel, regexOptions);

		static Regex regexLabelAndLocalLabel = new Regex(regexStringLabel + " | " + regexStringLocalLabel, regexOptions);

		//equal keyword only if directly after a label and optional whitespace
		static string regexStringLookaheadForLabel = @"(?<= (?:" + regexStringLabel + @" | " + regexStringLocalLabel + @") \s*)";
		static string equalKeywordString=regexStringLookaheadForLabel+@"\=";
		static Regex regexEqualKeyword = new Regex(equalKeywordString, regexOptions);
		static Regex regexGroupAlignKeywords = new Regex( groupAlignKeywordsString, regexOptions);
		static Regex regexGroupAlignParameterKeywords = new Regex(equalKeywordString + " | " + groupAlignKeywordsString, regexOptions);
		static Regex regexGroupSeparator = new Regex(commentStarString, regexOptions);

		static int Main(string[] args)
		{

			var options = new Options();
			if (CommandLine.Parser.Default.ParseArguments(args, options))
			{
				if (options.Verbose)
				{
					Console.WriteLine("Shallow");
					Debug.WriteLine("Shallow");
				}
				if (options.ListFile != null)
				{
					string[] fileNameArray = File.ReadAllLines(options.ListFile);
					//sort to process largest files first (as they take more time) - to get a little more use of each thread.
					var sortedFileNames = from fileName in fileNameArray
											   orderby new FileInfo(fileName).Length descending
											   select fileName;

					if(!options.MultiThreaded)
					{
						foreach(string fileName in fileNameArray)
						{
                            ProcessSingleFile(options, args, fileName, fileName);
						}
					}
					else
					{
						Parallel.ForEach(sortedFileNames, fileName => 
						{
                            ProcessSingleFile(options, args, fileName, fileName);
						});
					}
					return 0;
				}
				else if (options.InputFile != null)
				{
					string inputFile = options.InputFile;
					string outputFile = options.OutputFile;
					if (outputFile == null)
						outputFile = inputFile;

                    ProcessSingleFile(options, args, inputFile, outputFile);
					return 0;
				}
				else
				{
					Console.WriteLine("Error: Missing input filename or list filename");
					Debug.WriteLine("Error: Missing input filename or list filename");
					return 1;
				}
			}
			return 1;

		}
        static string CalcChecksum(List<string> lines,string[] args)
        {
            string allLines="";
            foreach(string line in lines)
            {
                allLines += line;
            }
            foreach (string arg in args)
            {
                allLines += arg;
            }

            string versionString = Assembly.GetExecutingAssembly().GetName().Version.ToString();
#if !DEBUG
            //if we are using release-mode, then every recompile should trigger a new hash, but in debug-mode that would just slow down development
            allLines += versionString;
#endif
            System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] allLinesAsBytes = System.Text.Encoding.ASCII.GetBytes(allLines);
            byte[] hash=md5.ComputeHash(allLinesAsBytes);
            string result = "";

            foreach(byte hashChar in hash)
            {
                result += hashChar;
            }
            return result;
        }

		private static void ProcessSingleFile(Options options, string[] args, string inputFile, string outputFile)
		{
			string currentDir = Directory.GetCurrentDirectory();
			string inputPath = Path.Combine(currentDir, inputFile);
			string outputPath = Path.Combine(currentDir, outputFile);

			if (options.Verbose)
			{
				Console.WriteLine("Reading from " + inputPath);
				Debug.WriteLine("Reading from " + inputPath);
			}
			string[] lineArray = File.ReadAllLines(inputFile);
			List<string> lines = new List<string>();
			lines.AddRange(lineArray);

            string hashString=";Shallow Hash=";
            if (lines.Count > 0 && lines[lines.Count - 1].Length>=hashString.Length && lines[lines.Count - 1].Substring(0, hashString.Length) == hashString)
            {
                string storedHash = lines[lines.Count - 1].Substring(hashString.Length);
                lines.RemoveAt(lines.Count - 1);
                string actualHash = CalcChecksum(lines,args);
                if (storedHash == actualHash)
                {
                    if (options.Verbose)
                    {
                        Console.WriteLine("File was not modified - skipped processing");
                        Debug.WriteLine("File was not modified - skipped processing");
                    }
                    return;
                }
            }


			if (!options.DontInsertNewlineAfterLabels && options.AllowDangerousOperations)
			{
				InsertNewlineAfterLabels(options, lines, regexComment, regexString, regexOpcodes, regexLabelAtBeginningOfLine);
                InsertNewlineAfterLabels(options, lines, regexComment, regexString, regexDataKeywords, regexLabelAtBeginningOfLine);
			}
            if (!options.DontInsertNewlineAfterLocalLabels && options.AllowDangerousOperations)
			{
                InsertNewlineAfterLabels(options, lines, regexComment, regexString, regexOpcodes, regexLocalLabelAtBeginningOfLine);
                InsertNewlineAfterLabels(options, lines, regexComment, regexString, regexDataKeywords, regexLocalLabelAtBeginningOfLine);
			}
            if (!options.DontAddSizeToOpcodesAndKeyword)
			{
                AddSizeToOpcodesAndKeyword(options, lines, regexComment, regexString, regexKeywordsMissingSize, sizeString);
			}

            if (!options.DontAddColonAfterLabel || !options.DontRemoveColonAfterLabel)
			{
                SetLabelEndingCharacter(lines, regexComment, regexString, regexLabelAtBeginningOfLine, !options.DontAddColonAfterLabel, regexAlwaysRemoveColonForThese);
			}
            if (!options.DontAddColonAfterLocalLabel || !options.DontRemoveColonAfterLocalLabel)
			{
                SetLabelEndingCharacter(lines, regexComment, regexString, regexLocalLabelAtBeginningOfLine, !options.DontAddColonAfterLocalLabel, regexAlwaysRemoveColonForThese);
			}
			if (!options.DontForceLowerCaseKeywords)
			{
                LowerCaseKeywords(options, lines, regexComment, regexString, regexBeginIndent);
                LowerCaseKeywords(options, lines, regexComment, regexString, regexEndIndent);
                LowerCaseKeywords(options, lines, regexComment, regexString, regexOpcodes);
                LowerCaseKeywords(options, lines, regexComment, regexString, regexDataKeywords);
                LowerCaseKeywords(options, lines, regexComment, regexString, regexOtherKeywords);
                LowerCaseKeywords(options, lines, regexComment, regexString, regexSizeKeywords);
                LowerCaseKeywords(options, lines, regexComment, regexString, regexRegisters);
                LowerCaseKeywords(options, lines, regexComment, regexString, regexPreprocessor);
			}

            ReIndent(options, lines, regexComment, regexString, regexWhiteSpaceAtBeginningOfLine, regexBeginIndent, regexEndIndent, regexLabelAndLocalLabel, regexEqualKeyword);

			if (options.SpacesBeforeParameters > 0)
			{
                AlignParameters(options, lines, regexComment, regexString, regexOpcodes, regexNonWhitespaceCharacter);
                AlignParameters(options, lines, regexComment, regexString, regexDataKeywords, regexNonWhitespaceCharacter);

                AlignGroupedKeywords(options, lines, regexComment, regexString, regexGroupSeparator, regexGroupAlignKeywords);
			}

            AlignGroupedParameters(options, lines, regexComment, regexString, regexGroupSeparator, regexGroupAlignParameterKeywords, regexNonWhitespaceCharacter);
            AlignComments(options, lines, regexComment, regexGroupSeparator, regexString);


            if (!options.DontSaveChecksum)
            {
                lines.Add(hashString + CalcChecksum(lines, args));
            }

            List<string> originalLines = new List<string>();
			originalLines.AddRange(lineArray);
			if (!originalLines.SequenceEqual(lines) || inputFile != outputFile)
			{
				for (int i = 0; i < originalLines.Count && i < lines.Count;i++)
				{
					if (originalLines[i] != lines[i])
					{
//						int test = 42;
					}
				}
                if (options.Verbose)
				{
					Console.WriteLine("Writing to " + outputPath);
					Debug.WriteLine("Writing to " + outputPath);
				}
				File.WriteAllLines(outputFile, lines);
			}
			else
			{
				if (options.Verbose)
				{
					Console.WriteLine("File unchanged by process - skipped saving");
                    Debug.WriteLine("File unchanged by process - skipped saving");
				}
			}
		}
		private static void LowerCaseKeywords(Options options, List<string> lines, Regex regexComment, Regex regexString, Regex regexKeywords)
		{
			for (int i = 0; i < lines.Count(); i++)
			{
				string lineWithoutComments = RemoveCommentsAndReplaceStrings(lines[i], regexComment,regexString);
				MatchCollection matches = regexKeywords.Matches(lineWithoutComments);
				foreach (Match match in matches)
				{
					lines[i] = lines[i].Substring(0, match.Index) +
						lines[i].Substring(match.Index, match.Length).ToLower() +
						lines[i].Substring(match.Index + match.Length);
				}
			}
		}
		static string ReplaceTabsWithSpaces(string input, int spacesPerTab)
		{
			while(input.IndexOf('	')>=0)
			{
				int index=input.IndexOf('	');
				input = input.Substring(0, index) + new string(' ', spacesPerTab - (index % spacesPerTab)) + input.Substring(index + 1);
			}
			return input;
		}

		private static void AlignGroupedKeywords(Options options, List<string> lines, Regex regexComment, Regex regexString, Regex regexGroupSeparator, Regex regexKeywords)
		{
			bool hasGroupAlignment = false;
			int groupAlignment = 0;
			for (int i = 0; i < lines.Count(); i++)
			{
				string lineWithoutComments = RemoveCommentsAndReplaceStrings(lines[i], regexComment,regexString);
				if (regexGroupSeparator.IsMatch(lines[i]))
				{
					hasGroupAlignment = false;
					continue;
				}
				if (!hasGroupAlignment)
				{
					//we are at the start of the new group - find largest alignment needed in the group.
					hasGroupAlignment = true;
					groupAlignment = 0;
					for (int j = i + 1; j < lines.Count(); j++)
					{
						//check for end of group
						string groupLineWithoutComments = RemoveCommentsAndReplaceStrings(lines[j], regexComment,regexString);
						groupLineWithoutComments = ReplaceTabsWithSpaces(groupLineWithoutComments, options.IndentSpacesPerTab);
						if (regexGroupSeparator.IsMatch(lines[j]))
							break;
						if (regexKeywords.IsMatch(groupLineWithoutComments))
						{
							Match match = regexKeywords.Match(groupLineWithoutComments);
							int thisLineRequiredAlignment = match.Index;

							if (thisLineRequiredAlignment > groupAlignment)
							{
								groupAlignment = thisLineRequiredAlignment;
							}
						}
					}
                    if (!options.DontUseTabs)
					{
						groupAlignment = options.IndentSpacesPerTab * ((groupAlignment + (options.IndentSpacesPerTab - 1)) / options.IndentSpacesPerTab);
					}
				}
				if (regexKeywords.IsMatch(lineWithoutComments))
				{
					string lineWithoutCommentsAndTabs = ReplaceTabsWithSpaces(lineWithoutComments, options.IndentSpacesPerTab);
					if (regexKeywords.IsMatch(lineWithoutCommentsAndTabs))
					{

//						Match matchNoTabs = regexKeywords.Match(lineWithoutCommentsAndTabs);
						Match match = regexKeywords.Match(lineWithoutComments);


						//replace spaces here
						int keywordStartIndex = match.Index;
						int whitespaceStartIndex = keywordStartIndex;
						while (whitespaceStartIndex > 0 && Char.IsWhiteSpace(lineWithoutComments[whitespaceStartIndex - 1]))
						{
							whitespaceStartIndex--;
						}
						//remove old whitespace
						lines[i] = lines[i].Remove(whitespaceStartIndex, keywordStartIndex-whitespaceStartIndex);

						//add new whitespace
						int spaceNeededBeforeKeyword = groupAlignment - whitespaceStartIndex;
                        char indentCharacter = !options.DontUseTabs ? '\t' : ' ';
						int numIndents = spaceNeededBeforeKeyword;
                        if (!options.DontUseTabs)
							numIndents = (numIndents + (options.IndentSpacesPerTab - 1)) / options.IndentSpacesPerTab;

						string whitespaceString = " ";
						if (numIndents > 0)
						{
							whitespaceString = new string(indentCharacter, numIndents);
						}
						lines[i] = lines[i].Insert(whitespaceStartIndex, whitespaceString);
					}
				}
			}
		}
        private static void AlignGroupedParameters(Options options, List<string> lines, Regex regexComment, Regex regexString, Regex regexGroupSeparator, Regex regexKeywords, Regex regexNonWhitespaceCharacter)
		{
			bool hasGroupAlignment=false;
			int groupAlignment=0;
			for (int i = 0; i < lines.Count(); i++)
			{
				string lineWithoutComments = RemoveCommentsAndReplaceStrings(lines[i], regexComment,regexString);
				if (regexGroupSeparator.IsMatch(lines[i]))
				{
					hasGroupAlignment=false;
					continue;
				}
				if(!hasGroupAlignment)
				{
					//we are at the start of the new group - find largest alignment needed in the group.
					hasGroupAlignment=true;
					groupAlignment=0;
					for (int j = i+1; j < lines.Count(); j++)
					{
						//check for end of group
						string groupLineWithoutComments = RemoveCommentsAndReplaceStrings(lines[j], regexComment,regexString);
						groupLineWithoutComments = ReplaceTabsWithSpaces(groupLineWithoutComments, options.IndentSpacesPerTab);
						if (regexGroupSeparator.IsMatch(lines[j]))
							break;
						if(regexKeywords.IsMatch(groupLineWithoutComments))
						{
							Match match=regexKeywords.Match(groupLineWithoutComments);
							int thisLineRequiredAlignment=match.Index+match.Length+1;
							if(thisLineRequiredAlignment>groupAlignment)
							{
								groupAlignment=thisLineRequiredAlignment;
							}
						}
					}
                    if (!options.DontUseTabs)
					{
						groupAlignment = options.IndentSpacesPerTab* ((groupAlignment + (options.IndentSpacesPerTab - 1)) / options.IndentSpacesPerTab);
					}
				}
				if (regexKeywords.IsMatch(lineWithoutComments))
				{
					string lineWithoutCommentsAndTabs = ReplaceTabsWithSpaces(lineWithoutComments,options.IndentSpacesPerTab);
					if (regexKeywords.IsMatch(lineWithoutCommentsAndTabs))
					{
						Match matchNoTabs = regexKeywords.Match(lineWithoutCommentsAndTabs);
						int spaceNeededAfterKeyword=groupAlignment-(matchNoTabs.Index+matchNoTabs.Length);
						Match match = regexKeywords.Match(lineWithoutComments);


						//only do the realignment if there is a non-whitespace character that needs to be aligned
						if (regexNonWhitespaceCharacter.IsMatch(lineWithoutComments,match.Index+match.Length))
						{
							Match matchNonWhitespace = regexNonWhitespaceCharacter.Match(lineWithoutComments,match.Index+match.Length);


							//remove old whitespace
							int keywordEndIndex = match.Index + match.Length;
							lines[i] = lines[i].Remove(keywordEndIndex, matchNonWhitespace.Index - keywordEndIndex);

							//add new whitespace
                            char indentCharacter = !options.DontUseTabs ? '\t' : ' ';
							int numIndents = spaceNeededAfterKeyword;
                            if (!options.DontUseTabs)
								numIndents = (numIndents + (options.IndentSpacesPerTab-1 )) / options.IndentSpacesPerTab;

							string whitespaceString = " ";
							if (numIndents > 0)
							{
								whitespaceString=new string(indentCharacter, numIndents);
							}
							lines[i] = lines[i].Insert(keywordEndIndex, whitespaceString);
						}
					}
				}
			}
		}

		private static void AlignParameters(Options options, List<string> lines, Regex regexComment, Regex regexString, Regex regexKeywords, Regex regexNonWhitespaceCharacter)
		{
			for (int i = 0; i < lines.Count(); i++)
			{
				string lineWithoutComments = RemoveCommentsAndReplaceStrings(lines[i], regexComment,regexString);
				if (regexKeywords.IsMatch(lineWithoutComments))
				{
					Match match = regexKeywords.Match(lineWithoutComments);

					//only do the realignment if there is a non-whitespace character that needs to be aligned
					if (regexNonWhitespaceCharacter.IsMatch(lineWithoutComments,match.Index+match.Length))
					{
						//remove old whitespace
						Match matchNonWhitespace = regexNonWhitespaceCharacter.Match(lineWithoutComments,match.Index+match.Length);
						int keywordEndIndex = match.Index + match.Length;
						lines[i] = lines[i].Remove(keywordEndIndex, matchNonWhitespace.Index - keywordEndIndex);

						//add new whitespace
                        char indentCharacter = !options.DontUseTabs ? '\t' : ' ';
						int numIndents=options.SpacesBeforeParameters-match.Length;
                        if (!options.DontUseTabs)
							numIndents=(numIndents+(options.IndentSpacesPerTab-1))/options.IndentSpacesPerTab;

						string whitespaceString = " ";

						if (numIndents > 0)
						{
							whitespaceString=new string(indentCharacter, numIndents);
						}
						lines[i] = lines[i].Insert(keywordEndIndex, whitespaceString);
					}
				}
			}
		}

        private static void AlignComments(Options options, List<string> lines, Regex regexComment, Regex regexGroupSeparator, Regex regexString)
		{
			bool hasGroupAlignment=false;
			int groupAlignment=0;
			for (int i = 0; i < lines.Count(); i++)
			{
				string lineWithoutStrings = ReplaceStringWithX(lines[i], regexString);
				if (regexGroupSeparator.IsMatch(lines[i]))
				{
					hasGroupAlignment=false;
					continue;
				}
				if(!hasGroupAlignment)
				{
					//we are at the start of the new group - find largest alignment needed in the group.
					hasGroupAlignment=true;
					groupAlignment=0;
					for (int j = i+1; j < lines.Count(); j++)
					{
						//check for end of group
						string groupLineWithoutStrings= ReplaceStringWithX(lines[j], regexString);
						groupLineWithoutStrings = ReplaceTabsWithSpaces(groupLineWithoutStrings, options.IndentSpacesPerTab);
						if (regexGroupSeparator.IsMatch(lines[j]))
							break;
						if(regexComment.IsMatch(groupLineWithoutStrings))
						{
							Match match=regexComment.Match(groupLineWithoutStrings);
                            if(match.Index>0)
                            {
							    int thisLineRequiredAlignment=match.Index;
							    if(thisLineRequiredAlignment>groupAlignment)
							    {
								    groupAlignment=thisLineRequiredAlignment;
							    }
                            }
						}
					}
                    if (!options.DontUseTabs)
					{
						groupAlignment = options.IndentSpacesPerTab* ((groupAlignment + (options.IndentSpacesPerTab - 1)) / options.IndentSpacesPerTab);
					}
				}
				if (regexComment.IsMatch(lineWithoutStrings))
				{
					string lineWithoutStringsAndTabs = ReplaceTabsWithSpaces(lineWithoutStrings,options.IndentSpacesPerTab);
					if (regexComment.IsMatch(lineWithoutStringsAndTabs))
					{
						Match matchNoTabs = regexComment.Match(lineWithoutStringsAndTabs);
                        if(matchNoTabs.Index>0)
                        {

                            int whitespaceStartIndexNoTabs = matchNoTabs.Index;
                            while (whitespaceStartIndexNoTabs > 0 && Char.IsWhiteSpace(lineWithoutStringsAndTabs[whitespaceStartIndexNoTabs - 1]))
                            {
                                whitespaceStartIndexNoTabs--;
                            }
                            int spaceNeededBeforeComment = groupAlignment - (whitespaceStartIndexNoTabs);

                            Match match = regexComment.Match(lineWithoutStrings);
                            //replace spaces here
                            int commentStartIndex = match.Index;
                            int whitespaceStartIndex = commentStartIndex;
                            while (whitespaceStartIndex > 0 && Char.IsWhiteSpace(lineWithoutStrings[whitespaceStartIndex - 1]))
                            {
                                whitespaceStartIndex--;
                            }
                            //remove old whitespace
                            lines[i] = lines[i].Remove(whitespaceStartIndex, commentStartIndex - whitespaceStartIndex);

							//add new whitespace
                            char indentCharacter = !options.DontUseTabs ? '\t' : ' ';
							int numIndents = spaceNeededBeforeComment;
                            if (!options.DontUseTabs)
								numIndents = (numIndents + (options.IndentSpacesPerTab-1 )) / options.IndentSpacesPerTab;

							string whitespaceString = " ";
							if (numIndents > 0)
							{
								whitespaceString=new string(indentCharacter, numIndents);
							}
							lines[i] = lines[i].Insert(whitespaceStartIndex, whitespaceString);
                        }
					}
				}
			}
		}

		private static void ReIndent(Options options, List<string> lines, Regex regexComment, Regex regexString, Regex regexWhiteSpaceAtBeginningOfLine, Regex regexBeginIndent, Regex regexEndIndent, Regex regexLabel, Regex regexEqualKeyword)
		{
			//re-indent based on conditionals
			int indent = options.MinimumIndent;
			for (int i = 0; i < lines.Count(); i++)
			{
				string lineWithoutComments = RemoveCommentsAndReplaceStrings(lines[i], regexComment,regexString);

				if (regexEndIndent.IsMatch(lineWithoutComments))
				{
					if (indent > options.MinimumIndent)
					{
						indent--;
					}
				}
                char indentCharacter = !options.DontUseTabs ? '\t' : ' ';
				int indentMultiplier = options.IndentSpacesPerTab;


				int labelLength = 0;
				if (regexLabel.IsMatch(lines[i]))
				{
					Match labelMatch = regexLabel.Match(lines[i]);
					//if there is a label then indent after label ends
					labelLength = labelMatch.Index + labelMatch.Length;
				}

				int numSpaces=indent*indentMultiplier-labelLength;

				int numIndents;
                if (!options.DontUseTabs)
					numIndents=(numSpaces+(options.IndentSpacesPerTab-1))/options.IndentSpacesPerTab;
				else
					numIndents=numSpaces;

				if(regexEqualKeyword.IsMatch(lines[i]))
				{
					//always remove space before equal-signs
					numIndents=0;
				}


				string lineAfterLabel = lines[i].Substring(labelLength);
				bool hadWhiteSpace = false;
				if (regexWhiteSpaceAtBeginningOfLine.IsMatch(lineAfterLabel))
				{
					//there's whitespace to replace
					lineAfterLabel = regexWhiteSpaceAtBeginningOfLine.Replace(lineAfterLabel, "");
					hadWhiteSpace = true;
				}

				string whitespaceString = " ";
				if (!options.IndentRequireOneSpaceAfterLabel)
				{
					if(labelLength>0 && lines[i][labelLength-1]==':')
					{
						//label ends with colon - whitespace not required
						whitespaceString = "";
					}
					else
					{
						Regex regexStartsWithAlphaNum = new Regex(@"^[0-9a-zA-Z_\\]");
						if(!regexStartsWithAlphaNum.IsMatch(lineAfterLabel))
						{
							//first keyword starts with a non-alphanumeric character so whitespace not required
							whitespaceString = "";
						}
					}
				}
				if (numIndents > 0)
				{
					whitespaceString=new string(indentCharacter, numIndents);
				}
				

				if(labelLength>0 || hadWhiteSpace)
				{
					//add whitespaces except in the case when there is something other than a label (i.e. a comment) at the start of the line
					lineAfterLabel=whitespaceString+lineAfterLabel;
				}

				lines[i] = lines[i].Substring(0, labelLength)+lineAfterLabel;






				if (regexBeginIndent.IsMatch(lineWithoutComments))
				{
					indent++;
				}

			}
		}
        private static string RemoveCommentsAndReplaceStrings(string line, Regex regexComment, Regex regexString)
        {
            return RemoveComments(ReplaceStringWithX(line, regexString),regexComment);
        }

        private static string RemoveComments(string line, Regex regexComment)
        {
            return regexComment.Replace(line, string.Empty);
        }

        private static string ReplaceStringWithX(string line, Regex regexString)
        {
            MatchCollection matches = regexString.Matches(line);
            foreach (Match match in matches)
            {
                line = line.Remove(match.Index, match.Length);
                line = line.Insert(match.Index, new string('x', match.Length));
            }
            return line;
        }

		private static void AddSizeToOpcodesAndKeyword(Options options, List<string> lines, Regex regexComment, Regex regexString, Regex regexKeywordsMissingSize, string sizeString)
		{
			for (int i = 0; i < lines.Count(); i++)
			{
				string lineWithoutComments = RemoveCommentsAndReplaceStrings(lines[i], regexComment,regexString);
				if (regexKeywordsMissingSize.IsMatch(lineWithoutComments))
				{
					Match splitPoint = regexKeywordsMissingSize.Match(lines[i]);
					lines[i] = lines[i].Substring(0, splitPoint.Index + splitPoint.Length)+sizeString+lines[i].Substring(splitPoint.Index + splitPoint.Length);
					if (options.Verbose)
					{
						Console.WriteLine("added size to keyword on line " + (i+1) + ": " + lines[i]);
						Debug.WriteLine("added size to keyword on line " + (i+1) + ": " + lines[i]);
					}
				}
			}
		}
		private static void SetLabelEndingCharacter(List<string> lines, Regex regexComment, Regex regexString, Regex regexLabel, bool addColon, Regex regexAlwaysRemoveColonForThese)
		{
			for (int i = 0; i < lines.Count(); i++)
			{
				string lineWithoutComments = RemoveCommentsAndReplaceStrings(lines[i], regexComment,regexString);
				bool addColonforLine = addColon;
				if (regexAlwaysRemoveColonForThese.IsMatch(lineWithoutComments))
					addColonforLine = false;

				if (regexLabel.IsMatch(lineWithoutComments))
				{
					Match labelMatch = regexLabel.Match(lines[i]);
					if(labelMatch.Length>0 && lines[i][labelMatch.Length-1]==':')
					{
						if(!addColonforLine)
						{
							//label had a colon, but user requested colons removed
							lines[i] = lines[i].Remove(labelMatch.Length-1,1);
						}
					}
					else
					{ 
						if(addColonforLine)
						{
							//label was missing a colon, but user requested colons added
							lines[i] = lines[i].Insert(labelMatch.Length, ":");
						}
					}
				}
			}
		}

		private static void InsertNewlineAfterLabels(Options options, List<string> lines, Regex regexComment, Regex regexString, Regex regexKeywords, Regex regexLabel)
		{
			for (int i = 0; i < lines.Count(); i++)
			{
				string lineWithoutComments = RemoveCommentsAndReplaceStrings(lines[i], regexComment,regexString);
				if (regexKeywords.IsMatch(lineWithoutComments))
				{
					if (regexLabel.IsMatch(lineWithoutComments))
					{
						if (options.Verbose)
						{
							Console.WriteLine("Inserting Newline in line " + (i+1) + ": " + lines[i]);
							Debug.WriteLine("Inserting Newline in line " + (i + 1) + ": " + lines[i]);
						}
						Match splitPoint = regexLabel.Match(lines[i]);
						//insert newline after the label end
						lines.Insert(i + 1, " " + lines[i].Substring(splitPoint.Index + splitPoint.Length));
						lines[i] = lines[i].Substring(0, splitPoint.Index + splitPoint.Length);
						//skip this newly inserted line
						i++;
					}
				}
			}
		}
	}
}
