namespace CodeGen
{
	using System;
	using System.Collections;
	using System.IO;
	using System.Text;
	using System.Collections.Specialized;
	using System.Windows.Forms;
	using Microsoft.Office.Core;
	using Extensibility;
	using System.Runtime.InteropServices;
	using EnvDTE;
	using Microsoft.VisualStudio.VCCodeModel;

	class StreamUpdateWriter: StringWriter
	{
		public StreamUpdateWriter(string sFileName)
		{
			m_sFileName = sFileName;
		}

		public override void Close()
		{
			CloseReplace();
		}

		public string CloseReplace()
		{
			if (m_bClosed)
				return "";
			m_bClosed = true;

			base.Close();

			string newContents = ToString();

			// Compare with existing file.
			try
			{
				StreamReader oldReader = new StreamReader(m_sFileName);
				string oldContents = oldReader.ReadToEnd();
				oldReader.Close();

				oldContents = oldContents.Replace("\r", "");
				if (oldContents == newContents)
					// No update.
					return "";

				// Try to overwrite existing file.
				string result;
				if (!File.Exists(m_sFileName))
					result = "New file: ";
				else
					result = "Updated file: ";
				StreamWriter newWriter = null;
				try
				{
					newWriter = new StreamWriter(m_sFileName);
				}
				catch (System.Exception)
				{
					// If can't write to current file, write a new version.
					int pos = m_sFileName.LastIndexOf('.');
					m_sFileName = m_sFileName.Substring(0, pos) + "_new" + m_sFileName.Substring(pos);
					try
					{
						newWriter = new StreamWriter(m_sFileName);
						result = "Temp updated file: ";
					}
					catch (System.Exception)
					{
					}
				}
				if (newWriter == null)
					// Unresolvable problem.
					return "";
				newWriter.Write(newContents);
				newWriter.Close();
				return result + m_sFileName + "\n";
			}
			catch (System.Exception)
			{
			}
			return "";
		}

		private string m_sFileName;
		private bool m_bClosed;
	};

	#region Read me for Add-in installation and setup information.
	// When run, the Add-in wizard prepared the registry for the Add-in.
	// At a later time, if the Add-in becomes unavailable for reasons such as:
	//   1) You moved this project to a computer other than which is was originally created on.
	//   2) You chose 'Yes' when presented with a message asking if you wish to remove the Add-in.
	//   3) Registry corruption.
	// you will need to re-register the Add-in by building the MyAddin21Setup project 
	// by right clicking the project in the Solution Explorer, then choosing install.
	#endregion
	
	/// <summary>
	///   The object for implementing an Add-in.
	/// </summary>
	/// <seealso class='IDTExtensibility2' />
	[GuidAttribute("340081A0-3357-4EB8-B7DD-F5B5E2ABD772"), ProgId("CodeGen.Connect")]
	public class Connect : Object, Extensibility.IDTExtensibility2, IDTCommandTarget
	{
		/// <summary>
		///		Implements the constructor for the Add-in object.
		///		Place your initialization code within this method.
		/// </summary>
		public Connect()
		{
		}

		/// <summary>
		///      Implements the OnConnection method of the IDTExtensibility2 interface.
		///      Receives notification that the Add-in is being loaded.
		/// </summary>
		/// <param term='application'>
		///      Root object of the host application.
		/// </param>
		/// <param term='connectMode'>
		///      Describes how the Add-in is being loaded.
		/// </param>
		/// <param term='addInInst'>
		///      Object representing this Add-in.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
		{
			applicationObject = (_DTE)application;
			addInInstance = (AddIn)addInInst;
			if (connectMode == Extensibility.ext_ConnectMode.ext_cm_UISetup)
			{
				AddMenuItem();
			}
			
		}

		/// <summary>
		///     Implements the OnDisconnection method of the IDTExtensibility2 interface.
		///     Receives notification that the Add-in is being unloaded.
		/// </summary>
		/// <param term='disconnectMode'>
		///      Describes how the Add-in is being unloaded.
		/// </param>
		/// <param term='custom'>
		///      Array of parameters that are host application specific.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)
		{
		}

		/// <summary>
		///      Implements the OnAddInsUpdate method of the IDTExtensibility2 interface.
		///      Receives notification that the collection of Add-ins has changed.
		/// </summary>
		/// <param term='custom'>
		///      Array of parameters that are host application specific.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnAddInsUpdate(ref System.Array custom)
		{
			AddMenuItem();
		}

		/// <summary>
		///      Implements the OnStartupComplete method of the IDTExtensibility2 interface.
		///      Receives notification that the host application has completed loading.
		/// </summary>
		/// <param term='custom'>
		///      Array of parameters that are host application specific.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnStartupComplete(ref System.Array custom)
		{
		}

		/// <summary>
		///      Implements the OnBeginShutdown method of the IDTExtensibility2 interface.
		///      Receives notification that the host application is being unloaded.
		/// </summary>
		/// <param term='custom'>
		///      Array of parameters that are host application specific.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnBeginShutdown(ref System.Array custom)
		{
		}
		
		/// <summary>
		///      Implements the QueryStatus method of the IDTCommandTarget interface.
		///      This is called when the command's availability is updated
		/// </summary>
		/// <param term='commandName'>
		///		The name of the command to determine state for.
		/// </param>
		/// <param term='neededText'>
		///		Text that is needed for the command.
		/// </param>
		/// <param term='status'>
		///		The state of the command in the user interface.
		/// </param>
		/// <param term='commandText'>
		///		Text requested by the neededText parameter.
		/// </param>
		/// <seealso class='Exec' />
		public void QueryStatus(string commandName, EnvDTE.vsCommandStatusTextWanted neededText, ref EnvDTE.vsCommandStatus status, ref object commandText)
		{
			if (neededText == EnvDTE.vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
			{
				if (commandName == "CodeGen.Connect.CodeGen")
				{
					status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported|vsCommandStatus.vsCommandStatusEnabled;
				}
			}
		}

		void AddMenuItem()
		{
			object []contextGUIDS = new object[] { };
			Commands commands = applicationObject.Commands;
			_CommandBars commandBars = applicationObject.CommandBars;

			// When run, the Add-in wizard prepared the registry for the Add-in.
			// At a later time, the Add-in or its commands may become unavailable for reasons such as:
			//   1) You moved this project to a computer other than which is was originally created on.
			//   2) You chose 'Yes' when presented with a message asking if you wish to remove the Add-in.
			//   3) You add new commands or modify commands already defined.
			// You will need to re-register the Add-in by building the CodeGenSetup project,
			// right-clicking the project in the Solution Explorer, and then choosing install.
			// Alternatively, you could execute the ReCreateCommands.reg file the Add-in Wizard generated in
			// the project directory, or run 'devenv /setup' from a command prompt.
			try
			{
				Command command = commands.AddNamedCommand(addInInstance, "CodeGen", "Generate TypeInfo", "Automatically generates type info in AutoTypeInfo.h", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled);
				CommandBar commandBar = (CommandBar)commandBars["Tools"];
				CommandBarControl commandBarControl = command.AddControl(commandBar, 1);
			}
			catch (System.Exception)
			{
			}
		}

		/// <summary>
		///      Implements the Exec method of the IDTCommandTarget interface.
		///      This is called when the command is invoked.
		/// </summary>
		/// <param term='commandName'>
		///		The name of the command to execute.
		/// </param>
		/// <param term='executeOption'>
		///		Describes how the command should be run.
		/// </param>
		/// <param term='varIn'>
		///		Parameters passed from the caller to the command handler.
		/// </param>
		/// <param term='varOut'>
		///		Parameters passed from the command handler to the caller.
		/// </param>
		/// <param term='handled'>
		///		Informs the caller if the command was handled or not.
		/// </param>
		/// <seealso class='Exec' />
		public void Exec(string commandName, EnvDTE.vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
		{
			handled = false;
			if (executeOption == EnvDTE.vsCommandExecOption.vsCommandExecOptionDoDefault)
			{
				if (commandName == "CodeGen.Connect.CodeGen")
				{
					GenerateTypeInfo();
					handled = true;
					return;
				}
			}
		}
		
		public void GenerateTypeInfo()
		{
			// Get current solution.
			if (!applicationObject.Solution.IsOpen)
				return;
			Solution sln = applicationObject.Solution;

			resultsDesc = "";

			// Create output file.
			FileInfo file = new FileInfo(sln.FileName);
			string outname = file.DirectoryName + "\\AutoTypeInfo.h";
			mainWriter = new StreamUpdateWriter(outname);

			logState.totalElems = 0;
			FindInfoDeclarations(false);

			// Search files for declarations.
			logState.totalElems = logState.totalElem; 
			processedTypes = new StringCollection();
			missedTypes = new StringCollection();
			localTypes = new ArrayList();

			FindInfoDeclarations(true);

			mainWriter.Write("\n");
			foreach (string miss in missedTypes)
				mainWriter.Write("// WARNING: Type " + miss + " not found\n");

			resultsDesc += mainWriter.CloseReplace();

			// Process local types.
			StreamUpdateWriter localWriter = null;
			ProjectItem localProject = null;
			foreach (CodeType type in localTypes)
			{
				if (type.ProjectItem != localProject)
				{
					if (localWriter != null)
					{
						resultsDesc += localWriter.CloseReplace();
						localWriter = null;
					}
					localProject = type.ProjectItem;

					string fileName = localProject.Properties.Item("FullPath").Value.ToString();
					int pos = fileName.LastIndexOf('.');
					string infoName = fileName.Substring(0, pos) + "_info.h";
					localWriter = new StreamUpdateWriter(infoName);
				}

				ProcessStruct(localWriter, type, false);
			}
			if (localWriter != null)
			{
				resultsDesc += localWriter.CloseReplace();
				localWriter = null;
			}

			applicationObject.StatusBar.Progress(false, "", 0, 0);

			resultsDesc = "Generated " + processedTypes.Count + " type infos\n" + resultsDesc;
			MessageBox.Show(resultsDesc, "CodeGen results for " + applicationObject.Solution.FullName);
		}

		private void FindInfoDeclarations(bool bActual)
		{
			logState.totalElem = 0;
			foreach (Project proj in applicationObject.Solution.Projects)
			{
				logState.curProj = proj.Name;
				FindInfoDeclarations(proj.ProjectItems, bActual);
			}
		}

		private void FindInfoDeclarations(ProjectItems projItems, bool bActual)
		{
			foreach (ProjectItem projItem in projItems)
			{
				try
				{
					if (projItem.ProjectItems != null && projItem.ProjectItems.Count > 0)
					{
						// Sub-project.
						int len = logState.curProj.Length;
						logState.curProj += " / " + projItem.Name;
						FindInfoDeclarations(projItem.ProjectItems, bActual);
						logState.curProj = logState.curProj.Substring(0, len);
					}
					else
					{
						// Project file.
						logState.totalElem++;
						if (bActual)
						{
							UpdateProgress();
							FindInfoDeclarations(projItem);
						}
					}
				}
				catch (System.Exception)
				{}
			}
		}

		private void FindInfoDeclarations(ProjectItem projItem)
		{
			try
			{
				if (projItem.FileCount != 1)
					return;
				if (projItem.Name == "AutoTypeInfo.h")
					return;

				// Plain text search the file. faster than accessing the CodeModel at this point.
				if (projItem.Properties == null)
					return;
				if (projItem.Properties.Item("Kind").Value.ToString() != "VCFile")
					return;

				curFileName = projItem.Properties.Item("FullPath").Value.ToString();
				StreamReader reader = new StreamReader(curFileName);
				string fileContents = reader.ReadToEnd();
				reader.Close();

				int iSearchInternal = 0;
				for (int start = 0;;)
				{
					int index = fileContents.IndexOf("AUTO_", start);
					if (index < 0)
						break;

					start = index+5;
					if (MatchAt(fileContents, start, "STRUCT_INFO"))
					{
						iSearchInternal++;
						start += 11;
					}
					else if (MatchAt(fileContents, start, "TYPE_INFO"))
					{
						start += 9;

						bool bLocal = false;
						if (MatchAt(fileContents, start, "_LOCAL"))
						{
							start += 6;
							bLocal = true;
						}

						if (fileContents.Substring(start, 2) == "_T")
							start += 2;
						if (fileContents[start] != '(')
							continue;
						start++;

						// Extract type name.
						index = fileContents.IndexOf(")", start);
						if (index > start)
						{
							try
							{
								string typeName = fileContents.Substring(start, index-start).Trim();
								if (bLocal)
								{
									CodeType type = GetCodeType(typeName, null, projItem);
									if (type != null && !localTypes.Contains(type))
										localTypes.Add(type);
								}
								else
								{
									ProcessStruct(mainWriter, typeName, null, projItem);
								}
							}
							catch (System.Exception)
							{}
						}
					}
				}

				if (iSearchInternal > 0 && projItem.FileCodeModel != null && projItem.FileCodeModel.CodeElements != null)
				{
					// Search all code elements for text.
					int iFound = FindInternalDeclarations(projItem.FileCodeModel.CodeElements);
					//if (iFound < iSearchInternal)
					//	mainWriter.Write("// WARNING: " + (iSearchInternal-iFound) + " AUTO_STRUCT_INFO structs not matched in file " + curFileName + "\n");
				}
			}
			catch (System.Exception)
			{}
		}

		private int FindInternalDeclarations(CodeElements elems)
		{
			int totalFound = 0;
			foreach (CodeElement elem in elems)
			{
				if (elem.Kind == vsCMElement.vsCMElementNamespace
					|| elem.Kind == vsCMElement.vsCMElementClass
					|| elem.Kind == vsCMElement.vsCMElementStruct)
				{
					totalFound += FindInternalDeclarations(elem);
				}
			}
			return totalFound;
		}

		private int FindInternalDeclarations(CodeElement elem)
		{
			int found = 0;
			if (elem.Children != null )
				// Recurse on children.
				found += FindInternalDeclarations(elem.Children);

			if (elem.Kind == vsCMElement.vsCMElementNamespace)
				return found;

			// Get type body text.
			EditPoint edit = elem.StartPoint.CreateEditPoint();
			string codeText = edit.GetText(elem.EndPoint);

			// Find all occurrences of the key in the text.
			for (int index = 0;;)
			{
				index = codeText.IndexOf("STRUCT_INFO", index);
				if (index < 0)
					break;
				if (!IndexInChildren(index, elem))
				{
					found++;
					if (MatchAt(codeText, index-5, "AUTO_"))
					{
						index += 11;

						if (processedTypes.Contains(elem.FullName))
							continue;

						if (MatchAt(codeText, index, "_LOCAL"))
						{
							if (!localTypes.Contains(elem))
								localTypes.Add(elem);
						}
						else
							ProcessStruct(mainWriter, (CodeType)elem, false);
					}
					break;
				}
				index += 11;
			}
			return found;
		}

		bool IndexInChildren(int index, CodeElement elem)
		{
			index += elem.StartPoint.AbsoluteCharOffset + elem.StartPoint.Line;
			if (elem.Children != null)
				foreach (CodeElement child in elem.Children)
					if (child.IsCodeType)
					{
						if (index >= child.StartPoint.AbsoluteCharOffset + child.StartPoint.Line
						&& index < child.EndPoint.AbsoluteCharOffset + child.EndPoint.Line)
							return true;
					}
			return false;
		}

		private bool bTimeToUpdateLog()
		{
			if (System.DateTime.Now.Ticks > logState.lastLogTicks + 100000)
			{
				logState.lastLogTicks = System.DateTime.Now.Ticks;
				return true;
			}
			return false;
		}

		// Generate type info for structs.
		private void ProcessStruct(StreamUpdateWriter writer, CodeType type, bool bExt)
		{
			try
			{
				if (processedTypes.Contains(type.FullName))
					return;

				if (bExt)
				{
					// Only process if no internal structure.
					if (FindInternalDeclarations((CodeElement)type) > 0)
						return;
				}
				processedTypes.Add(type.FullName);
				UpdateProgress();

				if (type.Kind == vsCMElement.vsCMElementEnum)
				{
					ProcessEnum(writer, (VCCodeEnum)type);
					return;
				}
				if (type.Kind != vsCMElement.vsCMElementStruct
					&& type.Kind != vsCMElement.vsCMElementClass)
					return;

				// Extract template declarations.
				string strucMacroString = "STRUCT_INFO";
				if (bExt)
					strucMacroString += "_TYPE";
				string strucNameString = type.FullName;
				bool bTemplate = false;
				if (type.Kind == vsCMElement.vsCMElementStruct)
					bTemplate = ((VCCodeStruct)type).IsTemplate;
				else if (type.Kind == vsCMElement.vsCMElementClass)
					bTemplate = ((VCCodeClass)type).IsTemplate;
				if (bTemplate)
				{
					int tpos = type.FullName.IndexOf('<');
					if (tpos > 0)
					{
						strucMacroString += "_T";
						strucNameString = strucNameString.Insert(tpos, ", ");
						string declArgs = "";
						if (type.Kind == vsCMElement.vsCMElementStruct)
							declArgs = ((VCCodeStruct)type).DeclarationText;
						else if (type.Kind == vsCMElement.vsCMElementClass)
							declArgs = ((VCCodeClass)type).DeclarationText;
						tpos = declArgs.IndexOf('<');
						int etpos = declArgs.IndexOf(':');
						if (etpos < 0)
							etpos = declArgs.Length-1;
						etpos = declArgs.LastIndexOf('>', etpos);
						strucNameString += ", " + declArgs.Substring(tpos, etpos+1-tpos);
					}
				}

				// Build string.
				string infoText = strucMacroString + "_BEGIN(" + strucNameString+ ")\n";
				int infoLen = infoText.Length;

				// Process children, recursively processing any dependent types.
				// Bases.
				foreach (CodeElement baseType in type.Bases)
				{
					if (baseType.IsCodeType)
						ProcessStruct(writer, (CodeType)baseType, true);
					else
						ProcessStruct(writer, baseType.FullName, type, type.ProjectItem);

					infoText += "\tSTRUCT_BASE_INFO(";
					if (baseType.Kind == vsCMElement.vsCMElementVCBase)
						infoText += ((VCCodeBase)baseType).DisplayName;
					else
						infoText += baseType.FullName;
					infoText += ")\n";
				}

				// Non-static variable members.
				foreach (CodeElement member in type.Members)
				{
					if (member.Kind == vsCMElement.vsCMElementVariable)
					{
						CodeVariable var = (CodeVariable)member;
						if (!var.IsShared && var.Access != vsCMAccess.vsCMAccessPrivate)
						{
							// Decompose additional type info into a text string.
							// Deficient code model doesn't have full type info, we must resort to text parsing.
							string typeName = var.Type.AsString;
							string typeInfo = "@";			// Placeholder for final type name, to simplify text manipulation.
							string attrInfo = "";

							// Extract meta-attributes from comments.
							for (int attrPos = 0;;)
							{
								attrPos = var.Comment.IndexOf("$<", attrPos);
								if (attrPos < 0)
									break;
								attrPos += 2;
								int endPos = var.Comment.IndexOf(">", attrPos);
								if (endPos < 0)
									break;

								attrInfo += " ATTR_INFO(" 
									+ var.Comment.Substring(attrPos, endPos-attrPos).Trim().Replace('=', ',')
									+ ")";
								attrPos = endPos+1;
							}

							// Decompose type outside in.
							for (;;)
							{
								int pos;

								// To do: Nesting: int (*) [4].

								// Array.
								pos = typeName.IndexOf('[');
								int lpos = typeName.IndexOf(']');
								if (pos > 0 && lpos > pos)
								{
									string dim = typeName.Substring(pos+1, lpos-pos-1).Trim();
									typeInfo = typeInfo.Replace("@", "TYPE_ARRAY(" + dim + ", @)");
									typeName = typeName.Remove(pos, lpos+1-pos);
									continue;
								}

								// Pointer.
								pos = typeName.LastIndexOf('*');
								if (pos > 0)
								{
									typeInfo = typeInfo.Replace("@", "TYPE_POINTER(@)");
									typeName = typeName.Remove(pos, 1);
									continue;
								}

								// Reference.
								pos = typeName.LastIndexOf('&');
								if (pos > 0)
								{
									typeInfo = typeInfo.Replace("@", "TYPE_REFERENCE(@)");
									typeName = typeName.Remove(pos, 1);
									continue;
								}

								break;
							}

							// Bitfield.
							int fpos = typeName.IndexOf(':');
							if (fpos > 0 && typeName[fpos+1] != ':')
							{
								string bits = typeName.Substring(fpos+1).Trim();
								typeName = typeName.Substring(0, fpos);
								infoText +="\tSTRUCT_BITFIELD_INFO(" + var.Name + ", " + typeName.Trim() + ", " + bits + attrInfo + ")\n";
							}
							else
							{
								// Get base type info for recursive processing.
								CodeTypeRef varType = var.Type;
								while (varType.TypeKind == vsCMTypeRef.vsCMTypeRefArray && varType.ElementType != null)
									varType = varType.ElementType;

								if (varType.CodeType != null)
									ProcessStruct(writer, varType.CodeType, true);
								else if (varType.TypeKind == vsCMTypeRef.vsCMTypeRefOther)
								{
									// Template.
									string varTypeName = varType.AsFullName;
									int pos = varTypeName.IndexOf('<');
									if (pos > 0)
									{
										varTypeName = varTypeName.Substring(0, pos);
										ProcessStruct(writer, varTypeName.Trim(), type, type.ProjectItem);
									}
								}

								typeInfo = typeInfo.Replace("@", "TYPE_INFO(" + typeName.Trim() + ")");
								infoText +="\tSTRUCT_VAR_INFO(" + var.Name + ", " + typeInfo + attrInfo + ")\n";
							}
						}
					}
				}

				if (infoText.Length == infoLen)
				{
					// No child info, write special macro.
					infoText = strucMacroString + "_EMPTY(" + strucNameString + ")\n\n";
				}
				else
				{
					infoText += strucMacroString + "_END(" + strucNameString + ")\n\n";
				}

				writer.Write(infoText);
			}
			catch (System.Exception)
			{
				writer.Write("// Error processing type " + type.FullName + "\n");
			}
		}

		private void ProcessStruct(StreamUpdateWriter writer, string typeName, CodeType parentType, ProjectItem projItem)
		{
			if (processedTypes.Contains(typeName))
				return;

			CodeType type = GetCodeType(typeName, parentType, projItem);
			if (type != null)
			{
				ProcessStruct(writer, type, true);
				return;
			}

			if (!missedTypes.Contains(typeName))
				missedTypes.Add(typeName);
		}

		private CodeType GetCodeType(string typeName, CodeType parentType, ProjectItem projItem)
		{
			string fullName = typeName;
			for (;;)
			{
				CodeType type = projItem.ContainingProject.CodeModel.CodeTypeFromFullName(fullName);
				if (type != null)
					return type;

				// Search scopes outward.
				if (parentType != null)
				{
					fullName = parentType.FullName + "::" + typeName;
					try
					{
						if (parentType.FullName != parentType.Name)
							parentType = (CodeType)parentType.Parent;
						else
							parentType = null;
					}
					catch (System.Exception)
					{
						parentType = null;
					}
				}
				else
					break;
			}

			return null;
		}

		private void ProcessEnum(StreamUpdateWriter writer, VCCodeEnum enu)
		{
			writer.Write("ENUM_INFO_BEGIN(" + enu.FullName + ")\n");
			foreach (VCCodeElement elem in enu.Members)
			{
				// Handle stupid enum scope.
				string scope = enu.FullName.Substring(0, enu.FullName.Length - enu.Name.Length);
				writer.Write("\tENUM_ELEM_INFO(" + scope + ", " + elem.Name + ")\n");
			}
			writer.Write("ENUM_INFO_END(" + enu.FullName + ")\n\n");
		}

		private void UpdateProgress()
		{
			if (bTimeToUpdateLog())
			{
				applicationObject.StatusBar.Progress(true, 
					"Searching files for type info declarations (generated " 
					+ processedTypes.Count + "): " 
					+ logState.curProj,
					logState.totalElem, logState.totalElems);
			}
		}

		bool MatchAt(string str, int index, string match)
		{
			if (index >= 0 && str.Length >= index+match.Length)
				if (str.Substring(index, match.Length) == match)
					return true;
			return false;
		}

		private _DTE applicationObject;
		private AddIn addInInstance;
		StreamUpdateWriter mainWriter;
		string curFileName;

		StringCollection processedTypes;
		StringCollection missedTypes;
		ArrayList localTypes;
		String resultsDesc;

		// Logging state
		struct LogState
		{
			public int totalElem, totalElems;
			public string curProj;
			public long lastLogTicks;
		};
		LogState logState;
	}
}