using log4net;
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.Plugins;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Skyscraper.Net;
using skyscraper5.Skyscraper.Scraper.Storage;
using skyscraper8.Skyscraper.Net;
using skyscraper8.Skyscraper.Net.Pcap;

namespace skyscraper8.Skyscraper.Scraper.Storage
{
	public class StorageConnectionManager
	{
		private StorageConnectionManager()
		{
			plugins = PluginManager.GetInstance();
			ini = plugins.Ini;
		}

		private readonly PluginManager plugins;
		private static StorageConnectionManager _instance;
		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
		private static Type storageNameAttributeType = typeof(StorageNameAttribute);
		private readonly Ini ini;

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

		public IEnumerable<Tuple<int,bool,string>> GetAllKnownFactoryNames()
		{
			ReadOnlyDictionary<int, DataStorageFactory> dataStorages = plugins.GetDataStorages();
			foreach (KeyValuePair<int, DataStorageFactory> dataStorageFactory in dataStorages)
			{
				yield return new Tuple<int, bool, string>(dataStorageFactory.Key, true, GetName(dataStorageFactory.Value));
			}

			ReadOnlyDictionary<int, ObjectStorageFactory> objectStorages = plugins.GetObjectStorages();
			foreach (KeyValuePair<int, ObjectStorageFactory> objectStorageFactory in objectStorages)
			{
				yield return new Tuple<int, bool, string>(objectStorageFactory.Key, false, GetName(objectStorageFactory.Value));
			}
		}

		public string GetName(object o)
		{
			Type type = o.GetType();
			Attribute attribute = type.GetCustomAttribute(storageNameAttributeType);
			StorageNameAttribute storageNameAttribute = attribute as StorageNameAttribute;
			if (storageNameAttribute == null)
			{
				logger.WarnFormat("{0} does not have a {1}.", type.Name, storageNameAttributeType.Name);
				return type.Name;
			}
			return storageNameAttribute.Name;
		}

		public DataStorageFactory GetDefaultDataStorageFactory()
		{
			int requiredDataStorageId = ini.ReadValue("startup", "dataStorage", -1);
			if (requiredDataStorageId == -1)
			{
				throw new ScraperStorageException("Could not determine the data storage factory from ini file. Configure it using the UI!");
			}

			string iniCategoryName = String.Format("dataStorage{0}", requiredDataStorageId);
			if (!ini.ContainsKey(iniCategoryName))
			{
				throw new ScraperStorageException("Could not find configuration for the storage factory. Configure it using the UI!");
			}

			ReadOnlyDictionary<int, DataStorageFactory> dataStorages = plugins.GetDataStorages();
			KeyValuePair<int, DataStorageFactory> valuePair = dataStorages.First(x => x.Key == requiredDataStorageId);
			if (valuePair.Value == null)
			{
				throw new ScraperStorageException(String.Format("Storage Factory {0} was not loaded. Reconfigure with the UI!", requiredDataStorageId));
			}

			string factoryCname = String.Format("dataStorage{0}", requiredDataStorageId);
			PluginManager.GetInstance().AutoconfigureObject(factoryCname, valuePair.Value);

			return valuePair.Value;
		}

		public ObjectStorageFactory GetDefaultObjectStorageFactory()
		{
			int requiredObjectStorageId = ini.ReadValue("startup", "objectStorage", -1);
			if (requiredObjectStorageId == -1)
			{
				throw new ScraperStorageException("Could not determine the object storage factory from ini file. Configure it using the UI!");
			}

			string iniCategoryName = String.Format("objectStorage{0}", requiredObjectStorageId);
			if (!ini.ContainsKey(iniCategoryName))
			{
				throw new ScraperStorageException("Could not find configuration for the object storage factory. Configure it using the UI!");
			}

			ReadOnlyDictionary<int, ObjectStorageFactory> dataStorages = plugins.GetObjectStorages();
			KeyValuePair<int, ObjectStorageFactory> valuePair = dataStorages.First(x => x.Key == requiredObjectStorageId);
			if (valuePair.Value == null)
			{
				throw new ScraperStorageException(String.Format("Object Storage Factory {0} was not loaded. Reconfigure with the UI!", requiredObjectStorageId));
			}

			string factoryCname = String.Format("objectStorage{0}", requiredObjectStorageId);
			PluginManager.GetInstance().AutoconfigureObject(factoryCname, valuePair.Value);

			return valuePair.Value;
		}

		public static object[] GetPluginConnectors(DataStorage dataStorage, ObjectStorage objectStorage)
		{
			object[] dataConnector = dataStorage.GetPluginConnector();
			object[] objectConnector = objectStorage.GetPluginConnector();
			object[] result = new object[dataConnector.Length + objectConnector.Length];
			Array.Copy(dataConnector, 0, result, 0, dataConnector.Length);
			Array.Copy(objectConnector, 0, result, dataConnector.Length, objectConnector.Length);
			return result;
		}

		public bool IniExists()
		{
			return ini != null;
		}

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

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

		private bool IsIpTafficHandlerConfigured()
		{
			if (ini == null)
				return false;
			if (!ini.ContainsKey("ip_handler"))
				return false;
			if (!ini["ip_handler"].ContainsKey("type"))
				return false;
			return true;
		}
		
		
		internal IpTrafficHandler GetDefaultIpTrafficHandler()
		{
			Type targetType;
			if (IsIpTafficHandlerConfigured())
			{
				ReadOnlyDictionary<int, Type> handlers = plugins.GetIpTrafficHandlers();
				int type = Int32.Parse(ini["ip_handler"]["type"]);
				if (!handlers.ContainsKey(type))
				{
					logger.WarnFormat("The IP traffic handler with ID {0} was not found. I'm gonna assume you want the pcap files.", type);
					targetType = typeof(PcapIpTrafficHandler);
				}
				else
				{
					targetType = handlers[type];
				}
			}
			else
			{
				logger.WarnFormat("You didn't call \"pcapon\" or \"pcapoff\" before. I'm gonna assume you want the pcap files.");
				targetType = typeof(PcapIpTrafficHandler);
			}

			ConstructorInfo constructorInfo = targetType.GetConstructor(new Type[] { });
			if (constructorInfo == null)
			{
				throw new ScraperStorageException(String.Format("The type {0} doesn't contain a parameterless constructor.", targetType.Name));
			}

			object invoke = constructorInfo.Invoke(new object[] { });
			return (IpTrafficHandler) invoke;
		}

		private bool IsSubTsDumpingConfigured()
		{
			if (ini == null)
				return false;
			if (!ini.ContainsKey("subts"))
				return false;
			if (!ini["subts"].ContainsKey("dump"))
				return false;
			return true;
		}

		internal bool IsSubTsDumpingEnabled()
		{
			if (IsSubTsDumpingConfigured())
			{
				return ini.ReadValue("subts", "dump", true);
			}
			else
			{
				logger.WarnFormat("You didn't call the subts option before. I'm gonna assume you want me to dump inner TS packets.");
				return true;
			}
		}

		internal string GetSubTsOutputPath()
		{
			return ini.ReadValue("subts", "outdir", "");
		}
	}
}
