//
// BaseAssemblyResolver.cs
//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// (C) 2005 Jb Evain
//
// 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.
//

namespace Mono.Cecil {

	using System;
	using System.Collections;
	using System.IO;
	using SR = System.Reflection;
	using System.Text;

	public abstract class BaseAssemblyResolver : IAssemblyResolver {

		ArrayList m_directories;
		string[] m_monoGacPaths;

		public void AddSearchDirectory (string directory)
		{
			m_directories.Add (directory);
		}

		public void RemoveSearchDirectory (string directory)
		{
			m_directories.Remove (directory);
		}

		public string [] GetSearchDirectories ()
		{
			return (string []) m_directories.ToArray (typeof (string));
		}

		public virtual AssemblyDefinition Resolve (string fullName)
		{
			return Resolve (AssemblyNameReference.Parse (fullName));
		}

		public BaseAssemblyResolver ()
		{
			m_directories = new ArrayList ();
			m_directories.Add (".");
			m_directories.Add ("bin");
		}

		public virtual AssemblyDefinition Resolve (AssemblyNameReference name)
		{
			AssemblyDefinition assembly;
			string frameworkdir = Path.GetDirectoryName (typeof (object).Module.FullyQualifiedName);

			assembly = SearchDirectory (name, m_directories);
			if (assembly != null)
				return assembly;

			if (IsZero (name.Version)) {
				assembly = SearchDirectory (name, new string [] {frameworkdir});
				if (assembly != null)
					return assembly;
			}

#if !CF_1_0 && !CF_2_0
			if (name.Name == "mscorlib") {
				assembly = GetCorlib (name);
				if (assembly != null)
					return assembly;
			}

			assembly = GetAssemblyInGac (name);
			if (assembly != null)
				return assembly;
#endif

			assembly = SearchDirectory (name, new string [] {frameworkdir});
			if (assembly != null)
				return assembly;

			throw new FileNotFoundException ("Could not resolve: " + name);
		}

		static readonly string [] _extentions = new string [] { ".dll", ".exe" };

		static AssemblyDefinition SearchDirectory (AssemblyNameReference name, IEnumerable directories)
		{
			foreach (string dir in directories) {
				foreach (string ext in _extentions) {
					string file = Path.Combine (dir, name.Name + ext);
					if (File.Exists (file))
						return AssemblyFactory.GetAssembly (file);
				}
			}

			return null;
		}

		static bool IsZero (Version version)
		{
			return version.Major == 0 && version.Minor == 0 && version.Build == 0 && version.Revision == 0;
		}

#if !CF_1_0 && !CF_2_0
		static AssemblyDefinition GetCorlib (AssemblyNameReference reference)
		{
			SR.AssemblyName corlib = typeof (object).Assembly.GetName ();
			if (corlib.Version == reference.Version || IsZero (reference.Version))
				return AssemblyFactory.GetAssembly (typeof (object).Module.FullyQualifiedName);

			string path = Directory.GetParent (
				Directory.GetParent (
					typeof (object).Module.FullyQualifiedName).FullName
				).FullName;

			string runtime_path = null;
			if (OnMono ()) {
				if (reference.Version.Major == 1)
					runtime_path = "1.0";
				else if (reference.Version.Major == 2) {
					if (reference.Version.Minor == 1)
						runtime_path = "2.1";
					else
						runtime_path = "2.0";
				} else if (reference.Version.Major == 4)
					runtime_path = "4.0";
			} else {
				switch (reference.Version.ToString ()) {
				case "1.0.3300.0":
					runtime_path = "v1.0.3705";
					break;
				case "1.0.5000.0":
					runtime_path = "v1.1.4322";
					break;
				case "2.0.0.0":
					runtime_path = "v2.0.50727";
					break;
				case "4.0.0.0":
					runtime_path = "v4.0.21006";
					break;
				}
			}

			if (runtime_path == null)
				throw new NotSupportedException ("Version not supported: " + reference.Version);

			path = Path.Combine (path, runtime_path);

			if (File.Exists (Path.Combine (path, "mscorlib.dll")))
				return AssemblyFactory.GetAssembly (Path.Combine (path, "mscorlib.dll"));

			return null;
		}

		public static bool OnMono ()
		{
			return typeof (object).Assembly.GetType ("System.MonoType", false) != null;
		}

		string[] MonoGacPaths {
			get {
				if (m_monoGacPaths == null)
					m_monoGacPaths = GetDefaultMonoGacPaths ();
				return m_monoGacPaths;	
			}
		}

		static string[] GetDefaultMonoGacPaths ()
		{
			ArrayList paths = new ArrayList ();
			string s = GetCurrentGacPath ();
			if (s != null)
				paths.Add (s);
			string gacPathsEnv = Environment.GetEnvironmentVariable ("MONO_GAC_PREFIX");
			if (gacPathsEnv != null && gacPathsEnv.Length > 0) {
				string[] gacPrefixes = gacPathsEnv.Split (Path.PathSeparator);
				foreach (string gacPrefix in gacPrefixes) {
					if (gacPrefix != null && gacPrefix.Length > 0) {
						string gac = Path.Combine (Path.Combine (Path.Combine (gacPrefix, "lib"), "mono"), "gac");
						if (Directory.Exists (gac) && !paths.Contains (gac))
							paths.Add (gac);
					}
				}
			}
			return (string[]) paths.ToArray (typeof (String));
		}

		AssemblyDefinition GetAssemblyInGac (AssemblyNameReference reference)
		{
			if (reference.PublicKeyToken == null || reference.PublicKeyToken.Length == 0)
				return null;

			if (OnMono ()) {
				foreach (string gacpath in MonoGacPaths) {
					string s = GetAssemblyFile (reference, gacpath);
					if (File.Exists (s))
						return AssemblyFactory.GetAssembly (s);
				}
			} else {
				string currentGac = GetCurrentGacPath ();
				if (currentGac == null)
					return null;

				string [] gacs = new string [] {"GAC_MSIL", "GAC_32", "GAC"};
				for (int i = 0; i < gacs.Length; i++) {
					string gac = Path.Combine (Directory.GetParent (currentGac).FullName, gacs [i]);
					string asm = GetAssemblyFile (reference, gac);
					if (Directory.Exists (gac) && File.Exists (asm))
						return AssemblyFactory.GetAssembly (asm);
				}
			}

			return null;
		}

		static string GetAssemblyFile (AssemblyNameReference reference, string gac)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (reference.Version);
			sb.Append ("__");
			for (int i = 0; i < reference.PublicKeyToken.Length; i++)
				sb.Append (reference.PublicKeyToken [i].ToString ("x2"));

			return Path.Combine (
				Path.Combine (
					Path.Combine (gac, reference.Name), sb.ToString ()),
					string.Concat (reference.Name, ".dll"));
		}

		static string GetCurrentGacPath ()
		{
			string file = typeof (Uri).Module.FullyQualifiedName;
			if (!File.Exists (file))
				return null;

			return Directory.GetParent (
				Directory.GetParent (
					Path.GetDirectoryName (
						file)
					).FullName
				).FullName;
		}
#endif
	}
}
