//
// Project.cs: Project class
//
// Author:
//   Marek Sieradzki (marek.sieradzki@gmail.com)
//   Ankit Jain (jankit@novell.com)
//
// (C) 2005 Marek Sieradzki
// Copyright 2011 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Mono.XBuild.Framework;
using Mono.XBuild.CommandLine;

namespace Microsoft.Build.BuildEngine {
	public class Project {
	
		bool				buildEnabled;
		Dictionary <string, List <string>>	conditionedProperties;
		string[]			defaultTargets;
		Encoding			encoding;
		BuildItemGroup			evaluatedItems;
		BuildItemGroup			evaluatedItemsIgnoringCondition;
		Dictionary <string, BuildItemGroup>	evaluatedItemsByName;
		Dictionary <string, BuildItemGroup>	evaluatedItemsByNameIgnoringCondition;
		BuildPropertyGroup		evaluatedProperties;
		string				firstTargetName;
		string				fullFileName;
		BuildPropertyGroup		globalProperties;
		GroupingCollection		groupingCollection;
		bool				isDirty;
		bool				isValidated;
		BuildItemGroupCollection	itemGroups;
		ImportCollection		imports;
		List<string>			initialTargets;
		Dictionary <string, BuildItemGroup> last_item_group_containing;
		bool				needToReevaluate;
		Engine				parentEngine;
		BuildPropertyGroupCollection	propertyGroups;
		string				schemaFile;
		TaskDatabase			taskDatabase;
		TargetCollection		targets;
		DateTime			timeOfLastDirty;
		UsingTaskCollection		usingTasks;
		XmlDocument			xmlDocument;
		bool				unloaded;
		bool				initialTargetsBuilt;
		bool				building;
		BuildSettings			current_settings;
		Stack<Batch>			batches;

		// This is used to keep track of "current" file,
		// which is then used to set the reserved properties
		// $(MSBuildThisFile*)
		Stack<string> this_file_property_stack;
		ProjectLoadSettings		project_load_settings;


		static string extensions_path;
		static XmlNamespaceManager	manager;
		static string ns = "http://schemas.microsoft.com/developer/msbuild/2003";

		public Project ()
			: this (Engine.GlobalEngine)
		{
		}

		public Project (Engine engine) : this (engine, null)
		{
		}
		
		public Project (Engine engine, string toolsVersion)
		{
			parentEngine  = engine;
			ToolsVersion = toolsVersion;

			buildEnabled = ParentEngine.BuildEnabled;
			xmlDocument = new XmlDocument ();
			xmlDocument.PreserveWhitespace = false;
			xmlDocument.AppendChild (xmlDocument.CreateElement ("Project", XmlNamespace));
			xmlDocument.DocumentElement.SetAttribute ("xmlns", ns);
			
			fullFileName = String.Empty;
			timeOfLastDirty = DateTime.Now;
			current_settings = BuildSettings.None;
			project_load_settings = ProjectLoadSettings.None;

			encoding = null;

			initialTargets = new List<string> ();
			defaultTargets = new string [0];
			batches = new Stack<Batch> ();
			this_file_property_stack = new Stack<string> ();

			globalProperties = new BuildPropertyGroup (null, this, null, false);
			foreach (BuildProperty bp in parentEngine.GlobalProperties)
				GlobalProperties.AddProperty (bp.Clone (true));
			
			ProcessXml ();

		}

		[MonoTODO ("Not tested")]
		public void AddNewImport (string projectFile,
					  string condition)
		{
			if (projectFile == null)
				throw new ArgumentNullException ("projectFile");

			XmlElement importElement = xmlDocument.CreateElement ("Import", XmlNamespace);
			xmlDocument.DocumentElement.AppendChild (importElement);
			importElement.SetAttribute ("Project", projectFile);
			if (!String.IsNullOrEmpty (condition))
				importElement.SetAttribute ("Condition", condition);

			AddImport (importElement, null, false);
			MarkProjectAsDirty ();
			NeedToReevaluate ();
		}

		public BuildItem AddNewItem (string itemName,
					     string itemInclude)
		{
			return AddNewItem (itemName, itemInclude, false);
		}
		
		[MonoTODO ("Adds item not in the same place as MS")]
		public BuildItem AddNewItem (string itemName,
					     string itemInclude,
					     bool treatItemIncludeAsLiteral)
		{
			BuildItemGroup big;

			if (itemGroups.Count == 0)
				big = AddNewItemGroup ();
			else {
				if (last_item_group_containing.ContainsKey (itemName)) {
					big = last_item_group_containing [itemName];
				} else {
					// FIXME: not tested
					BuildItemGroup [] groups = new BuildItemGroup [itemGroups.Count];
					itemGroups.CopyTo (groups, 0);
					big = groups [0];
				}
			}

			BuildItem item = big.AddNewItem (itemName, itemInclude, treatItemIncludeAsLiteral);
				
			MarkProjectAsDirty ();
			NeedToReevaluate ();

			return item;
		}

		[MonoTODO ("Not tested")]
		public BuildItemGroup AddNewItemGroup ()
		{
			XmlElement element = xmlDocument.CreateElement ("ItemGroup", XmlNamespace);
			xmlDocument.DocumentElement.AppendChild (element);

			BuildItemGroup big = new BuildItemGroup (element, this, null, false);
			itemGroups.Add (big);
			MarkProjectAsDirty ();
			NeedToReevaluate ();

			return big;
		}

		[MonoTODO ("Ignores insertAtEndOfProject")]
		public BuildPropertyGroup AddNewPropertyGroup (bool insertAtEndOfProject)
		{
			XmlElement element = xmlDocument.CreateElement ("PropertyGroup", XmlNamespace);
			xmlDocument.DocumentElement.AppendChild (element);

			BuildPropertyGroup bpg = new BuildPropertyGroup (element, this, null, false);
			propertyGroups.Add (bpg);
			MarkProjectAsDirty ();
			NeedToReevaluate ();

			return bpg;
		}
		
		[MonoTODO ("Not tested, isn't added to TaskDatabase (no reevaluation)")]
		public void AddNewUsingTaskFromAssemblyFile (string taskName,
							     string assemblyFile)
		{
			if (taskName == null)
				throw new ArgumentNullException ("taskName");
			if (assemblyFile == null)
				throw new ArgumentNullException ("assemblyFile");

			XmlElement element = xmlDocument.CreateElement ("UsingTask", XmlNamespace);
			xmlDocument.DocumentElement.AppendChild (element);
			element.SetAttribute ("TaskName", taskName);
			element.SetAttribute ("AssemblyFile", assemblyFile);

			UsingTask ut = new UsingTask (element, this, null);
			usingTasks.Add (ut);
			MarkProjectAsDirty ();
		}
		
		[MonoTODO ("Not tested, isn't added to TaskDatabase (no reevaluation)")]
		public void AddNewUsingTaskFromAssemblyName (string taskName,
							     string assemblyName)
		{
			if (taskName == null)
				throw new ArgumentNullException ("taskName");
			if (assemblyName == null)
				throw new ArgumentNullException ("assemblyName");

			XmlElement element = xmlDocument.CreateElement ("UsingTask", XmlNamespace);
			xmlDocument.DocumentElement.AppendChild (element);
			element.SetAttribute ("TaskName", taskName);
			element.SetAttribute ("AssemblyName", assemblyName);

			UsingTask ut = new UsingTask (element, this, null);
			usingTasks.Add (ut);
			MarkProjectAsDirty ();
		}
		
		[MonoTODO ("Not tested")]
		public bool Build ()
		{
			return Build (new string [0]);
		}
		
		[MonoTODO ("Not tested")]
		public bool Build (string targetName)
		{
			if (targetName == null)
				return Build ((string[]) null);
			else
				return Build (new string [1] { targetName });
		}
		
		[MonoTODO ("Not tested")]
		public bool Build (string [] targetNames)
		{
			return Build (targetNames, null);
		}
		
		[MonoTODO ("Not tested")]
		public bool Build (string [] targetNames,
				   IDictionary targetOutputs)
		{
			return Build (targetNames, targetOutputs, BuildSettings.None);
		}

		[MonoTODO ("Not tested")]
		public bool Build (string [] targetNames,
				   IDictionary targetOutputs,
				   BuildSettings buildFlags)
		
		{
			bool result = false;
			ParentEngine.StartProjectBuild (this, targetNames);

			// Invoking this to emit a warning in case of unsupported
			// ToolsVersion
			GetToolsVersionToUse (true);

			string current_directory = Environment.CurrentDirectory;
			try {
				current_settings = buildFlags;
				if (!String.IsNullOrEmpty (fullFileName))
					Directory.SetCurrentDirectory (Path.GetDirectoryName (fullFileName));
				building = true;
				result = BuildInternal (targetNames, targetOutputs, buildFlags);
			} catch (InvalidProjectFileException ie) {
				ParentEngine.LogErrorWithFilename (fullFileName, ie.Message);
				ParentEngine.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", fullFileName, ie.ToString ()));
			} catch (Exception e) {
				ParentEngine.LogErrorWithFilename (fullFileName, e.Message);
				ParentEngine.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", fullFileName, e.ToString ()));
				throw;
			} finally {
				ParentEngine.EndProjectBuild (this, result);
				current_settings = BuildSettings.None;
				Directory.SetCurrentDirectory (current_directory);
				building = false;
			}

			return result;
		}

		bool BuildInternal (string [] targetNames,
				   IDictionary targetOutputs,
				   BuildSettings buildFlags)
		{
			CheckUnloaded ();
			if (buildFlags == BuildSettings.None) {
				needToReevaluate = false;
				Reevaluate ();
			}

			ProcessBeforeAndAfterTargets ();

			if (targetNames == null || targetNames.Length == 0) {
				if (defaultTargets != null && defaultTargets.Length != 0) {
					targetNames = defaultTargets;
				} else if (firstTargetName != null) {
					targetNames = new string [1] { firstTargetName};
				} else {
					if (targets == null || targets.Count == 0) {
						LogError (fullFileName, "No target found in the project");
						return false;
					}

					return false;
				}
			}

			if (!initialTargetsBuilt) {
				foreach (string target in initialTargets) {
					if (!BuildTarget (target.Trim (), targetOutputs))
						return false;
				}
				initialTargetsBuilt = true;
			}

			foreach (string target in targetNames) {
				if (target == null)
					throw new ArgumentNullException ("Target name cannot be null");

				if (!BuildTarget (target.Trim (), targetOutputs))
					return false;
			}
				
			return true;
		}

		bool BuildTarget (string target_name, IDictionary targetOutputs)
		{
			if (target_name == null)
				throw new ArgumentException ("targetNames cannot contain null strings");

			if (!targets.Exists (target_name)) {
				LogError (fullFileName, "Target named '{0}' not found in the project.", target_name);
				return false;
			}

			string key = GetKeyForTarget (target_name);
			if (!targets [target_name].Build (key))
				return false;

			ITaskItem[] outputs;
			if (targetOutputs != null && ParentEngine.BuiltTargetsOutputByName.TryGetValue (key, out outputs))
				targetOutputs [target_name] = outputs;
			return true;
		}

		internal string GetKeyForTarget (string target_name)
		{
			return GetKeyForTarget (target_name, true);
		}

		internal string GetKeyForTarget (string target_name, bool include_global_properties)
		{
			// target name is case insensitive
			return fullFileName + ":" + target_name.ToLowerInvariant () +
					(include_global_properties ? (":" + GlobalPropertiesToString (GlobalProperties))
					 			   : String.Empty);
		}

		string GlobalPropertiesToString (BuildPropertyGroup bgp)
		{
			StringBuilder sb = new StringBuilder ();
			foreach (BuildProperty bp in bgp)
				sb.AppendFormat (" {0}:{1}", bp.Name, bp.FinalValue);
			return sb.ToString ();
		}

		void ProcessBeforeAndAfterTargets ()
		{
			var beforeTable = Targets.AsIEnumerable ()
						.SelectMany (target => GetTargetNamesFromString (target.BeforeTargets),
								(target, before_target) => new {before_target, name = target.Name})
						.ToLookup (x => x.before_target, x => x.name)
						.ToDictionary (x => x.Key, x => x.Distinct ().ToList ());

			foreach (var pair in beforeTable) {
				if (targets.Exists (pair.Key))
					targets [pair.Key].BeforeThisTargets = pair.Value;
				else
					LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
			}

			var afterTable = Targets.AsIEnumerable ()
						.SelectMany (target => GetTargetNamesFromString (target.AfterTargets),
								(target, after_target) => new {after_target, name = target.Name})
						.ToLookup (x => x.after_target, x => x.name)
						.ToDictionary (x => x.Key, x => x.Distinct ().ToList ());

			foreach (var pair in afterTable) {
				if (targets.Exists (pair.Key))
					targets [pair.Key].AfterThisTargets = pair.Value;
				else
					LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
			}
		}

		string[] GetTargetNamesFromString (string targets)
		{
			Expression expr = new Expression ();
			expr.Parse (targets, ParseOptions.AllowItemsNoMetadataAndSplit);
			return (string []) expr.ConvertTo (this, typeof (string []));
		}

		[MonoTODO]
		public string [] GetConditionedPropertyValues (string propertyName)
		{
			if (conditionedProperties.ContainsKey (propertyName))
				return conditionedProperties [propertyName].ToArray ();
			else
				return new string [0];
		}

		public BuildItemGroup GetEvaluatedItemsByName (string itemName)
		{			
			if (needToReevaluate) {
				needToReevaluate = false;
				Reevaluate ();
			}

			if (evaluatedItemsByName.ContainsKey (itemName))
				return evaluatedItemsByName [itemName];
			else
				return new BuildItemGroup (this);
		}

		public BuildItemGroup GetEvaluatedItemsByNameIgnoringCondition (string itemName)
		{
			if (needToReevaluate) {
				needToReevaluate = false;
				Reevaluate ();
			}

			if (evaluatedItemsByNameIgnoringCondition.ContainsKey (itemName))
				return evaluatedItemsByNameIgnoringCondition [itemName];
			else
				return new BuildItemGroup (this);
		}

		public string GetEvaluatedProperty (string propertyName)
		{
			if (needToReevaluate) {
				needToReevaluate = false;
				Reevaluate ();
			}

			if (propertyName == null)
				throw new ArgumentNullException ("propertyName");

			BuildProperty bp = evaluatedProperties [propertyName];

			return bp == null ? null : (string) bp;
		}

		[MonoTODO ("We should remember that node and not use XPath to get it")]
		public string GetProjectExtensions (string id)
		{
			if (id == null || id == String.Empty)
				return String.Empty;

			XmlNode node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);

			if (node == null)
				return String.Empty;
			else
				return node.InnerXml;
		}


		public void Load (string projectFileName)
		{
			Load (projectFileName, ProjectLoadSettings.None);
		}

		public void Load (string projectFileName, ProjectLoadSettings projectLoadSettings)
		{
			project_load_settings = projectLoadSettings;
			if (String.IsNullOrEmpty (projectFileName))
				throw new ArgumentNullException ("projectFileName");

			if (!File.Exists (projectFileName))
				throw new ArgumentException (String.Format ("Project file {0} not found", projectFileName),
						"projectFileName");

			this.fullFileName = Utilities.FromMSBuildPath (Path.GetFullPath (projectFileName));
			PushThisFileProperty (fullFileName);

			string filename = fullFileName;
			if (String.Compare (Path.GetExtension (fullFileName), ".sln", true) == 0) {
				Project tmp_project = ParentEngine.CreateNewProject ();
				tmp_project.FullFileName = filename;
				SolutionParser sln_parser = new SolutionParser ();
				sln_parser.ParseSolution (fullFileName, tmp_project, delegate (int errorNumber, string message) {
						LogWarning (filename, message);
					});
				filename = fullFileName + ".proj";
				try {
					tmp_project.Save (filename);
					ParentEngine.RemoveLoadedProject (tmp_project);
					DoLoad (new StreamReader (filename));
				} finally {
					if (Environment.GetEnvironmentVariable ("XBUILD_EMIT_SOLUTION") == null)
						File.Delete (filename);
				}
			} else {
				DoLoad (new StreamReader (filename));
			}
		}
		
		[MonoTODO ("Not tested")]
		public void Load (TextReader textReader)
		{
			Load (textReader, ProjectLoadSettings.None);
		}

		public void Load (TextReader textReader, ProjectLoadSettings projectLoadSettings)
		{
			project_load_settings = projectLoadSettings;
			if (!string.IsNullOrEmpty (fullFileName))
				PushThisFileProperty (fullFileName);
			DoLoad (textReader);
		}

		public void LoadXml (string projectXml)
		{
			LoadXml (projectXml, ProjectLoadSettings.None);
		}

		public void LoadXml (string projectXml, ProjectLoadSettings projectLoadSettings)
		{
			project_load_settings = projectLoadSettings;
			if (!string.IsNullOrEmpty (fullFileName))
				PushThisFileProperty (fullFileName);
			DoLoad (new StringReader (projectXml));
			MarkProjectAsDirty ();
		}


		public void MarkProjectAsDirty ()
		{
			isDirty = true;
			timeOfLastDirty = DateTime.Now;
		}

		[MonoTODO ("Not tested")]
		public void RemoveAllItemGroups ()
		{
			int length = ItemGroups.Count;
			BuildItemGroup [] groups = new BuildItemGroup [length];
			ItemGroups.CopyTo (groups, 0);

			for (int i = 0; i < length; i++)
				RemoveItemGroup (groups [i]);

			MarkProjectAsDirty ();
			NeedToReevaluate ();
		}

		[MonoTODO ("Not tested")]
		public void RemoveAllPropertyGroups ()
		{
			int length = PropertyGroups.Count;
			BuildPropertyGroup [] groups = new BuildPropertyGroup [length];
			PropertyGroups.CopyTo (groups, 0);

			for (int i = 0; i < length; i++)
				RemovePropertyGroup (groups [i]);

			MarkProjectAsDirty ();
			NeedToReevaluate ();
		}

		[MonoTODO]
		public void RemoveItem (BuildItem itemToRemove)
		{
			if (itemToRemove == null)
				throw new ArgumentNullException ("itemToRemove");

			if (!itemToRemove.FromXml && !itemToRemove.HasParentItem)
				throw new InvalidOperationException ("The object passed in is not part of the project.");

			BuildItemGroup big = itemToRemove.ParentItemGroup;

			if (big.Count == 1) {
				// ParentItemGroup for items from xml and that have parent is the same
				groupingCollection.Remove (big);
			} else {
				if (big.ParentProject != this)
					throw new InvalidOperationException ("The object passed in is not part of the project.");

				if (itemToRemove.FromXml)
					big.RemoveItem (itemToRemove);
				else
					big.RemoveItem (itemToRemove.ParentItem);
			}

			MarkProjectAsDirty ();
			NeedToReevaluate ();
		}

		[MonoTODO ("Not tested")]
		public void RemoveItemGroup (BuildItemGroup itemGroupToRemove)
		{
			if (itemGroupToRemove == null)
				throw new ArgumentNullException ("itemGroupToRemove");

			groupingCollection.Remove (itemGroupToRemove);
			MarkProjectAsDirty ();
		}
		
		[MonoTODO]
		// NOTE: does not modify imported projects
		public void RemoveItemGroupsWithMatchingCondition (string matchCondition)
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		public void RemoveItemsByName (string itemName)
		{
			if (itemName == null)
				throw new ArgumentNullException ("itemName");

			throw new NotImplementedException ();
		}

		[MonoTODO ("Not tested")]
		public void RemovePropertyGroup (BuildPropertyGroup propertyGroupToRemove)
		{
			if (propertyGroupToRemove == null)
				throw new ArgumentNullException ("propertyGroupToRemove");

			groupingCollection.Remove (propertyGroupToRemove);
			MarkProjectAsDirty ();
		}
		
		[MonoTODO]
		// NOTE: does not modify imported projects
		public void RemovePropertyGroupsWithMatchingCondition (string matchCondition)
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		public void ResetBuildStatus ()
		{
			// hack to allow built targets to be removed
			building = true;
			Reevaluate ();
			building = false;
		}

		public void Save (string projectFileName)
		{
			Save (projectFileName, Encoding.Default);
			isDirty = false;
		}

		[MonoTODO ("Ignores encoding")]
		public void Save (string projectFileName, Encoding encoding)
		{
			xmlDocument.Save (projectFileName);
			isDirty = false;
		}

		public void Save (TextWriter textWriter)
		{
			xmlDocument.Save (textWriter);
			isDirty = false;
		}

		public void SetImportedProperty (string propertyName,
						 string propertyValue,
						 string condition,
						 Project importProject)
		{
			SetImportedProperty (propertyName, propertyValue, condition, importProject,
				PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
		}

		public void SetImportedProperty (string propertyName,
						 string propertyValue,
						 string condition,
						 Project importedProject,
						 PropertyPosition position)
		{
			SetImportedProperty (propertyName, propertyValue, condition, importedProject,
				PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
		}

		[MonoTODO]
		public void SetImportedProperty (string propertyName,
						 string propertyValue,
						 string condition,
						 Project importedProject,
						 PropertyPosition position,
						 bool treatPropertyValueAsLiteral)
		{
			throw new NotImplementedException ();
		}

		public void SetProjectExtensions (string id, string content)
		{
			if (id == null)
				throw new ArgumentNullException ("id");
			if (content == null)
				throw new ArgumentNullException ("content");

			XmlNode projectExtensions, node;

			projectExtensions = xmlDocument.SelectSingleNode ("/tns:Project/tns:ProjectExtensions", XmlNamespaceManager);
			
			if (projectExtensions == null) {
				projectExtensions = xmlDocument.CreateElement ("ProjectExtensions", XmlNamespace);
				xmlDocument.DocumentElement.AppendChild (projectExtensions);

				node = xmlDocument.CreateElement (id, XmlNamespace);
				node.InnerXml = content;
				projectExtensions.AppendChild (node);
			} else {
				node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);

				if (node == null) {
					node = xmlDocument.CreateElement (id, XmlNamespace);
					projectExtensions.AppendChild (node);
				}
				
				node.InnerXml = content;
				
			}

			MarkProjectAsDirty ();
		}
		
		public void SetProperty (string propertyName,
					 string propertyValue)
		{
			SetProperty (propertyName, propertyValue, "true",
				PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
		}

		public void SetProperty (string propertyName,
					 string propertyValue,
					 string condition)
		{
			SetProperty (propertyName, propertyValue, condition,
				PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
		}

		public void SetProperty (string propertyName,
					 string propertyValue,
					 string condition,
					 PropertyPosition position)
		{
			SetProperty (propertyName, propertyValue, condition,
				PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
		}

		[MonoTODO]
		public void SetProperty (string propertyName,
					 string propertyValue,
					 string condition,
					 PropertyPosition position,
					 bool treatPropertyValueAsLiteral)
		{
			throw new NotImplementedException ();
		}

		internal void Unload ()
		{
			unloaded = true;
		}

		internal void CheckUnloaded ()
		{
			if (unloaded)
				throw new InvalidOperationException ("This project object has been unloaded from the MSBuild engine and is no longer valid.");
		}

		internal void NeedToReevaluate ()
		{
			needToReevaluate = true;
		}
				
		// Does the actual loading.
		void DoLoad (TextReader textReader)
		{
			try {
				ParentEngine.RemoveLoadedProject (this);
	
				xmlDocument.Load (textReader);

				if (xmlDocument.DocumentElement.Name == "VisualStudioProject")
					throw new InvalidProjectFileException (String.Format (
							"Project file '{0}' is a VS2003 project, which is not " +
							"supported by xbuild. You need to convert it to msbuild " +
							"format to build with xbuild.", fullFileName));

				if (SchemaFile != null) {
					xmlDocument.Schemas.Add (XmlSchema.Read (
								new StreamReader (SchemaFile), ValidationCallBack));
					xmlDocument.Validate (ValidationCallBack);
				}

				if (xmlDocument.DocumentElement.Name != "Project") {
					throw new InvalidProjectFileException (String.Format (
						"The element <{0}> is unrecognized, or not supported in this context.", xmlDocument.DocumentElement.Name));
				}
	
				if (xmlDocument.DocumentElement.GetAttribute ("xmlns") != ns) {
					throw new InvalidProjectFileException (
						@"The default XML namespace of the project must be the MSBuild XML namespace." + 
						" If the project is authored in the MSBuild 2003 format, please add " +
						"xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\" to the <Project> element. " +
						"If the project has been authored in the old 1.0 or 1.2 format, please convert it to MSBuild 2003 format.  ");
				}
				ProcessXml ();
				ParentEngine.AddLoadedProject (this);
			} catch (Exception e) {
				throw new InvalidProjectFileException (String.Format ("{0}: {1}",
							fullFileName, e.Message), e);
			} finally {
				if (textReader != null)
					textReader.Close ();
			}
		}

		void Reevaluate ()
		{
			ProcessXml ();
		}

		void ProcessXml ()
		{
			groupingCollection = new GroupingCollection (this);
			imports = new ImportCollection (groupingCollection);
			usingTasks = new UsingTaskCollection (this);
			itemGroups = new BuildItemGroupCollection (groupingCollection);
			propertyGroups = new BuildPropertyGroupCollection (groupingCollection);
			targets = new TargetCollection (this);
			last_item_group_containing = new Dictionary <string, BuildItemGroup> ();
			
			string effective_tools_version = GetToolsVersionToUse (false);
			taskDatabase = new TaskDatabase ();
			taskDatabase.CopyTasks (ParentEngine.GetDefaultTasks (effective_tools_version));

			initialTargets = new List<string> ();
			defaultTargets = new string [0];
			PrepareForEvaluate (effective_tools_version);
			ProcessElements (xmlDocument.DocumentElement, null);
			
			isDirty = false;
			Evaluate ();
		}

		void ProcessProjectAttributes (XmlAttributeCollection attributes)
		{
			foreach (XmlAttribute attr in attributes) {
				switch (attr.Name) {
				case "InitialTargets":
					initialTargets.AddRange (attr.Value.Split (
									new char [] {';', ' '},
									StringSplitOptions.RemoveEmptyEntries));
					break;
				case "DefaultTargets":
					// first non-empty DefaultTargets found is used
					if (defaultTargets == null || defaultTargets.Length == 0)
						defaultTargets = attr.Value.Split (new char [] {';', ' '},
							StringSplitOptions.RemoveEmptyEntries);
					EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets",
								DefaultTargets, PropertyType.Reserved));
					break;
				}
			}
		}

		internal void ProcessElements (XmlElement rootElement, ImportedProject ip)
		{
			ProcessProjectAttributes (rootElement.Attributes);
			foreach (XmlNode xn in rootElement.ChildNodes) {
				if (xn is XmlElement) {
					XmlElement xe = (XmlElement) xn;
					switch (xe.Name) {
					case "ProjectExtensions":
						AddProjectExtensions (xe);
						break;
					case "Warning":
					case "Message":
					case "Error":
						AddMessage (xe);
						break;
					case "Target":
						AddTarget (xe, ip);
						break;
					case "UsingTask":
						AddUsingTask (xe, ip);
						break;
					case "Import":
						AddImport (xe, ip, true);
						break;
					case "ImportGroup":
						AddImportGroup (xe, ip, true);
						break;
					case "ItemGroup":
						AddItemGroup (xe, ip);
						break;
					case "PropertyGroup":
						AddPropertyGroup (xe, ip);
						break;
					case  "Choose":
						AddChoose (xe, ip);
						break;
					case "ItemDefinitionGroup":
						AddItemDefinitionGroup (xe);
						break;
					default:
						var pf = ip == null ? null : string.Format (" '{0}'", ip.FullFileName);
						throw new InvalidProjectFileException (String.Format ("Invalid element '{0}' in project file{1}.", xe.Name, pf));
					}
				}
			}
		}
		
		void PrepareForEvaluate (string effective_tools_version)
		{
			evaluatedItems = new BuildItemGroup (null, this, null, true);
			evaluatedItemsIgnoringCondition = new BuildItemGroup (null, this, null, true);
			evaluatedItemsByName = new Dictionary <string, BuildItemGroup> (StringComparer.OrdinalIgnoreCase);
			evaluatedItemsByNameIgnoringCondition = new Dictionary <string, BuildItemGroup> (StringComparer.OrdinalIgnoreCase);
			if (building && current_settings == BuildSettings.None)
				RemoveBuiltTargets ();

			InitializeProperties (effective_tools_version);
		}

		void Evaluate ()
		{
			groupingCollection.Evaluate ();

			//FIXME: UsingTasks aren't really evaluated. (shouldn't use expressions or anything)
			foreach (UsingTask usingTask in UsingTasks)
				usingTask.Evaluate ();
		}

		// Removes entries of all earlier built targets for this project
		void RemoveBuiltTargets ()
		{
			ParentEngine.ClearBuiltTargetsForProject (this);
		}

		void InitializeProperties (string effective_tools_version)
		{
			BuildProperty bp;

			evaluatedProperties = new BuildPropertyGroup (null, null, null, true);
			conditionedProperties = new Dictionary<string, List<string>> ();

			foreach (BuildProperty gp in GlobalProperties) {
				bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
				evaluatedProperties.AddProperty (bp);
			}
			
			foreach (BuildProperty gp in GlobalProperties)
				ParentEngine.GlobalProperties.AddProperty (gp);

			// add properties that we dont have from parent engine's
			// global properties
			foreach (BuildProperty gp in ParentEngine.GlobalProperties) {
				if (evaluatedProperties [gp.Name] == null) {
					bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
					evaluatedProperties.AddProperty (bp);
				}
			}

			foreach (DictionaryEntry de in Environment.GetEnvironmentVariables ()) {
				bp = new BuildProperty ((string) de.Key, (string) de.Value, PropertyType.Environment);
				evaluatedProperties.AddProperty (bp);
			}

			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFile", Path.GetFileName (fullFileName),
						PropertyType.Reserved));
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFullPath", fullFileName, PropertyType.Reserved));
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectName",
						Path.GetFileNameWithoutExtension (fullFileName),
						PropertyType.Reserved));
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectExtension",
						Path.GetExtension (fullFileName),
						PropertyType.Reserved));

			string toolsPath = parentEngine.Toolsets [effective_tools_version].ToolsPath;
			if (toolsPath == null)
				throw new Exception (String.Format ("Invalid tools version '{0}', no tools path set for this.", effective_tools_version));
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildBinPath", toolsPath, PropertyType.Reserved));
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsPath", toolsPath, PropertyType.Reserved));
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsRoot", Path.GetDirectoryName (toolsPath), PropertyType.Reserved));
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsVersion", effective_tools_version, PropertyType.Reserved));
			SetExtensionsPathProperties (DefaultExtensionsPath);
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets", DefaultTargets, PropertyType.Reserved));
			evaluatedProperties.AddProperty (new BuildProperty ("OS", OS, PropertyType.Environment));
#if XBUILD_12
			// see http://msdn.microsoft.com/en-us/library/vstudio/hh162058(v=vs.120).aspx
			if (effective_tools_version == "12.0" || effective_tools_version == "14.0") {
				evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsPath32", toolsPath, PropertyType.Reserved));

				var frameworkToolsPath = ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version451);

				evaluatedProperties.AddProperty (new BuildProperty ("MSBuildFrameworkToolsPath", frameworkToolsPath, PropertyType.Reserved));
				evaluatedProperties.AddProperty (new BuildProperty ("MSBuildFrameworkToolsPath32", frameworkToolsPath, PropertyType.Reserved));
			}
#endif
			// FIXME: make some internal method that will work like GetDirectoryName but output String.Empty on null/String.Empty
			string projectDir;
			if (FullFileName == String.Empty)
				projectDir = Environment.CurrentDirectory;
			else
				projectDir = Path.GetDirectoryName (FullFileName);

			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDirectory", projectDir, PropertyType.Reserved));

			if (this_file_property_stack.Count > 0)
				// Just re-inited the properties, but according to the stack,
				// we should have a MSBuild*This* property set
				SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
		}

		internal void SetExtensionsPathProperties (string extn_path)
		{
			if (!String.IsNullOrEmpty (extn_path)) {
				evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath", extn_path, PropertyType.Reserved));
				evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath32", extn_path, PropertyType.Reserved));
				evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath64", extn_path, PropertyType.Reserved));
			}
		}

		// precedence:
		// ToolsVersion property
		// ToolsVersion attribute on the project
		// parentEngine's DefaultToolsVersion
		string GetToolsVersionToUse (bool emitWarning)
		{
			if (!String.IsNullOrEmpty (ToolsVersion))
				return ToolsVersion;

#if XBUILD_14
			return "14.0";
#elif XBUILD_12
			return "12.0";
#else
			if (!HasToolsVersionAttribute)
				return parentEngine.DefaultToolsVersion;

			if (parentEngine.Toolsets [DefaultToolsVersion] == null) {
				if (emitWarning)
					LogWarning (FullFileName, "Project has unknown ToolsVersion '{0}'. Using the default tools version '{1}' instead.",
						DefaultToolsVersion, parentEngine.DefaultToolsVersion);
				return parentEngine.DefaultToolsVersion;
			}

			return DefaultToolsVersion;
#endif
		}
		
		void AddProjectExtensions (XmlElement xmlElement)
		{
		}
		
		void AddMessage (XmlElement xmlElement)
		{
		}
		
		void AddTarget (XmlElement xmlElement, ImportedProject importedProject)
		{
			Target target = new Target (xmlElement, this, importedProject);
			targets.AddTarget (target);
			
			if (firstTargetName == null)
				firstTargetName = target.Name;
		}
		
		void AddUsingTask (XmlElement xmlElement, ImportedProject importedProject)
		{
			UsingTask usingTask;

			usingTask = new UsingTask (xmlElement, this, importedProject);
			UsingTasks.Add (usingTask);
		}

		void AddImport (XmlElement xmlElement, ImportedProject importingProject, bool evaluate_properties)
		{
			// eval all the properties etc till the import
			if (evaluate_properties) {
				groupingCollection.Evaluate (EvaluationType.Property | EvaluationType.Choose);
			}
			try {
				PushThisFileProperty (importingProject != null ? importingProject.FullFileName : FullFileName);

				string project_attribute = xmlElement.GetAttribute ("Project");
				if (String.IsNullOrEmpty (project_attribute))
					throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");

				Import.ForEachExtensionPathTillFound (xmlElement, this, importingProject,
					(importPath, from_source_msg) => AddSingleImport (xmlElement, importPath, importingProject, from_source_msg));
			} finally {
				PopThisFileProperty ();
			}
		}

		void AddImportGroup (XmlElement xmlElement, ImportedProject importedProject, bool evaluate_properties)
		{
			// eval all the properties etc till the import group
			if (evaluate_properties) {
				groupingCollection.Evaluate (EvaluationType.Property | EvaluationType.Choose);
			}
			string condition_attribute = xmlElement.GetAttribute ("Condition");
			if (!ConditionParser.ParseAndEvaluate (condition_attribute, this))
				return;
			foreach (XmlNode xn in xmlElement.ChildNodes) {
				if (xn is XmlElement) {
					XmlElement xe = (XmlElement) xn;
					switch (xe.Name) {
					case "Import":
						AddImport (xe, importedProject, evaluate_properties);
						break;
					default:
						throw new InvalidProjectFileException(String.Format("Invalid element '{0}' inside ImportGroup in project file '{1}'.", xe.Name, importedProject.FullFileName));
					}
				}
			}
		}

		void AddItemDefinitionGroup (XmlElement xmlElement)
		{
			string condition_attribute = xmlElement.GetAttribute ("Condition");
			if (!ConditionParser.ParseAndEvaluate (condition_attribute, this))
				return;

			foreach (XmlNode xn in xmlElement.ChildNodes) {
				// TODO: Add all nodes to some internal dictionary?
			}
		}

		bool AddSingleImport (XmlElement xmlElement, string projectPath, ImportedProject importingProject, string from_source_msg)
		{
			Import import = new Import (xmlElement, projectPath, this, importingProject);
			if (!ConditionParser.ParseAndEvaluate (import.Condition, this)) {
				ParentEngine.LogMessage (MessageImportance.Low,
						"Not importing project '{0}' as the condition '{1}' is false",
						import.ProjectPath, import.Condition);
				return false;
			}

			Import existingImport;
			if (Imports.TryGetImport (import, out existingImport)) {
				if (importingProject == null)
					LogWarning (fullFileName,
							"Cannot import project '{0}' again. It was already imported by " +
							"'{1}'. Ignoring.",
							projectPath, existingImport.ContainedInProjectFileName);
				else
					LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
						"A circular reference was found involving the import of '{0}'. " +
						"It was earlier imported by '{1}'. Only " +
						"the first import of this file will be used, ignoring others.",
						import.EvaluatedProjectPath, existingImport.ContainedInProjectFileName);

				return true;
			}

			if (String.Compare (fullFileName, import.EvaluatedProjectPath) == 0) {
				LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
						"The main project file was imported here, which creates a circular " +
						"reference. Ignoring this import.");

				return true;
			}

			if (project_load_settings != ProjectLoadSettings.IgnoreMissingImports &&
			    !import.CheckEvaluatedProjectPathExists ())
				return false;

			Imports.Add (import);
			string importingFile = importingProject != null ? importingProject.FullFileName : FullFileName;
			ParentEngine.LogMessage (MessageImportance.Low,
					"{0}: Importing project {1} {2}",
					importingFile, import.EvaluatedProjectPath, from_source_msg);

			import.Evaluate (project_load_settings == ProjectLoadSettings.IgnoreMissingImports);
			return true;
		}

		void AddItemGroup (XmlElement xmlElement, ImportedProject importedProject)
		{
			BuildItemGroup big = new BuildItemGroup (xmlElement, this, importedProject, false);
			ItemGroups.Add (big);
		}
		
		void AddPropertyGroup (XmlElement xmlElement, ImportedProject importedProject)
		{
			BuildPropertyGroup bpg = new BuildPropertyGroup (xmlElement, this, importedProject, false);
			PropertyGroups.Add (bpg);
		}
		
		void AddChoose (XmlElement xmlElement, ImportedProject importedProject)
		{
			BuildChoose bc = new BuildChoose (xmlElement, this, importedProject);
			groupingCollection.Add (bc);
		}
		
		static void ValidationCallBack (object sender, ValidationEventArgs e)
		{
			Console.WriteLine ("Validation Error: {0}", e.Message);
		}
		
		public bool BuildEnabled {
			get {
				return buildEnabled;
			}
			set {
				buildEnabled = value;
			}
		}

		[MonoTODO]
		public Encoding Encoding {
			get { return encoding; }
		}

		public string DefaultTargets {
			get {
				return String.Join ("; ", defaultTargets);
			}
			set {
				xmlDocument.DocumentElement.SetAttribute ("DefaultTargets", value);
				if (value != null)
					defaultTargets = value.Split (new char [] {';', ' '},
							StringSplitOptions.RemoveEmptyEntries);
			}
		}

		public BuildItemGroup EvaluatedItems {
			get {
				if (needToReevaluate) {
					needToReevaluate = false;
					Reevaluate ();
				}
				return evaluatedItems;
			}
		}

		public BuildItemGroup EvaluatedItemsIgnoringCondition {
			get {
				if (needToReevaluate) {
					needToReevaluate = false;
					Reevaluate ();
				}
				return evaluatedItemsIgnoringCondition;
			}
		}
		
		internal IDictionary <string, BuildItemGroup> EvaluatedItemsByName {
			get {
				// FIXME: do we need to do this here?
				if (needToReevaluate) {
					needToReevaluate = false;
					Reevaluate ();
				}
				return evaluatedItemsByName;
			}
		}

		internal IEnumerable EvaluatedItemsByNameAsDictionaryEntries {
			get {
				if (EvaluatedItemsByName.Count == 0)
					yield break;

				foreach (KeyValuePair<string, BuildItemGroup> pair in EvaluatedItemsByName) {
					foreach (BuildItem bi in pair.Value)
						yield return new DictionaryEntry (pair.Key, bi.ConvertToITaskItem (null, ExpressionOptions.ExpandItemRefs));
				}
			}
		}

		internal IDictionary <string, BuildItemGroup> EvaluatedItemsByNameIgnoringCondition {
			get {
				// FIXME: do we need to do this here?
				if (needToReevaluate) {
					needToReevaluate = false;
					Reevaluate ();
				}
				return evaluatedItemsByNameIgnoringCondition;
			}
		}

		// For batching implementation
		Dictionary<string, BuildItemGroup> perBatchItemsByName;
		Dictionary<string, BuildItemGroup> commonItemsByName;

		struct Batch {
			public Dictionary<string, BuildItemGroup> perBatchItemsByName;
			public Dictionary<string, BuildItemGroup> commonItemsByName;

			public Batch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
			{
				this.perBatchItemsByName = perBatchItemsByName;
				this.commonItemsByName = commonItemsByName;
			}
		}

		Stack<Batch> Batches {
			get { return batches; }
		}

		internal void PushBatch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
		{
			batches.Push (new Batch (perBatchItemsByName, commonItemsByName));
			SetBatchedItems (perBatchItemsByName, commonItemsByName);
		}

		internal void PopBatch ()
		{
			batches.Pop ();
			if (batches.Count > 0) {
				Batch b = batches.Peek ();
				SetBatchedItems (b.perBatchItemsByName, b.commonItemsByName);
			} else {
				SetBatchedItems (null, null);
			}
		}

		void SetBatchedItems (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
		{
			this.perBatchItemsByName = perBatchItemsByName;
			this.commonItemsByName = commonItemsByName;
		}

		// Honors batching
		internal bool TryGetEvaluatedItemByNameBatched (string itemName, out BuildItemGroup group)
		{
			if (perBatchItemsByName != null && perBatchItemsByName.TryGetValue (itemName, out group))
				return true;

			if (commonItemsByName != null && commonItemsByName.TryGetValue (itemName, out group))
				return true;

			group = null;
			return EvaluatedItemsByName.TryGetValue (itemName, out group);
		}

		internal string GetMetadataBatched (string itemName, string metadataName)
		{
			BuildItemGroup group = null;
			if (itemName == null) {
				//unqualified, all items in a batch(bucket) have the
				//same metadata values
				group = GetFirst<BuildItemGroup> (perBatchItemsByName.Values);
				if (group == null)
					group = GetFirst<BuildItemGroup> (commonItemsByName.Values);
			} else {
				//qualified
				TryGetEvaluatedItemByNameBatched (itemName, out group);
			}

			if (group != null) {
				foreach (BuildItem item in group) {
					if (item.HasMetadata (metadataName))
						return item.GetEvaluatedMetadata (metadataName);
				}
			}
			return String.Empty;
		}

		internal IEnumerable<BuildItemGroup> GetAllItemGroups ()
		{
			if (perBatchItemsByName == null && commonItemsByName == null)
				foreach (BuildItemGroup group in EvaluatedItemsByName.Values)
					yield return group;

			if (perBatchItemsByName != null)
				foreach (BuildItemGroup group in perBatchItemsByName.Values)
					yield return group;

			if (commonItemsByName != null)
				foreach (BuildItemGroup group in commonItemsByName.Values)
					yield return group;
		}

		T GetFirst<T> (ICollection<T> list)
		{
			if (list == null)
				return default (T);

			foreach (T t in list)
				return t;

			return default (T);
		}

		internal string ThisFileFullPath {
			get { return this_file_property_stack.Peek (); }
		}

		// Used for MSBuild*This* set of properties
		internal void PushThisFileProperty (string full_filename)
		{
			string last_file = this_file_property_stack.Count == 0 ? String.Empty : this_file_property_stack.Peek ();
			this_file_property_stack.Push (full_filename);
			if (last_file != full_filename)
				// first time, or different from previous one
				SetMSBuildThisFileProperties (full_filename);
		}

		internal void PopThisFileProperty ()
		{
			string last_file = this_file_property_stack.Pop ();
			if (this_file_property_stack.Count > 0 && last_file != this_file_property_stack.Peek ())
				SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
		}

		void SetMSBuildThisFileProperties (string full_filename)
		{
			if (String.IsNullOrEmpty (full_filename))
				return;

			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFile", Path.GetFileName (full_filename), PropertyType.Reserved));
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileFullPath", full_filename, PropertyType.Reserved));
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileName", Path.GetFileNameWithoutExtension (full_filename), PropertyType.Reserved));
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileExtension", Path.GetExtension (full_filename), PropertyType.Reserved));

			string project_dir = Path.GetDirectoryName (full_filename) + Path.DirectorySeparatorChar;
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectory", project_dir, PropertyType.Reserved));
			evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectoryNoRoot",
						project_dir.Substring (Path.GetPathRoot (project_dir).Length),
						PropertyType.Reserved));
		}


		internal void LogWarning (string filename, string message, params object[] messageArgs)
		{
			BuildWarningEventArgs bwea = new BuildWarningEventArgs (
				null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
				null, null);
			ParentEngine.EventSource.FireWarningRaised (this, bwea);
		}

		internal void LogError (string filename, string message,
				     params object[] messageArgs)
		{
			BuildErrorEventArgs beea = new BuildErrorEventArgs (
				null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
				null, null);
			ParentEngine.EventSource.FireErrorRaised (this, beea);
		}

		internal static string DefaultExtensionsPath {
			get {
				if (extensions_path == null) {
					// NOTE: code from mcs/tools/gacutil/driver.cs
					PropertyInfo gac = typeof (System.Environment).GetProperty (
							"GacPath", BindingFlags.Static | BindingFlags.NonPublic);

					if (gac != null) {
						MethodInfo get_gac = gac.GetGetMethod (true);
						string gac_path = (string) get_gac.Invoke (null, null);
						extensions_path = Path.GetFullPath (Path.Combine (
									gac_path, Path.Combine ("..", "xbuild")));
					}
				}
				return extensions_path;
			}
		}

		public BuildPropertyGroup EvaluatedProperties {
			get {
				if (needToReevaluate) {
					needToReevaluate = false;
					Reevaluate ();
				}
				return evaluatedProperties;
			}
		}

		internal IEnumerable EvaluatedPropertiesAsDictionaryEntries {
			get {
				foreach (BuildProperty bp in EvaluatedProperties)
					yield return new DictionaryEntry (bp.Name, bp.Value);
			}
		}

		public string FullFileName {
			get { return fullFileName; }
			set { fullFileName = value; }
		}

		public BuildPropertyGroup GlobalProperties {
			get { return globalProperties; }
			set {
				if (value == null)
					throw new ArgumentNullException ("value");
				
				if (value.FromXml)
					throw new InvalidOperationException ("GlobalProperties can not be set to persisted property group.");
				
				globalProperties = value;
			}
		}

		public bool IsDirty {
			get { return isDirty; }
		}

		public bool IsValidated {
			get { return isValidated; }
			set { isValidated = value; }
		}

		public BuildItemGroupCollection ItemGroups {
			get { return itemGroups; }
		}
		
		public ImportCollection Imports {
			get { return imports; }
		}
		
		public string InitialTargets {
			get {
				return String.Join ("; ", initialTargets.ToArray ());
			}
			set {
				initialTargets.Clear ();
				xmlDocument.DocumentElement.SetAttribute ("InitialTargets", value);
				if (value != null)
					initialTargets.AddRange (value.Split (
								new char [] {';', ' '}, StringSplitOptions.RemoveEmptyEntries));
			}
		}

		public Engine ParentEngine {
			get { return parentEngine; }
		}

		public BuildPropertyGroupCollection PropertyGroups {
			get { return propertyGroups; }
		}

		public string SchemaFile {
			get { return schemaFile; }
			set { schemaFile = value; }
		}

		public TargetCollection Targets {
			get { return targets; }
		}

		public DateTime TimeOfLastDirty {
			get { return timeOfLastDirty; }
		}
		
		public UsingTaskCollection UsingTasks {
			get { return usingTasks; }
		}

		[MonoTODO]
		public string Xml {
			get { return xmlDocument.InnerXml; }
		}

		// corresponds to the xml attribute
		public string DefaultToolsVersion {
			get {
				if (xmlDocument != null)
					return xmlDocument.DocumentElement.GetAttribute ("ToolsVersion");
				return null;
			}
			set {
				if (xmlDocument != null)
					xmlDocument.DocumentElement.SetAttribute ("ToolsVersion", value);
			}
		}

		public bool HasToolsVersionAttribute {
			get {
				return xmlDocument != null && xmlDocument.DocumentElement.HasAttribute ("ToolsVersion");
			}
		}

		public string ToolsVersion {
			get; internal set;
		}

		internal Dictionary <string, BuildItemGroup> LastItemGroupContaining {
			get { return last_item_group_containing; }
		}
		
		internal ProjectLoadSettings ProjectLoadSettings {
			get { return project_load_settings; }
			set { project_load_settings = value; }
		}

		internal static XmlNamespaceManager XmlNamespaceManager {
			get {
				if (manager == null) {
					manager = new XmlNamespaceManager (new NameTable ());
					manager.AddNamespace ("tns", ns);
				}
				
				return manager;
			}
		}
		
		internal TaskDatabase TaskDatabase {
			get { return taskDatabase; }
		}
		
		internal XmlDocument XmlDocument {
			get { return xmlDocument; }
		}
		
		internal static string XmlNamespace {
			get { return ns; }
		}

		static string OS {
			get {
				PlatformID pid = Environment.OSVersion.Platform;
				switch ((int)pid) {
				case 128:
				case 4:
					return "Unix";
				case 6:
					return "OSX";
				default:
					return "Windows_NT";
				}
			}
		}

	}
}