using System;
using Extensibility;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.CommandBars;
using System.Resources;
using System.Reflection;
using System.Globalization;

using Microsoft.VisualStudio.VCCodeModel;
using System.IO;
using System.Collections;
using System.Collections.Specialized;

namespace TypeInfo
{
	/// <summary>The object for implementing an Add-in.</summary>
	/// <seealso class='IDTExtensibility2' />
	public class Connect : 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, ext_ConnectMode connectMode, object addInInst, ref Array custom)
		{
			_applicationObject = (DTE2)application;
			_addInInstance = (AddIn)addInInst;
			if (connectMode == ext_ConnectMode.ext_cm_UISetup)
			{
				object[] contextGUIDS = new object[] { };
				Commands2 commands = (Commands2)_applicationObject.Commands;
				string toolsMenuName;

				try
				{
					//If you would like to move the command to a different menu, change the word "Tools" to the 
					//  English version of the menu. This code will take the culture, append on the name of the menu
					//  then add the command to that menu. You can find a list of all the top-level menus in the file
					//  CommandBar.resx.
					ResourceManager resourceManager = new ResourceManager("TypeInfo.CommandBar", Assembly.GetExecutingAssembly());
					CultureInfo cultureInfo = new System.Globalization.CultureInfo(_applicationObject.LocaleID);
					string resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Tools");
					toolsMenuName = resourceManager.GetString(resourceName);
				}
				catch
				{
					//We tried to find a localized version of the word Tools, but one was not found.
					//  Default to the en-US word, which may work for the current culture.
					toolsMenuName = "Tools";
				}

				//Place the command on the tools menu.
				//Find the MenuBar command bar, which is the top-level command bar holding all the main menu items:
				Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];

				//Find the Tools command bar on the MenuBar command bar:
				CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
				CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;

				//This try/catch block can be duplicated if you wish to add multiple commands to be handled by your Add-in,
				//  just make sure you also update the QueryStatus/Exec method to include the new command names.
				try
				{
					//Add a command to the Commands collection:
					Command command = commands.AddNamedCommand2(_addInInstance, "TypeInfo", "TypeInfo", "Executes the command for TypeInfo", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);

					//Add a control for the command to the tools menu:
					if ((command != null) && (toolsPopup != null))
					{
						command.AddControl(toolsPopup.CommandBar, 1);
					}
				}
				catch (System.ArgumentException)
				{
					//If we are here, then the exception is probably because a command with that name
					//  already exists. If so there is no need to recreate the command and we can 
					//  safely ignore the exception.
				}
			}
		}

		/// <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(ext_DisconnectMode disconnectMode, ref Array custom)
		{
		}

		/// <summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when 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 Array custom)
		{
		}

		/// <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 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 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, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)
		{
			if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
			{
				if (commandName == "TypeInfo.Connect.TypeInfo")
				{
					status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
					return;
				}
			}
		}

		/// <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, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
		{
			handled = false;
			if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
			{
				if (commandName == "TypeInfo.Connect.TypeInfo")
				{
					GenerateTypeInfo();
					handled = true;
					return;
				}
			}
		}

		//
		// TypeInfo data.
		//
		private DTE2 _applicationObject;
		private AddIn _addInInstance;

		StreamUpdateWriter mainWriter;
		string curFileName;

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

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

		//
		// Generate TypeInfo code.
		//
		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("Searching files for type info declarations", false);
							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)
				{
					// Search all code elements for text.
					int iFound = 0;
					if (projItem.FileCodeModel != null && projItem.FileCodeModel.CodeElements != null)
						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;
			try
			{
				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;
				}
			}
			catch (System.Exception)
			{
				mainWriter.Write("// ERROR while examining code element " + elem.FullName + "\n");
			}
			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(bool bForce)
		{
			if (bForce || 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("Searching files for type info declarations", false);

				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: while 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(string msg, bool bForce)
		{
			if (bTimeToUpdateLog(bForce))
			{
				_applicationObject.StatusBar.Progress(true,
					msg + ", generated "
					+ processedTypes.Count + " types: "
					+ 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;
		}
	}

	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();

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

					oldContents = oldContents.Replace("\r", "");
					if (oldContents == newContents)
						// No update.
						return "";
				}
				catch (System.Exception)
				{
				}

				// 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;
	};
}

