context: // dnlib: See LICENSE.txt for more info
  
  // See coreclr/src/vm/siginfo.cpp
  
  using System;
  using System.Diagnostics;
  
  namespace dnlib.DotNet {
  	/// <summary>
  	/// <c>System.Runtime.InteropServices.TypeIdentifierAttribute</c> helper code used by <see cref="SigComparer"/>
  	/// </summary>
  	static class TIAHelper {
  		readonly struct Info : IEquatable<Info> {
  			public readonly UTF8String? Scope;
  			public readonly UTF8String? Identifier;
  
  			public Info(UTF8String? scope, UTF8String? identifier) {
  				Scope = scope;
  				Identifier = identifier;
  			}
  
  			public bool Equals(Info other) => stricmp(Scope, other.Scope) && UTF8String.Equals(Identifier, other.Identifier);
  
  			static bool stricmp(UTF8String? a, UTF8String? b) {
  				var da = a?.Data;
  				var db = b?.Data;
  				if (da == db)
  					return true;
  				if (da == null || db == null)
  					return false;
  				if (da.Length != db.Length)
  					return false;
  				for (int i = 0; i < da.Length; i++) {
  					byte ba = da[i], bb = db[i];
  					if ((byte)'A' <= ba && ba <= (byte)'Z')
  						ba = (byte)(ba - 'A' + 'a');
  					if ((byte)'A' <= bb && bb <= (byte)'Z')
  						bb = (byte)(bb - 'A' + 'a');
  					if (ba != bb)
  						return false;
  				}
  				return true;
  			}
  		}
  
  		static Info? GetInfo(TypeDef td) {
  			if (td == null)
  				return null;
  			if (td.IsWindowsRuntime)
  				return null;
  
  			UTF8String? scope = null, identifier = null;
  			var tia = td.CustomAttributes.Find("System.Runtime.InteropServices.TypeIdentifierAttribute");
  			if (tia != null) {
  				if (tia.ConstructorArguments.Count >= 2) {
  					if (tia.ConstructorArguments[0].Type.GetElementType() != ElementType.String)
  						return null;
  					if (tia.ConstructorArguments[1].Type.GetElementType() != ElementType.String)
  						return null;
  					scope = tia.ConstructorArguments[0].Value as UTF8String ?? tia.ConstructorArguments[0].Value as string;
  					identifier = tia.ConstructorArguments[1].Value as UTF8String ?? tia.ConstructorArguments[1].Value as string;
  				}
  			}
  			else {
  				var asm = td.Module?.Assembly;
  				if (asm == null)
  					return null;
  				bool isTypeLib = asm.CustomAttributes.IsDefined("System.Runtime.InteropServices.ImportedFromTypeLibAttribute") ||
  								asm.CustomAttributes.IsDefined("System.Runtime.InteropServices.PrimaryInteropAssemblyAttribute");
  				if (!isTypeLib)
  					return null;
  			}
  
  			if (UTF8String.IsNull(identifier)) {
  				CustomAttribute gca;
  				if (td.IsInterface && td.IsImport)
  					gca = td.CustomAttributes.Find("System.Runtime.InteropServices.GuidAttribute");
  				else {
  					var asm = td.Module?.Assembly;
  					if (asm == null)
  						return null;
  					gca = asm.CustomAttributes.Find("System.Runtime.InteropServices.GuidAttribute");
  				}
  				if (gca == null)
  					return null;
  				if (gca.ConstructorArguments.Count < 1)
  					return null;
  				if (gca.ConstructorArguments[0].Type.GetElementType() != ElementType.String)
  					return null;
  				scope = gca.ConstructorArguments[0].Value as UTF8String ?? gca.ConstructorArguments[0].Value as string;
  				var ns = td.Namespace;
  				var name = td.Name;
  				if (UTF8String.IsNullOrEmpty(ns))
  					identifier = name;
  				else if (UTF8String.IsNullOrEmpty(name))
  					identifier = new UTF8String(Concat(ns.Data, (byte)'.', Array.Empty<byte>()));
  				else
  					identifier = new UTF8String(Concat(ns.Data, (byte)'.', name.Data));
  			}
  			return new Info(scope, identifier);
  		}
  
  		static byte[] Concat(byte[] a, byte b, byte[] c) {
  			var data = new byte[a.Length + 1 + c.Length];
  			for (int i = 0; i < a.Length; i++)
  				data[i] = a[i];
  			data[a.Length] = b;
  			for (int i = 0, j = a.Length + 1; i < c.Length; i++, j++)
  				data[j] = c[i];
  			return data;
  		}
  
  		internal static bool IsTypeDefEquivalent(TypeDef td) => GetInfo(td) != null && CheckEquivalent(td);
  
  		static bool CheckEquivalent(TypeDef td) {
  			Debug.Assert(td != null);
  
  			for (int i = 0; td != null && i < 1000; i++) {
  				if (i != 0) {
  					var info = GetInfo(td);
  					if (info == null)
  						return false;
  				}
  
  				bool f;
  				if (td.IsInterface)
  					f = td.IsImport || td.CustomAttributes.IsDefined("System.Runtime.InteropServices.ComEventInterfaceAttribute");
  				else
  					f = td.IsValueType || td.IsDelegate;
  				if (!f)
  					return false;
  				if (td.GenericParameters.Count > 0)
  					return false;
  
  				var declType = td.DeclaringType;
  				if (declType == null)
  					return td.IsPublic;
  
  				if (!td.IsNestedPublic)
  					return false;
  				td = declType;
  			}
  
  			return false;
  		}
  
  		public static bool Equivalent(TypeDef td1, TypeDef td2) {
  			var info1 = GetInfo(td1);
  			if (info1 == null)
  				return false;
  			var info2 = GetInfo(td2);
  			if (info2 == null)
  				return false;
  			if (!CheckEquivalent(td1) || !CheckEquivalent(td2))
  				return false;
  			if (!info1.Value.Equals(info2.Value))
  				return false;
  
  			// Caller has already compared names of the types and any declaring types
  
  			for (int i = 0; i < 1000; i++) {
  				if (td1.IsInterface) {
  					if (!td2.IsInterface)
  						return false;
  				}
  				else {
  					var bt1 = td1.BaseType;
  					var bt2 = td2.BaseType;
  					if (bt1 == null || bt2 == null)
  						return false;
  					if (td1.IsDelegate) {
  						if (!td2.IsDelegate)
  							return false;
  						if (!DelegateEquals(td1, td2))
  							return false;
  					}
  					else if (td1.IsValueType) {
  						if (td1.IsEnum != td2.IsEnum)
  							return false;
  						if (!td2.IsValueType)
  							return false;
  						if (!ValueTypeEquals(td1, td2, td1.IsEnum))
  							return false;
  					}
  					else
  						return false;
  				}
  
  				td1 = td1.DeclaringType;
  				td2 = td2.DeclaringType;
  				if (td1 == null && td2 == null)
  					break;
  				if (td1 == null || td2 == null)
  					return false;
  			}
  
  			return true;
  		}
  
  		static bool DelegateEquals(TypeDef td1, TypeDef td2) {
  			var invoke1 = td1.FindMethod(InvokeString);
  			var invoke2 = td2.FindMethod(InvokeString);
  
gt: 			if (invoke1 == null || invoke2 == null)