using log4net;
using skyscraper5.Docsis;
using skyscraper5.DsmCc;
using skyscraper5.Dvb;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper.Gps;
using skyscraper5.Skyscraper.IO.TunerInterface;
using skyscraper5.Skyscraper.Scraper.Storage;
using skyscraper5.Skyscraper.Scraper.StreamAutodetection;
using skyscraper5.src.Skyscraper.Scraper.Dns;
using skyscraper5.T2MI;
using skyscraper8.yo3explorer;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using skyscraper8.Skyscraper.Net;
using skyscraper8.Skyscraper.Net.Pcap;
using skyscraper8.Skyscraper.Plugins;
using skyscraper8.Skyscraper.Scraper.Storage;
using skyscraper8.Skyscraper.Text;
using skyscraper5.src.InteractionChannel.Model.Descriptors;

namespace skyscraper5.Skyscraper.Plugins
{
	internal class PluginManager
	{
		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);

		private PluginManager()
		{
			_mpePluginTypes = new List<Type>();
			_gpsReceiverFactories = new Dictionary<int, IGpsReceiverFactory>();
			descriptorBannedTables = new Dictionary<string, bool[]>();
			descriptorMap = new Dictionary<string, ConstructorInfo[]>();
			_streamTypeAutodetectionContestants = new List<Type>();
			_t2MiPacketConstructorInfos = new ConstructorInfo[256];
			tunerFactories = new Dictionary<TunerFactoryIdAttribute, ITunerFactory>();
			_docsisMacManagementMessageTypes = new Dictionary<MacManagementMessageTypeAttribute, Type>();
			_userDefinedDescriptors = new Dictionary<UserDefinedDescriptorAttribute, ConstructorInfo>();
			_extensionDescriptors = new Tuple<ConstructorInfo, ExtensionDescriptorAttribute>[256];
			_dsmCcMessages = new ConstructorInfo[ushort.MaxValue];
			_mpeg2ExtensionDescriptors = new ConstructorInfo[256];

			ScanAssembly(this.GetType().Assembly);

			FileInfo skyscraperMainAssembly = GetSkyscraperMainAssembly();
			DirectoryInfo skyscraperHome = skyscraperMainAssembly.Directory;
			LoadIni(skyscraperHome);

			if (skyscraperMainAssembly != null)
			{
				DirectoryInfo directory = skyscraperMainAssembly.Directory;
				logger.DebugFormat("Found skyscraper main assembly at: {0}", directory.FullName);
				FileInfo[] fileInfos = directory.GetFiles("skyscraper5.*.dll");
				foreach (FileInfo fileInfo in fileInfos)
				{
					try
					{
						logger.Info(String.Format("Trying to load: {0}", fileInfo.Name));
						Assembly loadFile = Assembly.LoadFile(fileInfo.FullName);
						ScanAssembly(loadFile);
						logger.Debug(String.Format("Loaded {0}", fileInfo.Name));
					}
					catch (Exception e)
					{
						logger.ErrorFormat("Failed to scan assembly {0}", fileInfo.Name);
					}
				}
			}
		}

		private void LoadIni(DirectoryInfo skyscraperHome)
		{
			iniFileInfo = new FileInfo(Path.Combine(skyscraperHome.FullName, iniFilename));
			if (iniFileInfo.Exists)
			{
				Ini = new Ini(iniFileInfo.FullName);
				if (Ini.ContainsKey("plugins"))
				{
					IniSection iniSection = Ini["plugins"];
					foreach (KeyValuePair<string, string> valuePair in iniSection)
					{
						FileInfo pluginFileInfo = new FileInfo(valuePair.Value);
						if (!pluginFileInfo.Exists)
							continue;

						if (resolveDirectoryInfos == null)
							resolveDirectoryInfos = new HashSet<DirectoryInfo>();
						if (IsOnWindows())
						{
							string runtimePath = Path.Combine(pluginFileInfo.Directory.FullName, "runtimes", "win", "lib", "netstandard2.0");
							DirectoryInfo runtimeDi = new DirectoryInfo(runtimePath);
							if (runtimeDi.Exists)
								resolveDirectoryInfos.Add(runtimeDi);
						}
						resolveDirectoryInfos.Add(pluginFileInfo.Directory);

						Assembly assembly = Assembly.LoadFile(pluginFileInfo.FullName);
						AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
						ScanAssembly(assembly);
					}
				}
			}
			else
			{
				Debug.WriteLine(String.Format("{0} was not found. Create it using the UI!", iniFileInfo.FullName));
				Ini = new Ini();
			}
		}

		private FileInfo GetSkyscraperMainAssembly()
		{
			try
			{
				Type type = typeof(PluginManager);
				Assembly assembly = type.Assembly;
				string location = assembly.Location;
				FileInfo fileInfo = new FileInfo(location);
				return fileInfo;
			}
			catch (Exception e)
			{
				logger.Warn(String.Format("Failed to resolve skyscraper main assembly."), e);
				return null;
			}
		}

		private HashSet<DirectoryInfo> resolveDirectoryInfos;
		private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
		{
			Debug.WriteLine("Trying to resolve: {0}", args.Name);
			string[] strings = args.Name.Split(',');
			string fname = strings[0] + ".dll";

			foreach (DirectoryInfo directoryInfo in resolveDirectoryInfos)
			{
				string path = Path.Combine(directoryInfo.FullName, fname);
				if (File.Exists(path))
				{
					Assembly assembly = Assembly.LoadFile(path);
					if (UsableAssembly(args.Name, assembly.GetName()))
						return assembly;
				}
			}

			return null;
		}

		private bool IsOnWindows()
		{
			switch (Environment.OSVersion.Platform)
			{
				case PlatformID.Win32NT: return true;
				case PlatformID.MacOSX: return false;
				case PlatformID.Unix: return true;
				case PlatformID.Other: return false;
				default:
					throw new NotImplementedException(Environment.OSVersion.Platform.ToString());
			}
		}

		private bool UsableAssembly(string requestedName, AssemblyName loadedName)
		{
			AssemblyName an = new AssemblyName(requestedName);
			if (an.Equals(loadedName))
				return true;

			if (an.Name.Equals(loadedName.Name))
			{
				if (an.Version <= loadedName.Version)
				{
					byte[] ltoken = an.GetPublicKeyToken();
					byte[] rtoken = loadedName.GetPublicKeyToken();
					bool result = ltoken.IsEqual(rtoken);
					return result;
				}
			}

			return false;
		}


		private FileInfo iniFileInfo;
		private const string iniFilename = "skyscraper5.ini";
		private static PluginManager _instance;

		public static PluginManager GetInstance()
		{
			if (_instance == null)
			{
				_instance = new PluginManager();
			}
			return _instance;
		}

		public static void NotifyIniUpdate(Ini newIni)
		{
			if (_instance == null)
				return;

			_instance.Ini = newIni;
		}

		private Type storageIdAttributeTyoe = typeof(StorageIdAttribute);
		private Type objectStorageFactoryType = typeof(ObjectStorageFactory);
		private Type dataStorageFactoryType = typeof(DataStorageFactory);
		private Type dnsParserPluginType = typeof(IDnsParser);
		private Type pluginLoadHookType = typeof(IPluginLoadHook);
		private Type docsisMacManagementType = typeof(MacManagementMessage);
		private Type tunerFactoryType = typeof(ITunerFactory);
		private Type t2MiPacketType = typeof(T2MiPacket);
		private Type mpePluginType = typeof(ISkyscraperMpePlugin);
		private Type gpsReceiverFactoryType = typeof(IGpsReceiverFactory);
		private Type streamTypeAutodetectionContestantType = typeof(Contestant);
		private Type filesystemProcessorType = typeof(FilesystemProcessorPlugin);
		private Type textDecoderType = typeof(TextDecoder);
		private PluginPrioritySorter sorter = new PluginPrioritySorter();
		private Type ipTrafficHandler = typeof(IpTrafficHandler);

		private List<IDnsParser> _dnsParsers;
		private List<Type> _mpePluginTypes;
		private Dictionary<int, IGpsReceiverFactory> _gpsReceiverFactories;
		private Dictionary<string, ConstructorInfo[]> descriptorMap;
		private Dictionary<string, bool[]> descriptorBannedTables;
		private List<Type> _streamTypeAutodetectionContestants;
		private ConstructorInfo[] _t2MiPacketConstructorInfos;
		private Dictionary<TunerFactoryIdAttribute, ITunerFactory> tunerFactories;
		private Dictionary<MacManagementMessageTypeAttribute, Type> _docsisMacManagementMessageTypes;
		private Dictionary<UserDefinedDescriptorAttribute, ConstructorInfo> _userDefinedDescriptors;
		private Tuple<ConstructorInfo, ExtensionDescriptorAttribute>[] _extensionDescriptors;
		private ConstructorInfo[] _dsmCcMessages;
		private ConstructorInfo[] _mpeg2ExtensionDescriptors;
		private TextDecoder[] _textDecoders;
		private Dictionary<int, Type> _ipTrafficHandles;

		private void ScanAssembly(Assembly assembly)
		{
			AssemblyName assemblyName = assembly.GetName();
			logger.DebugFormat("Scan assembly: {0}", assemblyName.Name);
			foreach (Type type in assembly.GetTypes())
			{
				bool isSkyscraperPlugin = type.GetCustomAttributes(typeof(SkyscraperPluginAttribute)).Any();
				if (!isSkyscraperPlugin)
					continue;

				bool isStorageFactory = false;
				if (type.IsAssignableTo(objectStorageFactoryType))
				{
					HandleObjectStorageFactory(type);
					isStorageFactory = true;
				}
				if (type.IsAssignableTo(dataStorageFactoryType))
				{
					HandleDataStorageFactory(type);
					isStorageFactory = true;
				}

				if (isStorageFactory)
					continue;

				if (type.IsAssignableTo(mpePluginType))
				{
					_mpePluginTypes.Add(type);
					continue;
				}
				else if (type.IsAssignableTo(gpsReceiverFactoryType))
				{
					IGpsReceiverFactory gpsReceiverFactory = (IGpsReceiverFactory)Activator.CreateInstance(type);
					int gpsReceiverKey = PluginPrioritySorter.GetPriorityFromObject(gpsReceiverFactory);
					_gpsReceiverFactories.Add(gpsReceiverKey, gpsReceiverFactory);
					continue;
				}
				else if (type.GetCustomAttribute(typeof(TsDescriptorAttribute)) != null)
				{
					HandleTsDescriptor(type);
					continue;
				}
				else if (type.IsAssignableTo(streamTypeAutodetectionContestantType))
				{
					_streamTypeAutodetectionContestants.Add(type);
					continue;
				}
				else if (type.IsAssignableTo(t2MiPacketType))
				{
					HandleT2MiPacketType(type);
					continue;
				}
				else if (type.IsAssignableTo(tunerFactoryType))
				{
					HandleTunerFactory(type);
					continue;
				}
				else if (type.IsAssignableTo(docsisMacManagementType))
				{
					HandleDocsisMacManagementType(type);
					continue;
				}
				else if (type.GetCustomAttribute(typeof(UserDefinedDescriptorAttribute)) != null)
				{
					HandleUserDefinedDescriptor(type);
					continue;
				}
				else if (type.GetCustomAttribute(typeof(ExtensionDescriptorAttribute)) != null)
				{
					HandleExtensionDescriptor(type);
					continue;
				}
				else if (type.GetCustomAttribute(typeof(DsmCcMessageAttribute)) != null)
				{
					HandleDsmCcMessage(type);
					continue;
				}
				else if (type.GetCustomAttribute(typeof(Mpeg2ExtensionDescriptorAttribute)) != null)
				{
					HandleMpeg2ExtensionDescriptor(type);
					continue;
				}
				else if (type.IsAssignableTo(pluginLoadHookType))
				{
					IPluginLoadHook loadHook = (IPluginLoadHook)Activator.CreateInstance(type);
					loadHook.OnLoad();
					continue;
				}
				else if (type.IsAssignableTo(dnsParserPluginType))
				{
					HandleDnsParser(type);
					continue;
				}
				else if (type.IsAssignableTo(filesystemProcessorType))
				{
					HandleFilesystemProcessor(type);
					continue;
				}
				else if (type.IsAssignableTo(textDecoderType))
				{
					HandleTextDecoder(type);
					continue;
				}
				else if (type.IsAssignableTo(ipTrafficHandler))
				{
					HandleIpTrafficHandler(type);
					continue;
				}

				throw new NotImplementedException();
			}
			
			_mpePluginTypes.Sort(sorter);
		}

		private void HandleIpTrafficHandler(Type type)
		{
			Attribute attribute = type.GetCustomAttribute(typeof(StorageIdAttribute));
			if (attribute == null)
				throw new PluginsException(String.Format("The type {0} does not have a {1}", type.Name, typeof(StorageIdAttribute)));

			if (_ipTrafficHandles == null)
				_ipTrafficHandles = new Dictionary<int, Type>();

			StorageIdAttribute sia = (StorageIdAttribute)attribute;
			if (_ipTrafficHandles.ContainsKey(sia.Id))
			{
				throw new PluginsException(String.Format("Multiple {0} claim to have ID {1}. {2} <-> {3}", nameof(IpTrafficHandler), sia.Id, _ipTrafficHandles[sia.Id].Name,type.Name));
			}

			_ipTrafficHandles.Add(sia.Id, type);
		}

		private void HandleTextDecoder(Type type)
		{
			List<EncodingTypeIdAttribute> idAttributes = type.GetCustomAttributes<EncodingTypeIdAttribute>().ToList();
			if (idAttributes == null || idAttributes.Count == 0)
			{
				logger.ErrorFormat("{0} does not have any {1}", type.Name, nameof(EncodingTypeIdAttribute));
				return;
			}

			if (_textDecoders == null)
				_textDecoders = new TextDecoder[byte.MaxValue];

			object instance = Activator.CreateInstance(type);
			TextDecoder textDecoder = (TextDecoder)instance;
			foreach (EncodingTypeIdAttribute attribute in idAttributes)
			{
				logger.DebugFormat("Text Decoder 0x{0:X2} is {1}", attribute.Id, type.Name);
				_textDecoders[attribute.Id] = textDecoder;
			}
		}

		#region Scraper Storages

		private Dictionary<int, DataStorageFactory> _dataStorages;
		private Dictionary<int, ObjectStorageFactory> _objectStorages;
		private void HandleDataStorageFactory(Type type)
		{
			Attribute idAttribute = type.GetCustomAttributes(storageIdAttributeTyoe).FirstOrDefault();
			if (idAttribute == null)
			{
				logger.ErrorFormat("{0} does not have an a {1}", type, storageIdAttributeTyoe.Name);
				return;
			}
			int id = ((StorageIdAttribute)idAttribute).Id;

			if (_dataStorages == null)
				_dataStorages = new Dictionary<int, DataStorageFactory>();

			DataStorageFactory child = (DataStorageFactory)Activator.CreateInstance(type);
			_dataStorages.Add(id, child);
		}

		private void HandleObjectStorageFactory(Type type)
		{
			Attribute idAttribute = type.GetCustomAttributes(storageIdAttributeTyoe).FirstOrDefault();
			if (idAttribute == null)
			{
				logger.ErrorFormat("{0} does not have an a {1}", type, storageIdAttributeTyoe.Name);
				return;
			}
			int id = ((StorageIdAttribute)idAttribute).Id;

			if (_objectStorages == null)
				_objectStorages = new Dictionary<int, ObjectStorageFactory>();

			ObjectStorageFactory child = (ObjectStorageFactory)Activator.CreateInstance(type);
			_objectStorages.Add(id, child);
		}
		#endregion

		#region DNS Plugin
		private void HandleDnsParser(Type type)
		{
			if (_dnsParsers == null)
				_dnsParsers = new List<IDnsParser>();

			IDnsParser idp = (IDnsParser)Activator.CreateInstance(type);
			_dnsParsers.Add(idp);
		}
		#endregion DNS Plugin

		private void HandleMpeg2ExtensionDescriptor(Type typeCandidate)
		{
			bool valid = false;

			Type targetAttributeType = typeof(Mpeg2ExtensionDescriptorAttribute);
			Type byteArrayType = typeof(byte[]);

			object[] attributes = typeCandidate.GetCustomAttributes(targetAttributeType, false);
			if (attributes.Length == 0)
				return;
			Mpeg2ExtensionDescriptorAttribute attribute = (Mpeg2ExtensionDescriptorAttribute)attributes[0];

			ConstructorInfo constructorInfo = typeCandidate.GetConstructor(new Type[] { byteArrayType });
			if (constructorInfo == null)
				throw new NotImplementedException(String.Format("{0} does not implement the correct constructor.", typeCandidate.Name));

			_mpeg2ExtensionDescriptors[attribute.Tag] = constructorInfo;
			valid = true;

			if (!valid)
			{
				throw new NotImplementedException(String.Format("{0} does not implement any MPEG-2 Extension Descriptors.", typeCandidate.Name));
			}
		}

		#region DSM-CC Messages

		private void HandleDsmCcMessage(Type type)
		{
			bool valid = false;
			Attribute attribute = type.GetCustomAttributes(typeof(DsmCcMessageAttribute)).FirstOrDefault();
			if (attribute != null)
			{
				DsmCcMessageAttribute dsmCcMessageAttribute = (DsmCcMessageAttribute)attribute;
				ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(byte[]) });
				if (constructorInfo == null)
					return;

				_dsmCcMessages[dsmCcMessageAttribute.MessageId] = constructorInfo;
				valid = true;
			}

			if (!valid)
				throw new ArgumentException(String.Format("{0} does not contain DSM-CC Message definitions.", type.FullName));
		}

		#endregion

		#region DVB Extension Descriptors
		private void HandleExtensionDescriptor(Type type)
		{
			bool foundSomeExtensionTypes = false;

			Assembly assembly = this.GetType().Assembly;
			Type[] types = assembly.GetTypes();

			Attribute attribute = type.GetCustomAttributes(typeof(ExtensionDescriptorAttribute)).FirstOrDefault();
			if (attribute != null)
			{
				ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(byte[]) });
				if (constructorInfo == null)
					return;

				ExtensionDescriptorAttribute unboxedAttribute = attribute as ExtensionDescriptorAttribute;
				_extensionDescriptors[unboxedAttribute.DescriptorId] = new Tuple<ConstructorInfo, ExtensionDescriptorAttribute>(constructorInfo, unboxedAttribute);
				foundSomeExtensionTypes = true;
			}

			if (!foundSomeExtensionTypes)
			{
				throw new AccessViolationException("Wait, this isn't supposed to happen! There are no extension descriptor types in my assembly!");
			}
		}
		#endregion

		#region User Defined Descriptors
		private void HandleUserDefinedDescriptor(Type type)
		{
			bool valid = false;
			Attribute attribute = type.GetCustomAttributes(typeof(UserDefinedDescriptorAttribute)).FirstOrDefault();
			if (attribute != null)
			{
				UserDefinedDescriptorAttribute userDefinedDescriptorAttribute = (UserDefinedDescriptorAttribute)attribute;
				ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(byte[]) });
				if (constructorInfo == null)
					return;

				_userDefinedDescriptors.Add(userDefinedDescriptorAttribute, constructorInfo);
				valid = true;
			}

			if (!valid)
				throw new ArgumentException(String.Format("{0} does not contain User defined descriptors."));
		}
		#endregion

		#region DOCSIS MAC Management Type

		private void HandleDocsisMacManagementType(Type type1)
		{
			foreach (MacManagementMessageTypeAttribute macManagementMessageTypeAttribute in type1.GetCustomAttributes<MacManagementMessageTypeAttribute>())
			{
				_docsisMacManagementMessageTypes.Add(macManagementMessageTypeAttribute, type1);
			}
		}
		#endregion

		#region Tuner Factory
		private Type tunerFactoryIdType = typeof(TunerFactoryIdAttribute);

		private void HandleTunerFactory(Type type)
		{
			object[] customAttributes = type.GetCustomAttributes(tunerFactoryIdType, false);
			if (customAttributes.Length == 0)
				return;

			TunerFactoryIdAttribute attribute = (TunerFactoryIdAttribute)customAttributes[0];

			ITunerFactory issf = (ITunerFactory)Activator.CreateInstance(type);
			tunerFactories.Add(attribute, issf);
		}
		#endregion
		
		#region T2-MI Packet Types

		private Type[] t2MiConstructorPattern = new Type[] { typeof(T2MIHeader), typeof(byte[]) };
		private Type t2MiPacketTypeAttribute = typeof(T2MiPacketTypeAttribute);
		private void HandleT2MiPacketType(Type type)
		{
			ConstructorInfo constructorInfo = type.GetConstructor(t2MiConstructorPattern);
			if (constructorInfo == null)
				return;

			Attribute customAttribute = type.GetCustomAttribute(t2MiPacketTypeAttribute);
			if (customAttribute == null)
			{
				throw new T2MiException(String.Format("{0} is missing the {1}", type.Name, t2MiPacketTypeAttribute.Name));
			}

			T2MiPacketTypeAttribute packetType = (T2MiPacketTypeAttribute)customAttribute;
			_t2MiPacketConstructorInfos[packetType.PacketType] = constructorInfo;
		}
		#endregion

		#region Ts Descriptor
		private void HandleTsDescriptor(Type type)
		{
			Attribute attribute = type.GetCustomAttributes(typeof(TsDescriptorAttribute)).FirstOrDefault();
			if (attribute != null)
			{
				ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(byte[]) });
				if (constructorInfo == null)
					return;

				TsDescriptorAttribute unboxedAttribute = attribute as TsDescriptorAttribute;
				foreach (string compatibleTable in unboxedAttribute.CompatibleTables)
				{
					ConstructorInfo[] constructorInfos = UpsertTable(compatibleTable);
					constructorInfos[unboxedAttribute.DescriptorId] = constructorInfo;
				}

				attribute = type.GetCustomAttributes(typeof(BannedTableAttribute)).FirstOrDefault();
				if (attribute != null)
				{
					BannedTableAttribute bannedTableAttribute = attribute as BannedTableAttribute;
					foreach (string table in bannedTableAttribute.Tables)
					{
						bool[] upsertBannedTable = UpsertBannedTable(table);
						upsertBannedTable[unboxedAttribute.DescriptorId] = true;
					}
				}
			}
		}

		private ConstructorInfo[] UpsertTable(string table)
		{
			if (!descriptorMap.ContainsKey(table))
			{
				descriptorMap[table] = new ConstructorInfo[256];
			}
			return descriptorMap[table];
		}

		private bool[] UpsertBannedTable(string table)
		{
			if (!descriptorBannedTables.ContainsKey(table))
			{
				descriptorBannedTables[table] = new bool[256];
			}
			return descriptorBannedTables[table];
		}
		#endregion

		#region yo3explorers

		private List<FilesystemProcessorPlugin> filesystemProcessorPlugins;
		private void HandleFilesystemProcessor(Type t)
		{
			if (filesystemProcessorPlugins == null)
				filesystemProcessorPlugins = new List<FilesystemProcessorPlugin>();

			FilesystemProcessorPlugin child = (FilesystemProcessorPlugin)Activator.CreateInstance(t);
			filesystemProcessorPlugins.Add(child);
		}

		#endregion

		public Ini Ini { get; private set; }

		public IReadOnlyList<FilesystemProcessorPlugin> GetFilesystemProcessors()
		{
			if (filesystemProcessorPlugins == null)
				return new List<FilesystemProcessorPlugin>();

			return filesystemProcessorPlugins.AsReadOnly();
		}

		public IReadOnlyList<IDnsParser> GetDnsParsers()
		{
			if (_dnsParsers == null)
				return new List<IDnsParser>();
			return _dnsParsers.AsReadOnly();
		}

		public Tuple<ConstructorInfo, ExtensionDescriptorAttribute>[] GetExtensionDescriptors()
		{
			Tuple<ConstructorInfo, ExtensionDescriptorAttribute>[] target = new Tuple<ConstructorInfo, ExtensionDescriptorAttribute>[256];
			_extensionDescriptors.CopyTo(target, 0);
			return target;
		}

		public ReadOnlyDictionary<MacManagementMessageTypeAttribute, Type> GetDocsisMacManagementMessageTypes()
		{
			return new ReadOnlyDictionary<MacManagementMessageTypeAttribute, Type>(_docsisMacManagementMessageTypes);
		}
		public ReadOnlyDictionary<TunerFactoryIdAttribute, ITunerFactory> GetTunerFactories()
		{
			ReadOnlyDictionary<TunerFactoryIdAttribute, ITunerFactory> result =
				new ReadOnlyDictionary<TunerFactoryIdAttribute, ITunerFactory>(tunerFactories);
			return result;
		}
		
		public ConstructorInfo[] GetT2MiPacketTypes()
		{
			ConstructorInfo[] t2MiPacketConstructorInfos = _t2MiPacketConstructorInfos;
			ConstructorInfo[] result = new ConstructorInfo[t2MiPacketConstructorInfos.Length];
			Array.Copy(t2MiPacketConstructorInfos, 0, result, 0, t2MiPacketConstructorInfos.Length);
			return result;
		}

		public ReadOnlyCollection<ISkyscraperMpePlugin> GetMpePlugins()
		{
			List<ISkyscraperMpePlugin> result = new List<ISkyscraperMpePlugin>();
			foreach(Type t in _mpePluginTypes)
			{
				object? v = Activator.CreateInstance(t);
				ISkyscraperMpePlugin mpePlugin = v as ISkyscraperMpePlugin;
				if (mpePlugin != null)
					result.Add(mpePlugin);
			}
			return result.AsReadOnly();
		}

		public ReadOnlyDictionary<int, IGpsReceiverFactory> GetGpsReceiverFactories()
		{
			return new ReadOnlyDictionary<int, IGpsReceiverFactory>(_gpsReceiverFactories);
		}

		public ReadOnlyDictionary<string, ConstructorInfo[]> GetDescriptorMappings()
		{
			return new ReadOnlyDictionary<string, ConstructorInfo[]>(descriptorMap);
		}

		public ReadOnlyDictionary<string, bool[]> GetDescriptorTableBans()
		{
			return new ReadOnlyDictionary<string, bool[]>(descriptorBannedTables);
		}

		public ReadOnlyCollection<Type> GetStreamTypeAutodetectionContestants()
		{
			return _streamTypeAutodetectionContestants.AsReadOnly();
		}

		public ReadOnlyDictionary<UserDefinedDescriptorAttribute, ConstructorInfo> GetUserDefinedDescriptors()
		{
			return new ReadOnlyDictionary<UserDefinedDescriptorAttribute, ConstructorInfo>(_userDefinedDescriptors);
		}

		public ConstructorInfo[] GetDsmCcMessages()
		{
			ConstructorInfo[] output = new ConstructorInfo[_dsmCcMessages.Length];
			Array.Copy(_dsmCcMessages, 0, output, 0, _dsmCcMessages.Length);
			return output;
		}

		public ConstructorInfo[] GetMpeg2ExtensionDescriptors()
		{
			ConstructorInfo[] output = new ConstructorInfo[_mpeg2ExtensionDescriptors.Length];
			Array.Copy(_mpeg2ExtensionDescriptors, 0, output, 0, _mpeg2ExtensionDescriptors.Length);
			return output;
		}

		public ReadOnlyDictionary<int, ObjectStorageFactory> GetObjectStorages()
		{
			return _objectStorages.AsReadOnly();
		}

		public ReadOnlyDictionary<int, DataStorageFactory> GetDataStorages()
		{
			return _dataStorages.AsReadOnly();
		}

		public ReadOnlyDictionary<int, Type> GetIpTrafficHandlers()
		{
			return _ipTrafficHandles.AsReadOnly();
		}

		public void AutoconfigureObject(string categoryName, object targetObject)
		{
			if (Ini == null)
				Ini = new Ini();
			Type bootingStorageType = targetObject.GetType();
			PropertyInfo[] propertyInfos = bootingStorageType.GetProperties();
			foreach (PropertyInfo propertyInfo in propertyInfos)
			{
				Type propertyInfoPropertyType = propertyInfo.PropertyType;
				object toSet;
				switch (propertyInfoPropertyType.Name)
				{
					case nameof(String):
						toSet = Ini.ReadValue(categoryName, propertyInfo.Name, "");
						break;
					case nameof(Int32):
						toSet = Ini.ReadValue(categoryName, propertyInfo.Name, 0);
						break;
					case nameof(Boolean):
						toSet = Ini.ReadValue(categoryName, propertyInfo.Name, false);
						break;
					case nameof(Single):
						toSet = Ini.ReadValue(categoryName, propertyInfo.Name, 0.0f);
						break;
					case nameof(UInt16):
						toSet = Ini.ReadValue(categoryName, propertyInfo.Name, 0);
						toSet = Convert.ToUInt16((int)toSet);
						break;
					default:
						throw new NotImplementedException(propertyInfoPropertyType.Name);
				}

				propertyInfo.SetValue(targetObject, toSet);
			}
		}

		public TextDecoder[] GetTextDecoders()
		{
			object clone = _textDecoders.Clone();
			TextDecoder[] saneClone = (TextDecoder[])clone;
			return saneClone;
		}

		public void SaveConfiguration()
		{
			Ini.Export(iniFileInfo);
		}
	}
}
