//
// ResolveAssemblyReference.cs: Searches for assembly files.
//
// Author:
//   Marek Sieradzki (marek.sieradzki@gmail.com)
//   Ankit Jain (jankit@novell.com)
// 
// (C) 2006 Marek Sieradzki
// Copyright 2009 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.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.Build.Tasks {
	public class ResolveAssemblyReference : TaskExtension {
	
		bool		autoUnify;
		ITaskItem[]	assemblyFiles;
		ITaskItem[]	assemblies;
		string		appConfigFile;
		string[]	allowedAssemblyExtensions;
		string[]	allowedRelatedFileExtensions;
		string[]	candidateAssemblyFiles;
		ITaskItem[]	copyLocalFiles;
		ITaskItem[]	filesWritten;
		bool		findDependencies;
		bool		findRelatedFiles;
		bool		findSatellites;
		bool		findSerializationAssemblies;
		ITaskItem[]	relatedFiles;
		ITaskItem[]	resolvedDependencyFiles;
		ITaskItem[]	resolvedFiles;
		ITaskItem[]	satelliteFiles;
		ITaskItem[]	scatterFiles;
		string[]	searchPaths;
		ITaskItem[]	serializationAssemblyFiles;
		bool 		silent;
		string		stateFile;
		ITaskItem[]	suggestedRedirects;
		string[]	targetFrameworkDirectories;
		string		targetProcessorArchitecture;
		static string []	default_assembly_extensions;

		AssemblyResolver	assembly_resolver;
		List<string> dependency_search_paths;
		Dictionary<string, ResolvedReference> assemblyNameToResolvedRef;
		Dictionary<string, ITaskItem>	tempSatelliteFiles, tempRelatedFiles,
			tempResolvedDepFiles, tempCopyLocalFiles;
		List<ITaskItem> tempResolvedFiles;
		List<PrimaryReference> primaryReferences;
		Dictionary<string, string> alreadyScannedAssemblyNames;
		Dictionary<string, string> conflictWarningsCache;

		//FIXME: construct and use a graph of the dependencies, useful across projects

		static ResolveAssemblyReference ()
		{
			default_assembly_extensions = new string [] { ".dll", ".exe" };
		}

		public ResolveAssemblyReference ()
		{
			assembly_resolver = new AssemblyResolver ();
		}

		//FIXME: make this reusable
		public override bool Execute ()
		{
			if (assemblies == null && assemblyFiles == null)
				// nothing to resolve
				return true;

			LogTaskParameters ();

			assembly_resolver.Log = Log;
			tempResolvedFiles = new List<ITaskItem> ();
			tempCopyLocalFiles = new Dictionary<string, ITaskItem> ();
			tempSatelliteFiles = new Dictionary<string, ITaskItem> ();
			tempRelatedFiles = new Dictionary<string, ITaskItem> ();
			tempResolvedDepFiles = new Dictionary<string, ITaskItem> ();

			primaryReferences = new List<PrimaryReference> ();
			assemblyNameToResolvedRef = new Dictionary<string, ResolvedReference> ();
			conflictWarningsCache = new Dictionary<string, string> ();

			ResolveAssemblies ();
			ResolveAssemblyFiles ();
			resolvedFiles = tempResolvedFiles.ToArray ();

			alreadyScannedAssemblyNames = new Dictionary<string, string> ();

			// the first element is place holder for parent assembly's dir
			dependency_search_paths = new List<string> () { String.Empty };
			dependency_search_paths.AddRange (searchPaths);

			// resolve dependencies
			foreach (PrimaryReference pref in primaryReferences)
				ResolveAssemblyFileDependencies (pref.TaskItem, pref.ParentCopyLocal);

			copyLocalFiles = tempCopyLocalFiles.Values.ToArray ();
			satelliteFiles = tempSatelliteFiles.Values.ToArray ();
			relatedFiles = tempRelatedFiles.Values.ToArray ();
			resolvedDependencyFiles = tempResolvedDepFiles.Values.ToArray ();

#if XBUILD_14
			DependsOnSystemRuntime = resolvedDependencyFiles.Any (x => Path.GetFileName (x.ItemSpec) == "System.Runtime.dll").ToString ();
#endif

			tempResolvedFiles.Clear ();
			tempCopyLocalFiles.Clear ();
			tempSatelliteFiles.Clear ();
			tempRelatedFiles.Clear ();
			tempResolvedDepFiles.Clear ();
			alreadyScannedAssemblyNames.Clear ();
			primaryReferences.Clear ();
			assemblyNameToResolvedRef.Clear ();
			conflictWarningsCache.Clear ();
			dependency_search_paths = null;

			return true;
		}

		void ResolveAssemblies ()
		{
			if (assemblies == null || assemblies.Length == 0)
				return;

			foreach (ITaskItem item in assemblies) {
				if (!String.IsNullOrEmpty (item.GetMetadata ("SubType"))) {
					Log.LogWarning ("Reference '{0}' has non-empty SubType. Ignoring.", item.ItemSpec);
					continue;
				}

				LogWithPrecedingNewLine (MessageImportance.Low, "Primary Reference {0}", item.ItemSpec);
				ResolvedReference resolved_ref = ResolveReference (item, searchPaths, true);
				if (resolved_ref == null)
					resolved_ref = ResolveWithAlternateName (item, allowedAssemblyExtensions ?? default_assembly_extensions);

				if (resolved_ref == null) {
					Log.LogWarning ("Reference '{0}' not resolved", item.ItemSpec);
					assembly_resolver.LogSearchLoggerMessages (MessageImportance.Normal);
				} else {
					Log.LogMessage (MessageImportance.Low,
							"\tReference {0} resolved to {1}. CopyLocal = {2}",
							item.ItemSpec, resolved_ref.TaskItem,
							resolved_ref.TaskItem.GetMetadata ("CopyLocal"));

					Log.LogMessage (MessageImportance.Low,
							"\tReference found at search path {0}",
							resolved_ref.FoundInSearchPathAsString);

					assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low);

					if (TryAddNewReference (tempResolvedFiles, resolved_ref) &&
						!IsFromGacOrTargetFramework (resolved_ref) &&
						resolved_ref.FoundInSearchPath != SearchPath.PkgConfig) {
						primaryReferences.Add (new PrimaryReference (
								resolved_ref.TaskItem,
								resolved_ref.TaskItem.GetMetadata ("CopyLocal")));
					}
				}
			}
		}


		ResolvedReference ResolveWithAlternateName (ITaskItem item, string[] extensions)
		{
			foreach (string extn in extensions) {
				if (item.ItemSpec.EndsWith (extn)) {
					ITaskItem altitem = new TaskItem (item.ItemSpec.Substring (0, item.ItemSpec.Length - extn.Length));
					item.CopyMetadataTo (altitem);
					return ResolveReference (altitem, searchPaths, true);
				}
			}
			return null;
		}

		// Use @search_paths to resolve the reference
		ResolvedReference ResolveReference (ITaskItem item, IEnumerable<string> search_paths, bool set_copy_local)
		{
			ResolvedReference resolved = null;
			bool specific_version;

			assembly_resolver.ResetSearchLogger ();

			if (!TryGetSpecificVersionValue (item, out specific_version))
				return null;

			var spath_index  = 0;
			foreach (string spath in search_paths) {
				if (string.IsNullOrEmpty (spath))
					continue;
				assembly_resolver.LogSearchMessage ("For searchpath {0}", spath);

				// The first value of search_paths can be the parent assembly directory.
				// In that case the value would be treated as a directory.
				// This code checks if we should treat the value as a TargetFramework assembly.
				// Doing so avoids CopyLocal beeing set to true.
				if (spath_index++ == 0 && targetFrameworkDirectories != null) {
					foreach (string fpath in targetFrameworkDirectories) {
						if (string.IsNullOrEmpty (fpath))
							continue;
						if (String.Compare (
								Path.GetFullPath (spath).TrimEnd (Path.DirectorySeparatorChar),
								Path.GetFullPath (fpath).TrimEnd (Path.DirectorySeparatorChar),
								StringComparison.InvariantCulture) != 0)
							continue;

						resolved = assembly_resolver.FindInTargetFramework (item,
							fpath, specific_version);

						break;
					}

					if  (resolved != null)
						break;
				}

				if (String.Compare (spath, "{HintPathFromItem}") == 0) {
					resolved = assembly_resolver.ResolveHintPathReference (item, specific_version);
				} else if (String.Compare (spath, "{TargetFrameworkDirectory}") == 0) {
					if (targetFrameworkDirectories == null)
						continue;
					foreach (string fpath in targetFrameworkDirectories) {
						resolved = assembly_resolver.FindInTargetFramework (item,
								fpath, specific_version);
						if (resolved != null)
							break;
					}
				} else if (String.Compare (spath, "{GAC}") == 0) {
					resolved = assembly_resolver.ResolveGacReference (item, specific_version);
				} else if (String.Compare (spath, "{RawFileName}") == 0) {
					//FIXME: identify assembly names, as extract the name, and try with that?
					AssemblyName aname;
					if (assembly_resolver.TryGetAssemblyNameFromFile (item.ItemSpec, out aname))
						resolved = assembly_resolver.GetResolvedReference (item, item.ItemSpec, aname, true,
								SearchPath.RawFileName);
				} else if (String.Compare (spath, "{CandidateAssemblyFiles}") == 0) {
					assembly_resolver.LogSearchMessage (
							"Warning: {{CandidateAssemblyFiles}} not supported currently");
				} else if (String.Compare (spath, "{PkgConfig}") == 0) {
					resolved = assembly_resolver.ResolvePkgConfigReference (item, specific_version);
				} else {
					resolved = assembly_resolver.FindInDirectory (
							item, spath,
							allowedAssemblyExtensions ?? default_assembly_extensions,
							specific_version);
				}

				if (resolved != null)
					break;
			}

			if (resolved != null && set_copy_local)
				SetCopyLocal (resolved.TaskItem, resolved.CopyLocal.ToString ());

			return resolved;
		}

		bool TryGetSpecificVersionValue (ITaskItem item, out bool specific_version)
		{
			specific_version = true;
			string value = item.GetMetadata ("SpecificVersion");
			if (String.IsNullOrEmpty (value)) {
				//AssemblyName name = new AssemblyName (item.ItemSpec);
				// If SpecificVersion is not specified, then
				// it is true if the Include is a strong name else false
				//specific_version = assembly_resolver.IsStrongNamed (name);

				// msbuild seems to just look for a ',' in the name :/
				specific_version = item.ItemSpec.IndexOf (',') >= 0;
				return true;
			}

			if (Boolean.TryParse (value, out specific_version))
				return true;

			Log.LogError ("Item '{0}' has attribute SpecificVersion with invalid value '{1}' " +
					"which could not be converted to a boolean.", item.ItemSpec, value);
			return false;
		}

		//FIXME: Consider CandidateAssemblyFiles also here
		void ResolveAssemblyFiles ()
		{
			if (assemblyFiles == null)
				return;

			foreach (ITaskItem item in assemblyFiles) {
				assembly_resolver.ResetSearchLogger ();

				if (!File.Exists (item.ItemSpec)) {
					LogWithPrecedingNewLine (MessageImportance.Low,
							"Primary Reference from AssemblyFiles {0}, file not found. Ignoring",
							item.ItemSpec);
					continue;
				}

				LogWithPrecedingNewLine (MessageImportance.Low, "Primary Reference from AssemblyFiles {0}", item.ItemSpec);
				string copy_local;

				AssemblyName aname;
				if (!assembly_resolver.TryGetAssemblyNameFromFile (item.ItemSpec, out aname)) {
					Log.LogWarning ("Reference '{0}' not resolved", item.ItemSpec);
					assembly_resolver.LogSearchLoggerMessages (MessageImportance.Normal);
					continue;
				}

				assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low);

				ResolvedReference rr = assembly_resolver.GetResolvedReference (item, item.ItemSpec, aname, true,
						SearchPath.RawFileName);
				copy_local = rr.CopyLocal.ToString ();

				if (!TryAddNewReference (tempResolvedFiles, rr))
					// already resolved
					continue;

				SetCopyLocal (rr.TaskItem, copy_local);

				FindAndAddRelatedFiles (item.ItemSpec, copy_local);
				FindAndAddSatellites (item.ItemSpec, copy_local);

				if (FindDependencies && !IsFromGacOrTargetFramework (rr) &&
						rr.FoundInSearchPath != SearchPath.PkgConfig)
					primaryReferences.Add (new PrimaryReference (item, copy_local));
			}
		}

		// Tries to resolve assemblies referenced by @item
		// Skips gac references
		// @item : filename
		void ResolveAssemblyFileDependencies (ITaskItem item, string parent_copy_local)
		{
			Queue<string> dependencies = new Queue<string> ();
			dependencies.Enqueue (item.ItemSpec);

			while (dependencies.Count > 0) {
				string filename = Path.GetFullPath (dependencies.Dequeue ());
				Assembly asm = Assembly.ReflectionOnlyLoadFrom (filename);
				if (alreadyScannedAssemblyNames.ContainsKey (asm.FullName))
					continue;

				// set the 1st search path to this ref's base path
				// Will be used for resolving the dependencies
				dependency_search_paths [0] = Path.GetDirectoryName (filename);

				foreach (AssemblyName aname in asm.GetReferencedAssemblies ()) {
					if (alreadyScannedAssemblyNames.ContainsKey (aname.FullName))
						continue;

					ResolvedReference resolved_ref = ResolveDependencyByAssemblyName (
						aname, asm.FullName, parent_copy_local);

					if (IncludeDependencies (resolved_ref, aname.FullName)) {
						tempResolvedDepFiles[resolved_ref.AssemblyName.FullName] = resolved_ref.TaskItem;
						dependencies.Enqueue (resolved_ref.TaskItem.ItemSpec);
					}
				}
				alreadyScannedAssemblyNames.Add (asm.FullName, String.Empty);
			}
		}

		// Resolves by looking dependency_search_paths
		// which is dir of parent reference file, and
		// SearchPaths
		ResolvedReference ResolveDependencyByAssemblyName (AssemblyName aname, string parent_asm_name,
				string parent_copy_local)
		{
			// This will check for compatible assembly name/version
			ResolvedReference resolved_ref;
			if (TryGetResolvedReferenceByAssemblyName (aname, false, out resolved_ref))
				return resolved_ref;

			LogWithPrecedingNewLine (MessageImportance.Low, "Dependency {0}", aname);
			Log.LogMessage (MessageImportance.Low, "\tRequired by {0}", parent_asm_name);

			ITaskItem item = new TaskItem (aname.FullName);
			item.SetMetadata ("SpecificVersion", "false");
			resolved_ref = ResolveReference (item, dependency_search_paths, false);

			if (resolved_ref != null) {
				Log.LogMessage (MessageImportance.Low, "\tReference {0} resolved to {1}.",
					aname, resolved_ref.TaskItem.ItemSpec);

				Log.LogMessage (MessageImportance.Low,
						"\tReference found at search path {0}",
						resolved_ref.FoundInSearchPathAsString);

				assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low);

				if (resolved_ref.FoundInSearchPath == SearchPath.Directory) {
					// override CopyLocal with parent's val
					SetCopyLocal (resolved_ref.TaskItem, parent_copy_local);

					Log.LogMessage (MessageImportance.Low,
							"\tThis is CopyLocal {0} as parent item has this value",
							parent_copy_local);

					if (TryAddNewReference (tempResolvedFiles, resolved_ref)) {
						FindAndAddRelatedFiles (resolved_ref.TaskItem.ItemSpec, parent_copy_local);
						FindAndAddSatellites (resolved_ref.TaskItem.ItemSpec, parent_copy_local);
					}
				} else {
					//gac or tgtfmwk
					Log.LogMessage (MessageImportance.Low,
							"\tThis is CopyLocal false as it is in the GAC," +
							"target framework directory or provided by a package.");

					TryAddNewReference (tempResolvedFiles, resolved_ref);
				}
			} else {
				Log.LogMessage (MessageImportance.Low, "Could not resolve the assembly \"{0}\".", aname);
				assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low);
			}

			return resolved_ref;
		}

		void FindAndAddRelatedFiles (string filename, string parent_copy_local)
		{
			if (!findRelatedFiles || allowedRelatedFileExtensions == null)
				return;

			foreach (string ext in allowedRelatedFileExtensions) {
				string rfile = Path.ChangeExtension (filename, ext);
				if (File.Exists (rfile)) {
					ITaskItem item = new TaskItem (rfile);
					SetCopyLocal (item, parent_copy_local);

					tempRelatedFiles.AddUniqueFile (item);
				}
			}
		}

		void FindAndAddSatellites (string filename, string parent_copy_local)
		{
			if (!FindSatellites)
				return;

			string basepath = Path.GetDirectoryName (filename);
			string resource = String.Format ("{0}{1}{2}",
					Path.GetFileNameWithoutExtension (filename),
					".resources",
					Path.GetExtension (filename));

			string dir_sep = Path.DirectorySeparatorChar.ToString ();
			foreach (string dir in Directory.GetDirectories (basepath)) {
				string culture = Path.GetFileName (dir);
				if (!CultureNamesTable.ContainsKey (culture))
					continue;

				string res_path = Path.Combine (dir, resource);
				if (File.Exists (res_path)) {
					ITaskItem item = new TaskItem (res_path);
					SetCopyLocal (item, parent_copy_local);
					item.SetMetadata ("DestinationSubdirectory", culture + dir_sep);
					tempSatelliteFiles.AddUniqueFile (item);
				}
			}
		}

		// returns true is it was new
		bool TryAddNewReference (List<ITaskItem> file_list, ResolvedReference key_ref)
		{
			ResolvedReference found_ref;
			if (!TryGetResolvedReferenceByAssemblyName (key_ref.AssemblyName, key_ref.IsPrimary, out found_ref)) {
				assemblyNameToResolvedRef [key_ref.AssemblyName.Name] = key_ref;
				file_list.Add (key_ref.TaskItem);

				return true;
			}
			return false;
		}

		void SetCopyLocal (ITaskItem item, string copy_local)
		{
			item.SetMetadata ("CopyLocal", copy_local);

			// Assumed to be valid value
			if (Boolean.Parse (copy_local))
				tempCopyLocalFiles.AddUniqueFile (item);
		}

		bool TryGetResolvedReferenceByAssemblyName (AssemblyName key_aname, bool is_primary, out ResolvedReference found_ref)
		{
			found_ref = null;
			// Match by just name
			if (!assemblyNameToResolvedRef.TryGetValue (key_aname.Name, out found_ref))
				// not there
				return false;

			// match for full name
			if (AssemblyResolver.AssemblyNamesCompatible (key_aname, found_ref.AssemblyName, true, false))
				// exact match, so its already there, dont add anything
				return true;

			// we have a name match, but version mismatch!
			assembly_resolver.LogSearchMessage ("A conflict was detected between '{0}' and '{1}'",
					key_aname.FullName, found_ref.AssemblyName.FullName);

			if (is_primary == found_ref.IsPrimary) {
				assembly_resolver.LogSearchMessage ("Unable to choose between the two. " +
						"Choosing '{0}' arbitrarily.", found_ref.AssemblyName.FullName);
				return true;
			}

			// since all dependencies are processed after
			// all primary refererences, the one in the cache
			// has to be a primary
			// Prefer a primary reference over a dependency

			assembly_resolver.LogSearchMessage ("Choosing '{0}' as it is a primary reference.",
					found_ref.AssemblyName.FullName);

			// If we can successfully use the primary reference, don't log a warning. It's too
			// verbose.
			//LogConflictWarning (found_ref.AssemblyName.FullName, key_aname.FullName);

			return true;
		}

		void LogWithPrecedingNewLine (MessageImportance importance, string format, params object [] args)
		{
			Log.LogMessage (importance, String.Empty);
			Log.LogMessage (importance, format, args);
		}

		// conflict b/w @main and @conflicting, picking @main
		void LogConflictWarning (string main, string conflicting)
		{
			string key = main + ":" + conflicting;
			if (!conflictWarningsCache.ContainsKey (key)) {
				Log.LogWarning ("Found a conflict between : '{0}' and '{1}'. Using '{0}' reference.",
						main, conflicting);
				conflictWarningsCache [key] = key;
			}
		}

		bool IsFromGacOrTargetFramework (ResolvedReference rr)
		{
			return rr.FoundInSearchPath == SearchPath.Gac ||
				rr.FoundInSearchPath == SearchPath.TargetFrameworkDirectory;
		}

		bool IncludeDependencies (ResolvedReference rr, string aname)
		{
			if (rr == null)
				return false;
			if (aname.Equals ("System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"))
				return true;
			return !IsFromGacOrTargetFramework (rr) && rr.FoundInSearchPath != SearchPath.PkgConfig;
		}

		void LogTaskParameters ()
		{
			Log.LogMessage (MessageImportance.Low, "TargetFrameworkDirectories:");
			if (TargetFrameworkDirectories != null)
				foreach (string dir in TargetFrameworkDirectories)
					Log.LogMessage (MessageImportance.Low, "\t{0}", dir);

			Log.LogMessage (MessageImportance.Low, "SearchPaths:");
			if (SearchPaths != null)
				foreach (string path in SearchPaths)
					Log.LogMessage (MessageImportance.Low, "\t{0}", path);
		}

		public bool AutoUnify {
			get { return autoUnify; }
			set { autoUnify = value; }
		}
		
		public ITaskItem[] AssemblyFiles {
			get { return assemblyFiles; }
			set { assemblyFiles = value; }
		}
		
		public ITaskItem[] Assemblies {
			get { return assemblies; }
			set { assemblies = value; }
		}
		
		public string AppConfigFile {
			get { return appConfigFile; }
			set { appConfigFile = value; }
		}
		
		public string[] AllowedAssemblyExtensions {
			get { return allowedAssemblyExtensions; }
			set { allowedAssemblyExtensions = value; }
		}

		public string[] AllowedRelatedFileExtensions {
			get { return allowedRelatedFileExtensions; }
			set { allowedRelatedFileExtensions = value; }
		}
		
		public string[] CandidateAssemblyFiles {
			get { return candidateAssemblyFiles; }
			set { candidateAssemblyFiles = value; }
		}
		
		[Output]
		public ITaskItem[] CopyLocalFiles {
			get { return copyLocalFiles; }
		}

#if XBUILD_14
		[Output]
		public string DependsOnSystemRuntime {
			get; private set;
		}
#endif
		
		[Output]
		public ITaskItem[] FilesWritten {
			get { return filesWritten; }
			set { filesWritten = value; }
		}
		
		public bool FindDependencies {
			get { return findDependencies; }
			set { findDependencies = value; }
		}
		
		public bool FindRelatedFiles {
			get { return findRelatedFiles; }
			set { findRelatedFiles = value; }
		}
		
		public bool FindSatellites {
			get { return findSatellites; }
			set { findSatellites = value; }
		}
		
		public bool FindSerializationAssemblies {
			get { return findSerializationAssemblies; }
			set { findSerializationAssemblies = value; }
		}

		public
		ITaskItem[]
		InstalledAssemblyTables { get; set; }

		[Output]
		public ITaskItem[] RelatedFiles {
			get { return relatedFiles; }
		}
		
		[Output]
		public ITaskItem[] ResolvedDependencyFiles {
			get { return resolvedDependencyFiles; }
		}
		
		[Output]
		public ITaskItem[] ResolvedFiles {
			get { return resolvedFiles; }
		}
		
		[Output]
		public ITaskItem[] SatelliteFiles {
			get { return satelliteFiles; }
		}
		
		[Output]
		public ITaskItem[] ScatterFiles {
			get { return scatterFiles; }
		}
		
		[Required]
		public string[] SearchPaths {
			get { return searchPaths; }
			set { searchPaths = value; }
		}
		
		[Output]
		public ITaskItem[] SerializationAssemblyFiles {
			get { return serializationAssemblyFiles; }
		}
		
		public bool Silent {
			get { return silent; }
			set { silent = value; }
		}
		
		public string StateFile {
			get { return stateFile; }
			set { stateFile = value; }
		}
		
		[Output]
		public ITaskItem[] SuggestedRedirects {
			get { return suggestedRedirects; }
		}

		public string TargetFrameworkMoniker { get; set; }

		public string TargetFrameworkMonikerDisplayName { get; set; }

		public string TargetFrameworkVersion { get; set; }

		public string[] TargetFrameworkDirectories {
			get { return targetFrameworkDirectories; }
			set { targetFrameworkDirectories = value; }
		}
		
		public string TargetProcessorArchitecture {
			get { return targetProcessorArchitecture; }
			set { targetProcessorArchitecture = value; }
		}


                static Dictionary<string, string> cultureNamesTable;
                static Dictionary<string, string> CultureNamesTable {
                        get {
                                if (cultureNamesTable == null) {
                                        cultureNamesTable = new Dictionary<string, string> ();
                                        foreach (CultureInfo ci in CultureInfo.GetCultures (CultureTypes.AllCultures))
                                                cultureNamesTable [ci.Name] = ci.Name;
                                }

                                return cultureNamesTable;
                        }
                }
	}

	static class ResolveAssemblyReferenceHelper {
		public static void AddUniqueFile (this Dictionary<string, ITaskItem> dic, ITaskItem item)
		{
			if (dic == null)
				throw new ArgumentNullException ("dic");
			if (item == null)
				throw new ArgumentNullException ("item");

			string fullpath = Path.GetFullPath (item.ItemSpec);
			if (!dic.ContainsKey (fullpath))
				dic [fullpath] = item;
		}
	}

	struct PrimaryReference {
		public ITaskItem TaskItem;
		public string ParentCopyLocal;

		public PrimaryReference (ITaskItem item, string parent_copy_local)
		{
			TaskItem = item;
			ParentCopyLocal = parent_copy_local;
		}
	}

}
