/***************************************************************************

Copyright (c) Microsoft Corporation. All rights reserved.
This code is licensed under the Visual Studio SDK license terms.
THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

***************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Project.Automation;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using MSBuild = Microsoft.Build.Evaluation;

namespace Microsoft.VisualStudio.Project
{
	[CLSCompliant(false), ComVisible(true)]
	public abstract class ProjectContainerNode : ProjectNode,
		IVsParentProject,
		IBuildDependencyOnProjectContainer
	{
		#region fields

		/// <summary>
		/// Setting this flag to true will build all nested project when building this project
		/// </summary>
		private bool buildNestedProjectsOnBuild = true;

		private ProjectElement nestedProjectElement;

		/// <summary>
		/// Defines the listener that would listen on file changes on the nested project node.
		/// </summary>
		///<devremark>			
		///This might need a refactoring when nested projects can be added and removed by demand.
		/// </devremark>
		private FileChangeManager nestedProjectNodeReloader;
		#endregion

		#region ctors
		protected ProjectContainerNode()
		{
		}
		#endregion

		#region properties
		/// <summary>
		/// Returns teh object that handles listening to file changes on the nested project files.
		/// </summary>
		internal FileChangeManager NestedProjectNodeReloader
		{
			get
			{
				if(this.nestedProjectNodeReloader == null)
				{
					this.nestedProjectNodeReloader = new FileChangeManager(this.Site);
					this.nestedProjectNodeReloader.FileChangedOnDisk += this.OnNestedProjectFileChangedOnDisk;
				}

				return this.nestedProjectNodeReloader;
			}
		}
		#endregion

		#region overridden properties
		/// <summary>
		/// This is the object that will be returned by EnvDTE.Project.Object for this project
		/// </summary>
		internal override object Object
		{
			get { return new OASolutionFolder<ProjectContainerNode>(this); }
		}

		#endregion

		#region public overridden methods
		/// <summary>
		/// Gets the nested hierarchy.
		/// </summary>
		/// <param name="itemId">The item id.</param>
		/// <param name="iidHierarchyNested">Identifier of the interface to be returned in ppHierarchyNested.</param>
		/// <param name="ppHierarchyNested">Pointer to the interface whose identifier was passed in iidHierarchyNested.</param>
		/// <param name="pItemId">Pointer to an item identifier of the root node of the nested hierarchy.</param>
		/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. If ITEMID is not a nested hierarchy, this method returns E_FAIL.</returns>
		[CLSCompliant(false)]
		public override int GetNestedHierarchy(UInt32 itemId, ref Guid iidHierarchyNested, out IntPtr ppHierarchyNested, out uint pItemId)
		{
			pItemId = VSConstants.VSITEMID_ROOT;
			ppHierarchyNested = IntPtr.Zero;
			if(this.FirstChild != null)
			{
				for(HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling)
				{
					NestedProjectNode p = n as NestedProjectNode;

					if(p != null && p.ID == itemId)
					{
						if(p.NestedHierarchy != null)
						{
							IntPtr iunknownPtr = IntPtr.Zero;
							int returnValue = VSConstants.S_OK;
							try
							{
								iunknownPtr = Marshal.GetIUnknownForObject(p.NestedHierarchy);
								Marshal.QueryInterface(iunknownPtr, ref iidHierarchyNested, out ppHierarchyNested);
							}
							catch(COMException e)
							{
								returnValue = e.ErrorCode;
							}
							finally
							{
								if(iunknownPtr != IntPtr.Zero)
								{
									Marshal.Release(iunknownPtr);
								}
							}

							return returnValue;
						}
						break;
					}
				}
			}

			return VSConstants.E_FAIL;
		}

		public override int IsItemDirty(uint itemId, IntPtr punkDocData, out int pfDirty)
		{
			HierarchyNode hierNode = this.NodeFromItemId(itemId);
			Debug.Assert(hierNode != null, "Hierarchy node not found");
			if(hierNode != this)
			{
				return ErrorHandler.ThrowOnFailure(hierNode.IsItemDirty(itemId, punkDocData, out pfDirty));
			}
			else
			{
				return ErrorHandler.ThrowOnFailure(base.IsItemDirty(itemId, punkDocData, out pfDirty));
			}
		}

		public override int SaveItem(VSSAVEFLAGS dwSave, string silentSaveAsName, uint itemid, IntPtr punkDocData, out int pfCancelled)
		{
			HierarchyNode hierNode = this.NodeFromItemId(itemid);
			Debug.Assert(hierNode != null, "Hierarchy node not found");
			if(hierNode != this)
			{
				return ErrorHandler.ThrowOnFailure(hierNode.SaveItem(dwSave, silentSaveAsName, itemid, punkDocData, out pfCancelled));
			}
			else
			{
				return ErrorHandler.ThrowOnFailure(base.SaveItem(dwSave, silentSaveAsName, itemid, punkDocData, out pfCancelled));
			}
		}

		protected override bool FilterItemTypeToBeAddedToHierarchy(string itemType)
		{
			if(String.Compare(itemType, ProjectFileConstants.SubProject, StringComparison.OrdinalIgnoreCase) == 0)
			{
				return true;
			}
			return base.FilterItemTypeToBeAddedToHierarchy(itemType);
		}

		/// <summary>
		/// Called to reload a project item. 
		/// Reloads a project and its nested project nodes.
		/// </summary>
		/// <param name="itemId">Specifies itemid from VSITEMID.</param>
		/// <param name="reserved">Reserved.</param>
		/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
		public override int ReloadItem(uint itemId, uint reserved)
		{
			#region precondition
			if(this.IsClosed)
			{
				return VSConstants.E_FAIL;
			}
			#endregion

			NestedProjectNode node = this.NodeFromItemId(itemId) as NestedProjectNode;

			if(node != null)
			{
				object propertyAsObject = node.GetProperty((int)__VSHPROPID.VSHPROPID_HandlesOwnReload);

				if(propertyAsObject != null && (bool)propertyAsObject)
				{
					node.ReloadItem(reserved);
				}
				else
				{
					this.ReloadNestedProjectNode(node);
				}

				return VSConstants.S_OK;
			}

			return base.ReloadItem(itemId, reserved);
		}

		/// <summary>
		/// Reloads a project and its nested project nodes.
		/// </summary>
		protected override void Reload()
		{
			base.Reload();
			this.CreateNestedProjectNodes();
		}
		#endregion

		#region IVsParentProject
		public virtual int OpenChildren()
		{
			IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution;

			Debug.Assert(solution != null, "Could not retrieve the solution from the services provided by this project");
			if(solution == null)
			{
				return VSConstants.E_FAIL;
			}

			IntPtr iUnKnownForSolution = IntPtr.Zero;
			int returnValue = VSConstants.S_OK; // be optimistic.

			try
			{
				this.DisableQueryEdit = true;
				this.EventTriggeringFlag = ProjectNode.EventTriggering.DoNotTriggerHierarchyEvents | ProjectNode.EventTriggering.DoNotTriggerTrackerEvents;
				iUnKnownForSolution = Marshal.GetIUnknownForObject(solution);

				// notify SolutionEvents listeners that we are about to add children
				IVsFireSolutionEvents fireSolutionEvents = Marshal.GetTypedObjectForIUnknown(iUnKnownForSolution, typeof(IVsFireSolutionEvents)) as IVsFireSolutionEvents;
				ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnBeforeOpeningChildren(this));

				this.AddVirtualProjects();

				ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnAfterOpeningChildren(this));
			}
			catch(Exception e)
			{
				// Exceptions are digested by the caller but we want then to be shown if not a ComException and if not in automation.
				if(!(e is COMException) && !Utilities.IsInAutomationFunction(this.Site))
				{
					string title = null;
					OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
					OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
					OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
					VsShellUtilities.ShowMessageBox(this.Site, title, e.Message, icon, buttons, defaultButton);
				}

				Trace.WriteLine("Exception : " + e.Message);
				throw;
			}
			finally
			{
				this.DisableQueryEdit = false;

				if(iUnKnownForSolution != IntPtr.Zero)
				{
					Marshal.Release(iUnKnownForSolution);
				}

				this.EventTriggeringFlag = ProjectNode.EventTriggering.TriggerAll;
			}

			return returnValue;
		}

		public virtual int CloseChildren()
		{
			int returnValue = VSConstants.S_OK; // be optimistic.

			IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution;
			Debug.Assert(solution != null, "Could not retrieve the solution from the services provided by this project");

			if(solution == null)
			{
				return VSConstants.E_FAIL;
			}

			IntPtr iUnKnownForSolution = IntPtr.Zero;

			try
			{
				iUnKnownForSolution = Marshal.GetIUnknownForObject(solution);

				// notify SolutionEvents listeners that we are about to close children
				IVsFireSolutionEvents fireSolutionEvents = Marshal.GetTypedObjectForIUnknown(iUnKnownForSolution, typeof(IVsFireSolutionEvents)) as IVsFireSolutionEvents;
				ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnBeforeClosingChildren(this));

				// If the removal crashes we never fire the close children event. IS that a problem?
				this.RemoveNestedProjectNodes();

				ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnAfterClosingChildren(this));
			}
			finally
			{
				if(iUnKnownForSolution != IntPtr.Zero)
				{
					Marshal.Release(iUnKnownForSolution);
				}
			}

			return returnValue;
		}
		#endregion

		#region IBuildDependencyOnProjectContainerNode
		/// <summary>
		/// Defines whether nested projects should be build with the parent project
		/// </summary>
		public virtual bool BuildNestedProjectsOnBuild
		{
			get { return this.buildNestedProjectsOnBuild; }
			set { this.buildNestedProjectsOnBuild = value; }
		}

		/// <summary>
		/// Enumerates the nested hierachies that should be added to the build dependency list.
		/// </summary>
		/// <returns></returns>
		public virtual IVsHierarchy[] EnumNestedHierachiesForBuildDependency()
		{
			List<IVsHierarchy> nestedProjectList = new List<IVsHierarchy>();
			// Add all nested project among projectContainer child nodes
			for(HierarchyNode child = this.FirstChild; child != null; child = child.NextSibling)
			{
				NestedProjectNode nestedProjectNode = child as NestedProjectNode;
				if(nestedProjectNode != null)
				{
					nestedProjectList.Add(nestedProjectNode.NestedHierarchy);
				}
			}

			return nestedProjectList.ToArray();
		}
		#endregion

		#region helper methods

		internal protected void RemoveNestedProjectNodes()
		{
			for(HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling)
			{
				NestedProjectNode p = n as NestedProjectNode;
				if(p != null)
				{
					p.CloseNestedProjectNode();
				}
			}

			// We do not care of file changes after this.
			this.NestedProjectNodeReloader.FileChangedOnDisk -= this.OnNestedProjectFileChangedOnDisk;
			this.NestedProjectNodeReloader.Dispose();
		}

		/// <summary>
		/// This is used when loading the project to loop through all the items
		/// and for each SubProject it finds, it create the project and a node
		/// in our Hierarchy to hold the project.
		/// </summary>
		internal protected void CreateNestedProjectNodes()
		{
			// 1. Create a ProjectElement with the found item and then Instantiate a new Nested project with this ProjectElement.
			// 2. Link into the hierarchy.			
			// Read subprojects from from msbuildmodel
			__VSCREATEPROJFLAGS creationFlags = __VSCREATEPROJFLAGS.CPF_NOTINSLNEXPLR | __VSCREATEPROJFLAGS.CPF_SILENT;

			if(this.IsNewProject)
			{
				creationFlags |= __VSCREATEPROJFLAGS.CPF_CLONEFILE;
			}
			else
			{
				creationFlags |= __VSCREATEPROJFLAGS.CPF_OPENFILE;
			}

            foreach (MSBuild.ProjectItem item in this.BuildProject.Items)
			{
				if(String.Compare(item.ItemType, ProjectFileConstants.SubProject, StringComparison.OrdinalIgnoreCase) == 0)
				{
					this.nestedProjectElement = new ProjectElement(this, item, false);

					if(!this.IsNewProject)
					{
						AddExistingNestedProject(null, creationFlags);
					}
					else
					{
						// If we are creating the subproject from a vstemplate/vsz file
						bool isVsTemplate = Utilities.IsTemplateFile(GetProjectTemplatePath(null));
						if(isVsTemplate)
						{
							RunVsTemplateWizard(null, true);
						}
						else
						{
							// We are cloning the specified project file
							AddNestedProjectFromTemplate(null, creationFlags);
						}
					}
				}
			}

			this.nestedProjectElement = null;
		}
		/// <summary>
		/// Add an existing project as a nested node of our hierarchy.
		/// This is used while loading the project and can also be used
		/// to add an existing project to our hierarchy.
		/// </summary>
		protected internal virtual NestedProjectNode AddExistingNestedProject(ProjectElement element, __VSCREATEPROJFLAGS creationFlags)
		{
			ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;

			if(elementToUse == null)
			{
				throw new ArgumentNullException("element");
			}

			string filename = elementToUse.GetFullPathForElement();
			// Delegate to AddNestedProjectFromTemplate. Because we pass flags that specify open project rather then clone, this will works.
			Debug.Assert((creationFlags & __VSCREATEPROJFLAGS.CPF_OPENFILE) == __VSCREATEPROJFLAGS.CPF_OPENFILE, "__VSCREATEPROJFLAGS.CPF_OPENFILE should have been specified, did you mean to call AddNestedProjectFromTemplate?");
			return AddNestedProjectFromTemplate(filename, Path.GetDirectoryName(filename), Path.GetFileName(filename), elementToUse, creationFlags);
		}

		/// <summary>
		/// Let the wizard code execute and provide us the information we need.
		/// Our SolutionFolder automation object should be called back with the
		/// details at which point it will call back one of our method to add
		/// a nested project.
		/// If you are trying to add a new subproject this is probably the
		/// method you want to call. If you are just trying to clone a template
		/// project file, then AddNestedProjectFromTemplate is what you want.
		/// </summary>
		/// <param name="element">The project item to use as the base of the nested project.</param>
		/// <param name="silent">true if the wizard should run silently, otherwise false.</param>
		[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Vs")]
		protected internal void RunVsTemplateWizard(ProjectElement element, bool silent)
		{
			ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;

			if(elementToUse == null)
			{
				throw new ArgumentNullException("element");
			}
			this.nestedProjectElement = elementToUse;

			Automation.OAProject oaProject = GetAutomationObject() as Automation.OAProject;
			if(oaProject == null || oaProject.ProjectItems == null)
				throw new System.InvalidOperationException(SR.GetString(SR.InvalidAutomationObject, CultureInfo.CurrentUICulture));
			Debug.Assert(oaProject.Object != null, "The project automation object should have set the Object to the SolutionFolder");
			Automation.OASolutionFolder<ProjectContainerNode> folder = oaProject.Object as Automation.OASolutionFolder<ProjectContainerNode>;

			// Prepare the parameters to pass to RunWizardFile
			string destination = elementToUse.GetFullPathForElement();
			string template = this.GetProjectTemplatePath(elementToUse);

			object[] wizParams = new object[7];
			wizParams[0] = EnvDTE.Constants.vsWizardAddSubProject;
			wizParams[1] = Path.GetFileNameWithoutExtension(destination);
			wizParams[2] = oaProject.ProjectItems;
			wizParams[3] = Path.GetDirectoryName(destination);
			wizParams[4] = Path.GetFileNameWithoutExtension(destination);
			wizParams[5] = Path.GetDirectoryName(folder.DTE.FullName); //VS install dir
			wizParams[6] = silent;

			IVsDetermineWizardTrust wizardTrust = this.GetService(typeof(SVsDetermineWizardTrust)) as IVsDetermineWizardTrust;
			if(wizardTrust != null)
			{
				Guid guidProjectAdding = Guid.Empty;

				// In case of a project template an empty guid should be added as the guid parameter. See env\msenv\core\newtree.h IsTrustedTemplate method definition.
				ErrorHandler.ThrowOnFailure(wizardTrust.OnWizardInitiated(template, ref guidProjectAdding));
			}

			try
			{
				// Make the call to execute the wizard. This should cause AddNestedProjectFromTemplate to be
				// called back with the correct set of parameters.
				EnvDTE.IVsExtensibility extensibilityService = (EnvDTE.IVsExtensibility)GetService(typeof(EnvDTE.IVsExtensibility));
				EnvDTE.wizardResult result = extensibilityService.RunWizardFile(template, 0, ref wizParams);
				if(result == EnvDTE.wizardResult.wizardResultFailure)
					throw new COMException();
			}
			finally
			{
				if(wizardTrust != null)
				{
					ErrorHandler.ThrowOnFailure(wizardTrust.OnWizardCompleted());
				}
			}
		}

		/// <summary>
		/// This will clone a template project file and add it as a
		/// subproject to our hierarchy.
		/// If you want to create a project for which there exist a
		/// vstemplate, consider using RunVsTemplateWizard instead.
		/// </summary>
		protected internal virtual NestedProjectNode AddNestedProjectFromTemplate(ProjectElement element, __VSCREATEPROJFLAGS creationFlags)
		{
			ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;

			if(elementToUse == null)
			{
				throw new ArgumentNullException("element");
			}
			string destination = elementToUse.GetFullPathForElement();
			string template = this.GetProjectTemplatePath(elementToUse);

			return this.AddNestedProjectFromTemplate(template, Path.GetDirectoryName(destination), Path.GetFileName(destination), elementToUse, creationFlags);
		}

		/// <summary>
		/// This can be called directly or through RunVsTemplateWizard.
		/// This will clone a template project file and add it as a
		/// subproject to our hierarchy.
		/// If you want to create a project for which there exist a
		/// vstemplate, consider using RunVsTemplateWizard instead.
		/// </summary>
		protected internal virtual NestedProjectNode AddNestedProjectFromTemplate(string fileName, string destination, string projectName, ProjectElement element, __VSCREATEPROJFLAGS creationFlags)
		{
			// If this is project creation and the template specified a subproject in its project file, this.nestedProjectElement will be used 
			ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;

			if(elementToUse == null)
			{
				// If this is null, this means MSBuild does not know anything about our subproject so add an MSBuild item for it
				elementToUse = new ProjectElement(this, fileName, ProjectFileConstants.SubProject);
			}

			NestedProjectNode node = CreateNestedProjectNode(elementToUse);
			node.Init(fileName, destination, projectName, creationFlags);

			// In case that with did not have an existing element, or the nestedProjectelement was null 
			//  and since Init computes the final path, set the MSBuild item to that path
			if(this.nestedProjectElement == null)
			{
				string relativePath = node.Url;
				if(Path.IsPathRooted(relativePath))
				{
					relativePath = this.ProjectFolder;
					if(!relativePath.EndsWith("/\\", StringComparison.Ordinal))
					{
						relativePath += Path.DirectorySeparatorChar;
					}

					relativePath = new Url(relativePath).MakeRelative(new Url(node.Url));
				}

				elementToUse.Rename(relativePath);
			}

			this.AddChild(node);
			return node;
		}

		/// <summary>
		/// Override this method if you want to provide your own type of nodes.
		/// This would be the case if you derive a class from NestedProjectNode
		/// </summary>
		protected virtual NestedProjectNode CreateNestedProjectNode(ProjectElement element)
		{
			return new NestedProjectNode(this, element);
		}

		/// <summary>
		/// Links the nested project nodes to the solution. The default implementation parses all nested project nodes and calles AddVirtualProjectEx on them.
		/// </summary>
		protected virtual void AddVirtualProjects()
		{
			for(HierarchyNode child = this.FirstChild; child != null; child = child.NextSibling)
			{
				NestedProjectNode nestedProjectNode = child as NestedProjectNode;
				if(nestedProjectNode != null)
				{
					nestedProjectNode.AddVirtualProject();
				}
			}
		}

		/// <summary>
		/// Based on the Template and TypeGuid properties of the
		/// element, generate the full template path.
		/// 
		/// TypeGuid should be the Guid of a registered project factory.
		/// Template can be a full path, a project template (for projects
		/// that support VsTemplates) or a relative path (for other projects).
		/// </summary>
		protected virtual string GetProjectTemplatePath(ProjectElement element)
		{
			ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;

			if(elementToUse == null)
			{
				throw new ArgumentNullException("element");
			}

			string templateFile = elementToUse.GetMetadata(ProjectFileConstants.Template);
			Debug.Assert(!String.IsNullOrEmpty(templateFile), "No template file has been specified in the template attribute in the project file");

			string fullPath = templateFile;
			if(!Path.IsPathRooted(templateFile))
			{
				RegisteredProjectType registeredProjectType = this.GetRegisteredProject(elementToUse);

				// This is not a full path
				Debug.Assert(registeredProjectType != null && (!String.IsNullOrEmpty(registeredProjectType.DefaultProjectExtensionValue) || !String.IsNullOrEmpty(registeredProjectType.WizardTemplatesDirValue)), " Registered wizard directory value not set in the registry.");

				// See if this specify a VsTemplate file
				fullPath = registeredProjectType.GetVsTemplateFile(templateFile);
				if(String.IsNullOrEmpty(fullPath))
				{
					// Default to using the WizardTemplateDir to calculate the absolute path
					fullPath = Path.Combine(registeredProjectType.WizardTemplatesDirValue, templateFile);
				}
			}

			return fullPath;
		}

		/// <summary>
		/// Get information from the registry based for the project 
		/// factory corresponding to the TypeGuid of the element
		/// </summary>
		private RegisteredProjectType GetRegisteredProject(ProjectElement element)
		{
			ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;

			if(elementToUse == null)
			{
				throw new ArgumentNullException("element");
			}

			// Get the project type guid from project elementToUse				
			string typeGuidString = elementToUse.GetMetadataAndThrow(ProjectFileConstants.TypeGuid, new Exception());
			Guid projectFactoryGuid = new Guid(typeGuidString);

			EnvDTE.DTE dte = this.ProjectMgr.Site.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
			Debug.Assert(dte != null, "Could not get the automation object from the services exposed by this project");

			if(dte == null)
				throw new InvalidOperationException();

			RegisteredProjectType registeredProjectType = RegisteredProjectType.CreateRegisteredProjectType(projectFactoryGuid);
			Debug.Assert(registeredProjectType != null, "Could not read the registry setting associated to this project.");
			if(registeredProjectType == null)
			{
				throw new InvalidOperationException();
			}
			return registeredProjectType;
		}

		/// <summary>
		/// Reloads a nested project node by deleting it and readding it.
		/// </summary>
		/// <param name="node">The node to reload.</param>
		protected virtual void ReloadNestedProjectNode(NestedProjectNode node)
		{
			if(node == null)
			{
				throw new ArgumentNullException("node");
			}

			IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution;

			if(solution == null)
			{
				throw new InvalidOperationException();
			}

			NestedProjectNode newNode = null;
			try
			{
				// (VS 2005 UPDATE) When deleting and re-adding the nested project,
				// we do not want SCC to see this as a delete and add operation. 
				this.EventTriggeringFlag = ProjectNode.EventTriggering.DoNotTriggerTrackerEvents;

				// notify SolutionEvents listeners that we are about to add children
				IVsFireSolutionEvents fireSolutionEvents = solution as IVsFireSolutionEvents;

				if(fireSolutionEvents == null)
				{
					throw new InvalidOperationException();
				}

				ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnBeforeUnloadProject(node.NestedHierarchy));

				int isDirtyAsInt = 0;
				this.IsDirty(out isDirtyAsInt);

				bool isDirty = (isDirtyAsInt == 0) ? false : true;

				ProjectElement element = node.ItemNode;
				node.CloseNestedProjectNode();

				// Remove from the solution
				this.RemoveChild(node);

				// Now readd it                
				try
				{
					__VSCREATEPROJFLAGS flags = __VSCREATEPROJFLAGS.CPF_NOTINSLNEXPLR | __VSCREATEPROJFLAGS.CPF_SILENT | __VSCREATEPROJFLAGS.CPF_OPENFILE;
					newNode = this.AddExistingNestedProject(element, flags);
					newNode.AddVirtualProject();
				}
				catch(Exception e)
				{
					// We get a System.Exception if anything failed, thus we have no choice but catch it. 
					// Exceptions are digested by VS. Show the error if not in automation.
					if(!Utilities.IsInAutomationFunction(this.Site))
					{
						string message = (String.IsNullOrEmpty(e.Message)) ? SR.GetString(SR.NestedProjectFailedToReload, CultureInfo.CurrentUICulture) : e.Message;
						string title = string.Empty;
						OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
						OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
						OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
						VsShellUtilities.ShowMessageBox(this.Site, title, message, icon, buttons, defaultButton);
					}

					// Do not digest exception. let the caller handle it. If in a later stage this exception is not digested then the above messagebox is not needed.
					throw;
				}

#if DEBUG
				IVsHierarchy nestedHierarchy;
				ErrorHandler.ThrowOnFailure(solution.GetProjectOfUniqueName(newNode.GetMkDocument(), out nestedHierarchy));
				Debug.Assert(nestedHierarchy != null && Utilities.IsSameComObject(nestedHierarchy, newNode.NestedHierarchy), "The nested hierrachy was not reloaded correctly.");
#endif
				this.SetProjectFileDirty(isDirty);

				ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnAfterLoadProject(newNode.NestedHierarchy));
			}
			finally
			{
				// In this scenario the nested project failed to unload or reload the nested project. We will unload the whole project, otherwise the nested project is lost.
				// This is similar to the scenario when one wants to open a project and the nested project cannot be loaded because for example the project file has xml errors.
				// We should note that we rely here that if the unload fails then exceptions are not digested and are shown to the user.
				if(newNode == null || newNode.NestedHierarchy == null)
				{
					ErrorHandler.ThrowOnFailure(solution.CloseSolutionElement((uint)__VSSLNCLOSEOPTIONS.SLNCLOSEOPT_UnloadProject | (uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, this, 0));
				}
				else
				{
					this.EventTriggeringFlag = ProjectNode.EventTriggering.TriggerAll;
				}
			}
		}

		/// <summary>
		/// Event callback. Called when one of the nested project files is changed.
		/// </summary>
		/// <param name="sender">The FileChangeManager object.</param>
		/// <param name="e">Event args containing the file name that was updated.</param>
		private void OnNestedProjectFileChangedOnDisk(object sender, FileChangedOnDiskEventArgs e)
		{
			#region Pre-condition validation
			Debug.Assert(e != null, "No event args specified for the FileChangedOnDisk event");

			// We care only about time change for reload.
			if((e.FileChangeFlag & _VSFILECHANGEFLAGS.VSFILECHG_Time) == 0)
			{
				return;
			}

			// test if we actually have a document for this id.
			string moniker;
			this.GetMkDocument(e.ItemID, out moniker);
			Debug.Assert(NativeMethods.IsSamePath(moniker, e.FileName), " The file + " + e.FileName + " has changed but we could not retrieve the path for the item id associated to the path.");
			#endregion

			bool reload = true;
			if(!Utilities.IsInAutomationFunction(this.Site))
			{
				// Prompt to reload the nested project file. We use the moniker here since the filename from the event arg is canonicalized.
				string message = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.QueryReloadNestedProject, CultureInfo.CurrentUICulture), moniker);
				string title = string.Empty;
				OLEMSGICON icon = OLEMSGICON.OLEMSGICON_INFO;
				OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_YESNO;
				OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
				reload = (VsShellUtilities.ShowMessageBox(this.Site, message, title, icon, buttons, defaultButton) == NativeMethods.IDYES);
			}

			if(reload)
			{
				// We have to use here the interface method call, since it might be that specialized project nodes like the project container item
				// is owerwriting the default functionality.
				this.ReloadItem(e.ItemID, 0);
			}
		}
		#endregion
	}
}
